Canvas absolute and relative coordinates, delta pixels retrieval - python

In this test script I draw a square I may zoom in by using the mouse wheel.
If I right click inside a cell I get the right cell coordinates (not x and y, but column and row): this is exactly what I expect it to write to the console in the background.
If I, instead, move the canvas by pressing the mouse left button and dragging it somewhere else, the coordinates are not right any more.
Where do I get the delta x and delta y (or offsets) to give back the right info?
FYI:
1) get_pos() is the method that does the check and produces the result.
2) the following code has been tested on Ubuntu 16.10 (with the latest updates) running Python 3.5.2.
import tkinter as tk
import tkinter.ttk as ttk
class GriddedMazeCanvas(tk.Canvas):
def almost_centered(self, cols, rows):
width = int(self['width'])
height = int(self['height'])
cell_dim = self.settings['cell_dim']
rows = rows % height
cols = cols % width
w = cols * cell_dim
h = rows * cell_dim
if self.zoom < 0:
raise ValueError('zoom is negative:', self.zoom)
zoom = self.zoom
if self.drawn() and 1 != zoom:
w *= zoom
h *= zoom
h_shift = (width - w) // 2
v_shift = (height - h) // 2
return [h_shift, v_shift,
h_shift + w, v_shift + h]
def __init__(self, *args, **kwargs):
if 'settings' not in kwargs:
raise ValueError("'settings' not passed.")
settings = kwargs['settings']
del kwargs['settings']
super().__init__(*args, **kwargs)
self.config(highlightthickness=0)
self.settings = settings
self.bind_events()
def draw_maze(self, cols, rows):
self.cols = cols
self.rows = rows
if self.not_drawn():
self.cells = {}
self.cell_dim = self.settings['cell_dim']
self.border_thickness = self.settings['border_thickness']
self.zoom = 1
self.delete(tk.ALL)
maze, coords = self._draw_maze(cols, rows, fix=False)
lines = self._draw_grid(coords)
return maze, lines
def _draw_maze(self, cols, rows, fix=True):
data = self.settings
to_max = data['to_max']
border_thickness = data['border_thickness']
poligon_color = data['poligon_color']
poligon_border_color = data['poligon_border_color']
coords = self.almost_centered(cols, rows)
if fix:
# Fix for the disappearing NW borders
if to_max == cols:
coords[0] += 1
if to_max == rows:
coords[1] += 1
maze = self.create_rectangle(*coords,
fill=poligon_color,
outline=poligon_border_color,
width=border_thickness,
tag='maze')
return maze, coords
def _draw_grid(self, coords):
data = self.settings
poligon_border_color = data['poligon_border_color']
cell_dim = data['cell_dim']
if coords is None:
if self.not_drawn():
raise ValueError('The maze is still uninitialized.')
x1, y1, x2, y2 = self.almost_centered(self.cols, self.rows)
else:
x1, y1, x2, y2 = coords
zoom = self.zoom
if self.drawn() and 1 != zoom:
if self.zoom < 1:
self.zoom = zoom = 1
print('no zooming below 1.')
else:
cell_dim *= zoom
lines = []
for i, x in enumerate(range(x1, x2, cell_dim)):
line = self.create_line(x, y1, x, y2,
fill=poligon_border_color,
tags=('grid', 'grid_hl_{}'.format(i)))
lines.append(line)
for i, y in enumerate(range(y1, y2, cell_dim)):
line = self.create_line(x1, y, x2, y,
fill=poligon_border_color,
tags=('grid', 'grid_vl_{}'.format(i)))
lines.append(line)
return lines
def drawn(self):
return hasattr(self, 'cells')
def not_drawn(self):
return not self.drawn()
def bind_events(self):
self.bind('<Button-4>', self.onZoomIn)
self.bind('<Button-5>', self.onZoomOut)
self.bind('<ButtonPress-1>', self.onScrollStart)
self.bind('<B1-Motion>', self.onScrollMove)
self.tag_bind('maze', '<ButtonPress-3>', self.onMouseRight)
def onScrollStart(self, event):
print(event.x, event.y, self.canvasx(event.x), self.canvasy(event.y))
self.scan_mark(event.x, event.y)
def onMouseRight(self, event):
col, row = self.get_pos(event)
print('zoom:', self.zoom, ' col, row:', col, row)
def onScrollMove(self, event):
delta = event.x, event.y
self.scan_dragto(*delta, gain=1)
def onZoomIn(self, event):
if self.not_drawn():
return
max_zoom = 9
self.zoom += 1
if self.zoom > max_zoom:
print("Can't go beyond", max_zoom)
self.zoom = max_zoom
return
print('Zooming in.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def onZoomOut(self, event):
if self.not_drawn():
return
self.zoom -= 1
if self.zoom < 1:
print("Can't go below one.")
self.zoom = 1
return
print('Zooming out.', event.num, event.x, event.y, self.zoom)
self.draw_maze(self.cols, self.rows)
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
print('x1, y1, x2, y2:', x1, y1, x2, y2,
' bbox:', self.bbox('maze'))
if not (x1 <= x <= x2 and y1 <= y <= y2):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - x1) // scale
row = (y - y1) // scale
return col, row
class CanvasButton(ttk.Button):
def freeze_origin(self):
if not hasattr(self, 'origin'):
canvas = self.canvas
self.origin = canvas.xview()[0], canvas.yview()[0]
def reset(self):
canvas = self.canvas
x, y = self.origin
canvas.yview_moveto(x)
canvas.xview_moveto(y)
def __init__(self, *args, **kwargs):
if 'canvas' not in kwargs:
raise ValueError("'canvas' not passed.")
canvas = kwargs['canvas']
del kwargs['canvas']
super().__init__(*args, **kwargs)
self.config(command=self.reset)
self.canvas = canvas
root = tk.Tk()
settings = {'cell_dim': 3,
'to_max': 200,
'border_thickness': 1,
'poligon_color': '#F7F37E',
'poligon_border_color': '#AC5D33'}
frame = ttk.Frame(root)
canvas = GriddedMazeCanvas(frame,
settings=settings,
width=640,
height=480)
button = CanvasButton(frame, text='Reset', canvas=canvas)
button.freeze_origin()
canvas.draw_maze(20, 10)
canvas.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.EW)
frame.rowconfigure(0, weight=1)
frame.grid()
root.mainloop()

Looking at a previously answered question, I learned how to find the delta x and delta y I was looking for.
So, the solution is:
def get_pos(self, event):
x, y = event.x, event.y
cols, rows = self.cols, self.rows
cell_dim, zoom = self.cell_dim, self.zoom
x1, y1, x2, y2 = self.almost_centered(cols, rows)
# the following line stores deltax and deltay into x0, y0
x0, y0 = int(self.canvasx(0)), int(self.canvasy(0))
# then it is trivial to compute the solution
xa, ya = x1 - x0, y1 - y0
xb, yb = x2 - x0, y2 - y0
if not (xa <= x <= xb and ya <= y <= yb):
print('Here we are out of bounds.')
return None, None
scale = zoom * cell_dim
col = (x - xa) // scale
row = (y - ya) // scale
return col, row

Related

Moving widgets in Canvas Tkinter

I have a canvas with a little oval in it. It moves throughout the widget using the arrow keys but when it's on the edge of the canvas if I move it beyond that, the oval just disappears.
I want the oval stays on any edge of the canvas no matter if I continue pressing the arrow key corresponding to that edge without disappearing.
This is the code:
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w//2
y = h//2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x+20, y+20, fill='cyan')
def left(event):
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x = 0
y = 10
my_canvas.move(my_circle, x, y)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
This is what it looks like:
The oval on an edge
And if I continue pressing the key looks like this:
The oval disappearing
You could test the current coordinates and compare them to your canvas size.
I created a function to get the current x1, y1, x2, y2 from your oval. This way you have the coordiantes of the borders of your oval.
So all I do is testing if the oval is touching a border.
from tkinter import *
root = Tk()
root.title("Oval")
root.geometry("800x600")
w = 600
h = 400
x = w // 2
y = h // 2
my_canvas = Canvas(root, width=w, height=h, bg='black')
my_canvas.pack(pady=20)
my_circle = my_canvas.create_oval(x, y, x + 20, y + 20, fill='cyan')
def left(event):
x1, y1, x2, y2 = get_canvas_position()
if x1 > 0:
x = -10
y = 0
my_canvas.move(my_circle, x, y)
def right(event):
x1, y1, x2, y2 = get_canvas_position()
if x2 < w:
x = 10
y = 0
my_canvas.move(my_circle, x, y)
def up(event):
x1, y1, x2, y2 = get_canvas_position()
if y1 > 0:
x = 0
y = -10
my_canvas.move(my_circle, x, y)
def down(event):
x1, y1, x2, y2 = get_canvas_position()
if y2 < h:
x = 0
y = 10
my_canvas.move(my_circle, x, y)
def get_canvas_position():
return my_canvas.coords(my_circle)
root.bind('<Left>', left)
root.bind('<Right>', right)
root.bind('<Up>', up)
root.bind('<Down>', down)
root.mainloop()
The canvas object is stored via 2 sets of coordinates [x1, y1, x2, y2]. You should check against the objects current location by using the .coords() method. The dimensions of the canvas object will affect the coordinates.
def left(event):
x = -10
y = 0
if my_canvas.coords(my_circle)[0] > 0: # index 0 is X coord left side object.
my_canvas.move(my_circle, x, y)
def right(event):
x = 10
y = 0
# The border collision now happens at 600 as per var "w" as previously defined above.
if my_canvas.coords(my_circle)[2] < w: # index 2 is X coord right side object.
my_canvas.move(my_circle, x, y)
Now repeat a similar process for up and down.

Exclude results in find_closest (Tkinter, Python)

I am writing a script to store movements over a hexgrid using Tkinter. As part of this I want to use a mouse-click on a Tkinter canvas to first identify the click location, and then draw a line between this point and the location previously clicked.
Generally this works, except that after I've drawn a line, it become an object that qualifies for future calls off the find_closest method. This means I can still draw lines between points, but selecting the underlying Hex in the Hexgrid over times becomes nearly impossible. I was wondering if someone could help me find a solution to exclude particular objects (lines) from the find_closest method.
edit: I hope this code example is minimal enough.
import tkinter
from tkinter import *
from math import radians, cos, sin, sqrt
class App:
def __init__(self, parent):
self.parent = parent
self.c1 = Canvas(self.parent, width=int(1.5*340), height=int(1.5*270), bg='white')
self.c1.grid(column=0, row=0, sticky='nsew')
self.clickcount = 0
self.clicks = [(0,0)]
self.startx = int(20*1.5)
self.starty = int(20*1.5)
self.radius = int(20*1.5) # length of a side
self.hexagons = []
self.columns = 10
self.initGrid(self.startx, self.starty, self.radius, self.columns)
self.c1.bind("<Button-1>", self.click)
def initGrid(self, x, y, radius, cols):
"""
2d grid of hexagons
"""
radius = radius
column = 0
for j in range(cols):
startx = x
starty = y
for i in range(6):
breadth = column * (1.5 * radius)
if column % 2 == 0:
offset = 0
else:
offset = radius * sqrt(3) / 2
self.draw(startx + breadth, starty + offset, radius)
starty = starty + 2 * (radius * sqrt(3) / 2)
column = column + 1
def draw(self, x, y, radius):
start_x = x
start_y = y
angle = 60
coords = []
for i in range(6):
end_x = start_x + radius * cos(radians(angle * i))
end_y = start_y + radius * sin(radians(angle * i))
coords.append([start_x, start_y])
start_x = end_x
start_y = end_y
hex = self.c1.create_polygon(coords[0][0], coords[0][1], coords[1][0], coords[1][1], coords[2][0],
coords[2][1], coords[3][0], coords[3][1], coords[4][0], coords[4][1],
coords[5][0], coords[5][1], fill='black')
self.hexagons.append(hex)
def click(self, evt):
self.clickcount = self.clickcount + 1
x, y = evt.x, evt.y
tuple_alfa = (evt.x, evt.y)
self.clicks.append(tuple_alfa)
if self.clickcount >= 2:
start = self.clicks[self.clickcount - 1]
startx = start[0]
starty = start[1]
self.c1.create_line(evt.x, evt.y, startx, starty, fill='white')
clicked = self.c1.find_closest(x, y)[0]
print(clicked)
root = tkinter.Tk()
App(root)
root.mainloop()

How can I move a list of tkinter lines in a set space?

import time
from tkinter import *
class Template:
def __init__(self):
self.window = Tk()
self.window.title("2D Display")
self.canvas = self.canvas_display()
self.line1 = self.line_creation(650,350,500 * .3, 1000)
self.line3 = self.line_movement_creation(0, 350,2000, 350)
self.horizon = self.canvas.create_line(0,350,2000, 350, width = 2, fill ="white")
self.speedx = 0 # x movement of line3
self.speedy = 9 # y movement of line3
self.active = True
self.pos1 = []
self.move_active() #Code that creates the problem
self.canvas.update()
def canvas_display(self): #canvas
canvas = Canvas(self.window, width=500, height=400, background='black')
canvas.pack(expand=True, fill="both")
canvas.update()
return canvas
Upwards is Initialization
def line_creation(self,x,y,x1,y1): #creation of multple lines
spacing = 0
lines = [] # could list([])
for i in range(11):
id = self.canvas.create_line( x, y, x1 + spacing, y1, width=2, fill="white")
lines.append(id)
spacing += 100
pos1 = self.canvas.coords(id)
self.pos1 = pos1
print(self.pos1)
return lines
This is the creation method for the vertices lines
def line_movement_creation(self,x,y,x1,y1):
spacing1 = 0
lines = []
for i in range(4):
id = self.canvas.create_line(x , y+spacing1, x1, y1 + spacing1, width=2, fill="white")
lines.append(id)
spacing1 += 100
#line = [] equal all horizontal and vertical, 12 - 15 equal horizontal moving lines
return lines
The is the creation for the horizontal lines
def line_update(self): #line movement method
for line in self.line3:
self.canvas.move(line, self.speedx, self.speedy)
#Create variables for all x and y values for the lines
pos = self.canvas.coords(line)
print(pos)
if pos[3] >= 800:
self.canvas.move(line, self.speedx, self.speedy - 460)
def move_active(self):
if self.active:
self.line_update()
self.window.after(40, self.move_active)
This is what moves the lines creating an illusion of movement. I want to take the list of horizontal lines and set them between the outer most vertical lines. So it would stay between the vertical lines. Creating a road like image. I think I need to make a separate list for both but I am not sure. So to clarify I can someone help show me how to make the horizontal lines not attach to the ends of the screen but to inside the horizontal lines Code Demonstration
def run(self):
self.window.mainloop()
if __name__ == '__main__':
Temp = Template()
Temp.run()
So, first I would suggest that you use some game engine like pygame because it is faster and provides a bit more options and other stuff but here it is with tkinter (basically it is some simple trigonometry):
import tkinter as tk
import math
class Canvas(tk.Canvas):
def __init__(self, parent, width=700, height=500, **kwargs):
super().__init__(parent, width=width, height=height, **kwargs)
self.width = width
self.height = height
self.angle = 70
self.speedy = 10
self.change_speed_ms = 50
self.draw_angle(self.angle, 5)
self.lines, self.interval = self.init_lines(amount=5)
self.draw_lines()
#property
def radians(self):
return math.radians(self.angle)
def draw_angle(self, view_angle, lines):
orient = 0
adjacent = self.height // 2
step = view_angle // lines
half = view_angle // 2
for angle in range(orient - half, orient + half + step, step):
rad = math.radians(angle)
delta = math.tan(rad) * adjacent
x1, y1 = self.width // 2, self.height // 2
x2, y2 = x1 + delta, y1 + adjacent
self.create_line(x1, y1, x2, y2)
def init_lines(self, amount=5):
interval = round((self.height // 2) / amount)
coordinate_list = list()
offset = self.height // 2
for y in range(0, self.height // 2 + interval, interval):
delta = math.tan(self.radians / 2) * y
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
y1 = y2 = y + offset
line = self.create_line(x1, y1, x2, y2)
coordinate_list.append((line, (x1, y1, x2, y2)))
return coordinate_list, interval
def draw_lines(self):
tmp_lst = list()
for id_, (x1, y1, x2, y2) in self.lines:
y1 += self.speedy
if y1 > self.height + self.interval - self.speedy:
y1 = self.height // 2
y = y2 = y1
adjacent = y - self.height // 2
delta = math.tan(self.radians / 2) * adjacent
x1 = self.width // 2 - delta
x2 = self.width // 2 + delta
self.coords(id_, x1, y1, x2, y2)
tmp_lst.append((id_, (x1, y1, x2, y2)))
self.lines = tmp_lst
self.after(self.change_speed_ms, self.draw_lines)
root = tk.Tk()
Canvas(root, highlightthickness=0).pack()
root.mainloop()
So the main method here is Canvas().draw_lines(). First of you get a list of line IDs and their coordinates at set intervals based on the amount of total lines, then you iterate over them, change their y value and accordingly calculate the opposite side of a right-angle triangle using tan and the adjacent side which is known from the current y coordinate and the starting point (the middle).

tkinter python - Can I create window collision?

New to programming. Working on a simple pong clone. Started the ball but want to make sure all sides of the window (500x500) will have the ball bounce off of it. How could I do this? Thanks!
P.S. This is my current code if needed.
import threading
import random
import time
import string
import os.path
from random import randint
from tkinter import *
class Pong:
Title = 'Pong'
Size = '500x500'
class Ball:
def __init__(self,canvas,x1,y1,x2,y2):
self.x1 =x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
self.canvas = canvas
self.ball = canvas.create_oval(self.x1, self.y1, self.x2, self.y2, fill="black")
def move_ball(self):
deltax = randint(0,5)
deltay = randint(0,5)
self.canvas.move(self.ball,deltax,deltay)
self.canvas.after(50,self.move_ball)
def PongGame():
print("Moved to PongGame.")
ball1 = Ball(canvas,10,10,30,30)
ball1.move_ball()
def titleButtonClicked(event):
print("Title screen button clicked.")
btn.pack_forget()
btn.place(x=600,y=600)
msg.pack_forget()
PongGame()
root = Tk()
root.geometry(Pong.Size)
root.title(Pong.Title)
root.resizable(False,False)
msg = Label(root, text = Pong.Title, font = ("", 50))
msg.pack()
canvas = Canvas(root, width = 500, height = 500)
canvas.pack()
btn=Button(root, text = "Start")
btn.bind('<Button-1>', titleButtonClicked)
btn.place(x=220,y=300)
root.mainloop()
Collisions are not trivial; the simplest is to reverse the x or the y velocity after checking which edge of the bounding box of the ball intersects with the boundaries of the canvas.
Maybe something like this:
import random
import tkinter as tk
WIDTH, HEIGHT = 500, 500
class Ball:
radius = 10
spawn_center = (250, 100)
def __init__(self, canvas):
self.canvas = canvas
self.id = None
self.create_ball()
self.velocity = None
self.assign_random_velocity()
self.keep_moving = True
self.move()
def create_ball(self):
xc, yc = self.spawn_center
x0, y0, = xc - self.radius, yc + self.radius
x1, y1, = xc + self.radius, yc - self.radius
self.id = self.canvas.create_oval(x0, y0, x1, y1)
def assign_random_velocity(self):
dx = random.randrange(1, 5) * random.choice((1, -1))
dy = random.randrange(1, 5) * random.choice((1, -1))
self.velocity = (dx, dy)
def move(self):
if self.keep_moving is None:
return
self.check_collision()
self.canvas.move(self.id, *self.velocity)
self.keep_moving = self.canvas.after(10, self.move)
def cancel_move(self):
if self.keep_moving is not None:
self.canvas.after_cancel(self.keep_moving)
self.keep_moving = None
def check_collision(self):
x0, y0, x1, y1 = self.canvas.coords(self.id)
dx, dy = self.velocity
if x0 < 0:
x0 = 0
dx = -dx
elif x1 > WIDTH:
x1 = WIDTH
dx = -dx
if y0 < 0:
y0 = 0
dy = -dy
elif y1 > HEIGHT:
y1 = HEIGHT
dy = -dy
self.velocity = dx, dy
class PongBoard(tk.Canvas):
def __init__(self, master):
self.master = master
super().__init__(self.master)
self.ball = None
self.spawn_new_ball()
def spawn_new_ball(self):
if self.ball is not None:
self.ball.cancel_move()
self.delete(self.ball.id)
self.ball = Ball(self)
root = Tk()
root.geometry(f'{WIDTH}x{HEIGHT+20}')
board = PongBoard(root)
new_ball_btn = tk.Button(root, text='spawn new ball', command=board.spawn_new_ball)
board.pack(expand=True, fill=tk.BOTH)
new_ball_btn.pack()
root.mainloop()
This will get you started, but you will have to implement the paddles, the paddles movement, the collision checking of the ball with the paddles, and keep the score by yourself.

How do I import a gif in Tkinter?

So I have this maze code, and it's really cool but when you play you move around a red square. Is there a way that I can change this red square that moves around into an image, instead of where it says return "red" I want to put this personaje.gif file so a little person can appear moving through the maze.
this is my code:
import random
import Tkinter as tk
import sys
from PIL import Image, ImageTk
class Application(tk.Frame):
def __init__(self, width=600, height=600, size=600):
tk.Frame.__init__(self)
self.maze = Maze(width, height)
personaje = Image.open("personaje.gif")
photo = ImageTk.PhotoImage(personaje)
self.size = size
self.steps = 0
self.grid()
self.create_widgets()
self.draw_maze()
self.create_events()
def create_widgets(self):
width = self.maze.width * self.size
height = self.maze.height * self.size
self.canvas = tk.Canvas(self, width=width, height=height)
self.canvas.grid()
self.status = tk.Label(self)
self.status.grid()
def draw_maze(self):
for i, row in enumerate(self.maze.maze):
for j, col in enumerate(row):
x0 = j * self.size
y0 = i * self.size
x1 = x0 + self.size
y1 = y0 + self.size
color = self.get_color(x=j, y=i)
id = self.canvas.create_rectangle(x0, y0, x1, y1, width=0, fill=color)
if self.maze.start_cell == (j, i):
self.cell = id
self.canvas.tag_raise(self.cell) # bring to front
self.status.config(text='Movidas mínimas: %d' % self.maze.steps)
def create_events(self):
self.canvas.bind_all('<KeyPress-Up>', self.move_cell)
self.canvas.bind_all('<KeyPress-Down>', self.move_cell)
self.canvas.bind_all('<KeyPress-Left>', self.move_cell)
self.canvas.bind_all('<KeyPress-Right>', self.move_cell)
def move_cell(self, event):
if event.keysym == 'Up':
if self.check_move(0, -1):
self.canvas.move(self.cell, 0, -self.size)
self.steps += 1
if event.keysym == 'Down':
if self.check_move(0, 1):
self.canvas.move(self.cell, 0, self.size)
self.steps += 1
if event.keysym == 'Left':
if self.check_move(-1, 0):
self.canvas.move(self.cell, -self.size, 0)
self.steps += 1
if event.keysym == 'Right':
if self.check_move(1, 0):
self.canvas.move(self.cell, self.size, 0)
self.steps += 1
args = (self.steps, self.maze.steps)
self.status.config(text='Movimientos %d/%d' % args)
self.check_status()
def check_move(self, x, y):
x0, y0 = self.get_cell_coords()
x1 = x0 + x
y1 = y0 + y
return self.maze.maze[y1][x1] == 0
def get_cell_coords(self):
position = self.canvas.coords(self.cell)
x = int(position[0] / self.size)
y = int(position[1] / self.size)
return (x, y)
def check_status(self):
if self.maze.exit_cell == self.get_cell_coords():
args = (self.steps, self.maze.steps)
self.status.config(text='Resuelto en %d/%d movidas!' % args)
def get_color(self, x, y):
if self.maze.start_cell ==(x,y):
return "red"
if self.maze.exit_cell == (x, y):
return 'green'
if self.maze.maze[y][x] == 1:
return 'black'
class Maze(object):
def __init__(self, width=21, height=21, exit_cell=(1, 1)):
self.width = width
self.height = height
self.exit_cell = exit_cell
self.create()
def create(self):
self.maze = [[1] * self.width for _ in range(self.height)] # full of walls
self.start_cell = None
self.steps = None
self.recursion_depth = None
self._visited_cells = []
self._visit_cell(self.exit_cell)
def _visit_cell(self, cell, depth=0):
x, y = cell
self.maze[y][x] = 0 # remove wall
self._visited_cells.append(cell)
neighbors = self._get_neighbors(cell)
random.shuffle(neighbors)
for neighbor in neighbors:
if not neighbor in self._visited_cells:
self._remove_wall(cell, neighbor)
self._visit_cell(neighbor, depth+1)
self._update_start_cell(cell, depth)
def _get_neighbors(self, cell):
x, y = cell
neighbors = []
# Left
if x - 2 > 0:
neighbors.append((x-2, y))
# Right
if x + 2 < self.width:
neighbors.append((x+2, y))
# Up
if y - 2 > 0:
neighbors.append((x, y-2))
# Down
if y + 2 < self.height:
neighbors.append((x, y+2))
return neighbors
def _remove_wall(self, cell, neighbor):
x0, y0 = cell
x1, y1 = neighbor
# Vertical
if x0 == x1:
x = x0
y = (y0 + y1) / 2
# Horizontal
if y0 == y1:
x = (x0 + x1) / 2
y = y0
self.maze[y][x] = 0 # remove wall
def _update_start_cell(self, cell, depth):
if depth > self.recursion_depth:
self.recursion_depth = depth
self.start_cell = cell
self.steps = depth * 2 # wall + cell
def show(self, verbose=False):
MAP = {0: ' ', # path
1: '#', # wall
2: 'B', # exit
3: 'A', # start
}
x0, y0 = self.exit_cell
self.maze[y0][x0] = 2
x1, y1 = self.start_cell
self.maze[y1][x1] = 3
for row in self.maze:
print ' '.join([MAP[col] for col in row])
if verbose:
print "Steps from A to B:", self.steps
if __name__ == '__main__':
from optparse import OptionParser
parser = OptionParser(description="Random maze game")
parser.add_option('-W', '--width', type=int, default=43)
parser.add_option('-H', '--height', type=int, default=43)
parser.add_option('-s', '--size', type=int, default=11,
help="cell size")
args, _ = parser.parse_args()
for arg in ('width', 'height'):
if getattr(args, arg) % 2 == 0:
setattr(args, arg, getattr(args, arg) + 1)
print "Warning: %s must be odd, using %d instead" % (arg, getattr(args, arg))
sys.setrecursionlimit(5000)
app = Application(args.width, args.height, args.size)
app.master.title('Maze game')
app.mainloop()

Categories

Resources