Hello I got a problem with using the Tkinter package on python. I want to create a window containing two major widgets of which one is a canvas that will later show a grid with cells. When initializing the canvas I use "create_rectangle" to fill the canvas with the desired objects. Also when clicking on a cell (for testing reasons) the canvas should change its color in the area of the rectangle. However when initialy displaying the window at first no objects can be seen (the expected result would be only white colored rectangles) and only when a Click on the canvas is performed the area changes its color as desired.
While looking through the internet I tried several variations of the order of the pack()- and create_rectangle()-methods. This is the code:
from tkinter import *
from tkinter.ttk import *
import cells
GRID_WIDTH = 15
GRID_HEIGHT = 15
class Ui(Frame):
""" Class to represent the ui of conways game of life"""
def __init__(self, parent, grid):
self.parent = parent
self.grid = grid
Frame.__init__(self, parent)
self.__setup()
def __setup(self):
""" Method to setup the components of the ui """
self.parent.title("Conway's game of life")
self.pack()
#Setup a frame to hold control components
frame_cntrl = Frame(self)
frame_cntrl.pack(side = RIGHT, anchor="n")
self.__setup_cntrl_components(frame_cntrl)
#Setup a frame to hold the Grid
self.canvas = Canvas(self)
self.canvas.pack(side = LEFT)
self.__draw_grid()
self.canvas.bind("<Button-1>", self.__canvas_clicked)
def __setup_cntrl_components(self, parent):
""" Method to setup the control elements of the ui"""
#Setup a label for the generation
self.lb_generation = Label(parent, text="dummy")
self.lb_generation.pack(side = TOP)
#Setup a button for iteration
self.bt_iteration = Button(parent, text="Iterate")
self.bt_iteration.pack(side = TOP)
def __draw_cell(self, x, y):
""" Draws a cell on the canvas"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
color = "black" if self.grid.cell_alive(x, y) else "white"
x0 = width * x / self.grid.width + 1
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height + 1
y1 = height * (y + 1) / self.grid.height
self.canvas.create_rectangle(x0, y0, x1, y1, width=0, fill=color)
def __draw_grid(self):
""" Method to setup the grid itself"""
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
for i in range(0, self.grid.width):
for j in range(0, self.grid.height):
self.__draw_cell(i, j)
def __canvas_clicked(self, event):
""" Method for when the cell was clicked """
x, y, width, height = event.x, event.y, self.canvas.winfo_width(), self.canvas.winfo_height()
cell_x = int(x / width * self.grid.width)
cell_y = int(y / height * self.grid.height)
self.grid.switch_cell(cell_x, cell_y)
self.__draw_cell(cell_x, cell_y)
if __name__ == "__main__":
Ui(Tk(), cells.Grid(GRID_WIDTH, GRID_HEIGHT)).mainloop()
Problem 1:
Your main problem is that, before the canvas is actually displayed, canvas.winfo_width() and canvas.winfo_height() do not return the canvas width and height, but the value 1.
I suggest you create the canvas as follows:
# Define canvas and save dimensions
self.canvas_width = 300
self.canvas_height = 200
self.canvas = Canvas(self, width = self.canvas_width,
height = self.canvas_height)
Then, in your code, replace each instance of:
width, height = self.canvas.winfo_width(), self.canvas.winfo_height()
with
width, height = self.canvas_width, self.canvas_height
Problem 2:
When creating each cell I don't think you need to add 1 to x0 and y0. Instead, it should be:
x0 = width * x / self.grid.width
x1 = width * (x + 1) / self.grid.width
y0 = height * y / self.grid.height
y1 = height * (y + 1) / self.grid.height
Related
Is it possible?
Just to place it anywhere onto the window and drag it anywhere I want it.
Here is an example of draggable item I am looking to achieve like from HTML (I know, it's got nothing to do with html): How TO - Create a Draggable HTML Element.
Here is an example of what I mean by a ruler. A ruler like this:
It's only for display purposes and not calculating anything..
I'll be using Grid manager in this case.
I'll be happy to see any examples!
Standard module tkinter and ttk doesn't have rulers and I don't know any external module for tkinter which has rulers.
Using Canvas I can create widget which draws lines with numbers.
But it is still primitive widget which doesn't resize, doesn't scroll lines and numbers, doesn't rescale, and doesn't show mouse position.
EDIT: Now rules show mouse position using red lines. But if there is no Canvas then they have to know offset - how far they are from left top corner of window.
import tkinter as tk
class VRuler(tk.Canvas):
'''Vertical Ruler'''
def __init__(self, master, width, height, offset=0):
super().__init__(master, width=width, height=height)
self.offset = offset
step = 10
# start at `step` to skip line for `0`
for y in range(step, height, step):
if y % 50 == 0:
# draw longer line with text
self.create_line(0, y, 13, y, width=2)
self.create_text(20, y, text=str(y), angle=90)
else:
self.create_line(2, y, 7, y)
self.position = self.create_line(0, 0, 50, 0, fill='red', width=2)
def set_mouse_position(self, y):
y -= self.offset
self.coords(self.position, 0, y, 50, y)
class HRuler(tk.Canvas):
'''Horizontal Ruler'''
def __init__(self, master, width, height, offset=0):
super().__init__(master, width=width, height=height)
self.offset = offset
step = 10
# start at `step` to skip line for `0`
for x in range(step, width, step):
if x % 50 == 0:
# draw longer line with text
self.create_line(x, 0, x, 13, width=2)
self.create_text(x, 20, text=str(x))
else:
self.create_line((x, 2), (x, 7))
self.position = self.create_line(0, 0, 0, 50, fill='red', width=2)
def set_mouse_position(self, x):
x -= self.offset
self.coords(self.position, x, 0, x, 50)
def motion(event):
x, y = event.x, event.y
hr.set_mouse_position(x)
vr.set_mouse_position(y)
def click(event):
print(event.x, event.y)
root = tk.Tk()
root['bg'] = 'black'
vr = VRuler(root, 25, 250)#, offset=25)
vr.place(x=0, y=28)
hr = HRuler(root, 250, 25)#, offset=25)
hr.place(x=28, y=0)
c = tk.Canvas(root, width=250, height=250)
c.place(x=28, y=28)
#root.bind('<Motion>', motion) # it needs offset=28 if there is no Canvas
#root.bind('<Button-1>', click)
c.bind('<Motion>', motion)
c.bind('<Button-1>', click)
root.mainloop()
Here was the code to draw a line. Is it possible to let the user drag a line so that it forms a curve?
from tkinter import Canvas, Tk
# Image dimensions
w,h = 640,480
# Create canvas
root = Tk()
canvas = Canvas(root, width = w, height = h, bg = 'white')
canvas.pack()
def on_click(event):
""" set starting point of the line """
global x1, y1
x1 = event.x
y1 = event.y
def on_click_release(event):
""" draw the line """
canvas.create_line(x1, y1, event.x, event.y)
def clear_canvas(event):
canvas.delete('all')
canvas.bind("<Button-1>", on_click)
canvas.bind("<ButtonRelease-1>", on_click_release)
root.bind("<Key-c>", clear_canvas)
root.mainloop()
Once again thank youuuuu!!! :)))))
For an arc you need to track the mouse as it moves across the screen, rather than just the start and end points.
The code below will only create arcs from the bottom left, to the bottom right of a rectangle, but you can add any other arcs you want by changing the start angle and the extent angle of the arc.
from tkinter import Canvas, Tk, ARC
# Image dimensions
w,h = 640,480
# Create canvas
root = Tk()
canvas = Canvas(root, width = w, height = h, bg = 'white')
canvas.pack()
# curve points
global points
global temp_arc
points = []
temp_arc = None
def arc():
x = [point[0] for point in points]
y = [point[1] for point in points]
return canvas.create_arc(x[0], y[0], x[-1], y[-1], start = 0, style = ARC, width = 2, extent = 180)
def motion(event):
global temp_arc
points.append([event.x, event.y])
if temp_arc != None:
canvas.delete(temp_arc)
temp_arc = arc()
def on_click_release(event):
arc()
global points
points = []
def clear_canvas(event):
canvas.delete('all')
canvas.bind("<B1-Motion>", motion)
canvas.bind("<ButtonRelease-1>", on_click_release)
root.bind("<Key-c>", clear_canvas)
root.mainloop()
I do not know if my question is a stupid one or a tricky one.
So, using Tkinter and Canvas, I succeed to implement a scrolling/zooming function that work perfectly, thanks to this post Move and zoom a tkinter canvas with mouse. I also add a binding to resize the canvas size when the window size change without trouble.
Using coords and after, I have no trouble to move object around.
The trouble came when I tried to combine everything.
Moving and scrolling : no trouble
Scrolling and Zooming : ok
Zooming, moving and scrolling : do not work
The code bellow reproduce the trouble (python 2.7, work on windows). For what I can see, the trouble come from the scaling, maybe caused by the change of coords of the objects, that induce the canvas resizing, and then disable the scaling? If it is the case, I need help to solve this issue. If it is not the case, I need help to found the issue...
By removing/disable the line self.master.after(50, self.Display), moving do no occur anymore.
import Tkinter as tk
import math
class Example:
def __init__ (self, master):
self.master = master
self.interval = 0
self.SizeX, self.SizeY = master.winfo_width(), master.winfo_height()
#Canvas Frame
self.SystemCanvasFrame = tk.Frame(master, bg='black')
self.SystemCanvasFrame.grid(row=0, column=0)
#Canvas
self.SystemCanvas = tk.Canvas(self.SystemCanvasFrame, width=int(self.SizeX*0.75)-20, height=self.SizeY-20, bg="black")
self.SystemCanvas.focus_set()
self.xsb = tk.Scrollbar(self.SystemCanvasFrame, orient="horizontal", command=self.SystemCanvas.xview)
self.ysb = tk.Scrollbar(self.SystemCanvasFrame, orient="vertical", command=self.SystemCanvas.yview)
self.SystemCanvas.configure(scrollregion=(-500,-500,500,500))
self.SystemCanvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
#add the canvas with scroll bar in grid format
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.SystemCanvas.grid(row=0, column=0, sticky="nsew")
# This is what enables using the mouse to slide the window:
self.SystemCanvas.bind("<ButtonPress-1>", self.move_start)
self.SystemCanvas.bind("<B1-Motion>", self.move_move)
#windows scroll
self.SystemCanvas.bind("<MouseWheel>",self.zoomer)
#resize the main window
self.master.bind('<Configure>', self.UpdateCanvasSize)
#Create Objects
self.Size = 5 #object Size
x0 = 0
y0 = 0
x1 = self.Size
y1 = self.Size
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='green', outline='green', width=3, tags='Green')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='red', outline='red', width=3, tags='Red')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='yellow', outline='yellow', width=1, tags='Yellow')
self.Display()
def Display(self):
self.interval += 0.5 #speed parameter
GreenPos = self.UpdatePosition(0.1*self.interval, (0,0), 50)
RedPos = self.UpdatePosition(0.02*self.interval+180, (0,0), 200)
YellowPos = self.UpdatePosition(0.3*self.interval, RedPos, 10)
self.MoveObject('Green', GreenPos)
self.MoveObject('Red', RedPos)
self.MoveObject('Yellow', YellowPos)
self.master.after(50, self.Display) #Disable to zoom
def MoveObject (self, Obj, pos): #only move object that are in the field of view
"""Move Obj to the given position (tuple - xy)"""
ID = self.SystemCanvas.find_withtag(Obj)
#Convert the Center of the object to the coo need for tk
x0 = pos[0] - self.Size/2.0 #radius of the circle
y0 = pos[1] - self.Size/2.0
x1 = pos[0] + self.Size/2.0
y1 = pos[1] + self.Size/2.0
self.SystemCanvas.coords(ID, x0,y0,x1,y1)
def UpdatePosition(self, angle, center, distance):
"""Calculate next object position around the Center at the Distance and speed determine by Angle (in Radian) - Center of the object"""
h = center[0]
k = center[1]
radius = distance
Rad = angle
x = h+radius*math.cos(Rad)
y = k+radius*math.sin(Rad)
return (x, y)
def UpdateCanvasSize(self, event):
"""Permit to resize the canvas to the window"""
self.SizeX, self.SizeY = self.master.winfo_width(), self.master.winfo_height()
self.SystemCanvas.config(width=int(self.SizeX*0.75)-20, height=self.SizeY-20)
def move_start(self, event):
"""Detect the beginning of the move"""
self.SystemCanvas.scan_mark(event.x, event.y)
self.SystemCanvas.focus_set() #security, set the focus on the Canvas
def move_move(self, event):
"""Detect the move of the mouse"""
self.SystemCanvas.scan_dragto(event.x, event.y, gain=1)
def zoomer(self,event):
"""Detect the zoom action by the mouse. Zoom on the mouse focus"""
true_x = self.SystemCanvas.canvasx(event.x)
true_y = self.SystemCanvas.canvasy(event.y)
if (event.delta > 0):
self.SystemCanvas.scale("all", true_x, true_y, 1.2, 1.2)
elif (event.delta < 0):
self.SystemCanvas.scale("all", true_x, true_y, 0.8, 0.8)
self.SystemCanvas.configure(scrollregion = self.SystemCanvas.bbox("all"))
if __name__ == '__main__':
root = tk.Tk()
root.geometry('1125x750')
app = Example(root)
root.mainloop()
I'm new to Tkinter so this might not be the most elegant solution but I hope it gives you an idea on how to solve the problem.
The zoomer method scales your coordinates but these coordinates are reset anytime you call MoveObject or UpdatePosition. I added code that keeps track of the scale factor, self.scale, and a method update_coord that scales a given coordinate based on the scale factor. Finally, I called update_coord in the MoveObject and UpdatePosition methods.
Here is the working code;
import Tkinter as tk
import math
class Example:
def __init__ (self, master):
self.scale = 1 #Added
self.master = master
self.interval = 0
self.SizeX, self.SizeY = master.winfo_width(), master.winfo_height()
#Canvas Frame
self.SystemCanvasFrame = tk.Frame(master, bg='black')
self.SystemCanvasFrame.grid(row=0, column=0)
#Canvas
self.SystemCanvas = tk.Canvas(self.SystemCanvasFrame, width=int(self.SizeX*0.75)-20, height=self.SizeY-20, bg="black")
self.SystemCanvas.focus_set()
self.xsb = tk.Scrollbar(self.SystemCanvasFrame, orient="horizontal", command=self.SystemCanvas.xview)
self.ysb = tk.Scrollbar(self.SystemCanvasFrame, orient="vertical", command=self.SystemCanvas.yview)
self.SystemCanvas.configure(scrollregion=(-500,-500,500,500))
self.SystemCanvas.configure(yscrollcommand=self.ysb.set, xscrollcommand=self.xsb.set)
#add the canvas with scroll bar in grid format
self.xsb.grid(row=1, column=0, sticky="ew")
self.ysb.grid(row=0, column=1, sticky="ns")
self.SystemCanvas.grid(row=0, column=0, sticky="nsew")
# This is what enables using the mouse to slide the window:
self.SystemCanvas.bind("<ButtonPress-1>", self.move_start)
self.SystemCanvas.bind("<B1-Motion>", self.move_move)
#windows scroll
self.SystemCanvas.bind("<MouseWheel>",self.zoomer)
#resize the main window
self.master.bind('<Configure>', self.UpdateCanvasSize)
#Create Objects
self.Size = 5 #object Size
x0 = 0
y0 = 0
x1 = self.Size
y1 = self.Size
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='green', outline='green', width=3, tags='Green')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='red', outline='red', width=3, tags='Red')
self.SystemCanvas.create_oval(x0,y0,x1,y1, fill='yellow', outline='yellow', width=1, tags='Yellow')
self.Display()
#**Added Method
def update_coord(self, coord):
"""Calculate the scaled cordinate for a given cordinate based on the zoomer scale factor"""
new_coord = [coord_i * self.scale for coord_i in coord]
return new_coord
def Display(self):
self.interval += 0.5 #speed parameter
GreenPos = self.UpdatePosition(0.1*self.interval, (0,0), 50)
RedPos = self.UpdatePosition(0.02*self.interval+180, (0,0), 200)
YellowPos = self.UpdatePosition(0.3*self.interval, RedPos, 10)
self.MoveObject('Green', GreenPos)
self.MoveObject('Red', RedPos)
self.MoveObject('Yellow', YellowPos)
self.master.after(1, self.Display) #Disable to zoom
def MoveObject (self, Obj, pos): #only move object that are in the field of view
"""Move Obj to the given position (tuple - xy)"""
ID = self.SystemCanvas.find_withtag(Obj)
#Convert the Center of the object to the coo need for tk
x0 = pos[0] - self.Size/2.0 #radius of the circle
y0 = pos[1] - self.Size/2.0
x1 = pos[0] + self.Size/2.0
y1 = pos[1] + self.Size/2.0
c_0 = self.update_coord([x0, y0]) #Added
c_1 = self.update_coord([x1, y1]) #Added
self.SystemCanvas.coords(ID, c_0[0], c_0[1], c_1[0], c_1[1]) #Added/Edited
def UpdatePosition(self, angle, center, distance):
"""Calculate next object position around the Center at the Distance and speed determine by Angle (in Radian) - Center of the object"""
h = center[0]
k = center[1]
radius = distance
Rad = angle
x = h+radius*math.cos(Rad)
y = k+radius*math.sin(Rad)
return self.update_coord([x, y]) #Added/Edited
def UpdateCanvasSize(self, event):
"""Permit to resize the canvas to the window"""
self.SizeX, self.SizeY = self.master.winfo_width(), self.master.winfo_height()
self.SystemCanvas.config(width=int(self.SizeX*0.75)-20, height=self.SizeY-20)
def move_start(self, event):
"""Detect the beginning of the move"""
self.SystemCanvas.scan_mark(event.x, event.y)
self.SystemCanvas.focus_set() #security, set the focus on the Canvas
def move_move(self, event):
"""Detect the move of the mouse"""
self.SystemCanvas.scan_dragto(event.x, event.y, gain=1)
def zoomer(self,event):
"""Detect the zoom action by the mouse. Zoom on the mouse focus"""
true_x = self.SystemCanvas.canvasx(event.x)
true_y = self.SystemCanvas.canvasy(event.y)
if (event.delta > 0):
self.SystemCanvas.scale("all", true_x, true_y, 1.2, 1.2)
self.scale *= 1.2 #**Added
elif (event.delta < 0):
self.SystemCanvas.scale("all", true_x, true_y, 0.8, 0.8)
self.scale *= 0.8 #**Added
#self.SystemCanvas.configure(scrollregion = self.SystemCanvas.bbox("all")) #**Removed (This disables scrollbar after zoom)
if __name__ == '__main__':
root = tk.Tk()
root.geometry('1125x750')
app = Example(root)
root.mainloop()
I am trying to simulate a grid like what would be used for a game board using tkinter rectangles being drawn onto a canvas, however I am having trouble making a loop that does so correctly.
Pretty much I have a variable that contains a certain amount of rows for a grid and a variable for the amount of columns, and I need to create a grid of rectangles based off of those configurations which can change anytime the application is run, which is making it a bit tricky for me.
Anyways, I currently have a function to draw a rectangle to the screen like so:
def _place_empty_tiles(self):
self._canvas.update()
width = self._canvas.winfo_width()
height = self._canvas.winfo_height()
self.x = width / self._game.columns
self.y = height / self._game.rows
for i in range(self._game.columns):
click_point = point.from_pixel(self.x, self.y, width, height)
self._state.handle_click(click_point)
self.x += 60
def _redraw_game_pieces(self)->None:
'''Delete and redraw all the of game pieces'''
self._canvas.delete(tkinter.ALL)
canvas_width = self._canvas.winfo_width()
canvas_height = self._canvas.winfo_height()
for tile in self._state.all_tiles():
center_x, center_y = tile.center().pixel(canvas_width, canvas_height)
radius_x = tile.radius_frac() * canvas_width
radius_y = tile.radius_frac() * canvas_height
self._canvas.create_rectangle(
center_x - radius_x, center_y - radius_y,
center_x + radius_x, center_y + radius_y,
fill = '#006000', outline = '#000000')
You may noticed I have some custom coordinate conversion methods going on to compensate for the screen re sizing. However, my problem is under the function _place_empty_tiles(self) in the for loop. Let's say the amount of columns is 4, it will currently print out on the canvas the following:
However,
it is actually making a row, and
that is only one row.
How can I make it so I can create a grid with a similar style given the amount of rows and columns are stored in self._game.columns and self._game.rows?
UPDATE:
Tried basing it off The tkinter Knight's Tour Demo I rewrote the function like so:
def _place_empty_tiles(self):
self._canvas.update()
width = self._canvas.winfo_width()
height = self._canvas.winfo_height()
for r in range(self._game.rows -1,-1,-1):
for c in range(self._game.columns):
click_point = point.from_pixel(c*30+4, r*30+4, c*30+30, r*30+30)
self._state.handle_click(click_point)
self._redraw_game_pieces()
which is close, but still getting some unexpected results:
The Tk demos include a Knight's Tour demo that draws a chessboard. Someone converted it for tkinter and you can examine the _draw_board function to see an example of how to create such a layout using canvas rectangles:
def draw_board(canvas):
# draw checkerboard
for r in range(7, -1, -1):
for c in range(8):
if c&1 ^ r&1:
fill = 'tan3'
dfill = 'tan4'
else:
fill = 'bisque'
dfill= 'bisque3'
coords = (c*30+4, r*30+4, c*30+30, r*30+30)
canvas.create_rectangle(coords, fill=fill, disabledfill=dfill,
width=2, state='disabled')
Here's an example that draws a grid that fits the window as closely as possible. It is designed to redraw itself whenever the window resizes. If you don't want that behavior, you can hard-code the cell width and height.
The cells are stored in a dictionary indexed by row and column, to make it easy to reference each tile. Clicking on a tile will toggle it between blue and red. Note that when the window resizes it won't remember which cells were previously clicked on. That's easy to fix if you want.
import Tkinter as tk
class App(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.canvas = tk.Canvas(self, width=500, height=500, borderwidth=0, highlightthickness=0)
self.canvas.pack(side="top", fill="both", expand="true")
self.rows = 20
self.columns = 20
self.tiles = {}
self.canvas.bind("<Configure>", self.redraw)
self.status = tk.Label(self, anchor="w")
self.status.pack(side="bottom", fill="x")
def redraw(self, event=None):
self.canvas.delete("rect")
cellwidth = int(self.canvas.winfo_width()/self.columns)
cellheight = int(self.canvas.winfo_height()/self.columns)
for column in range(self.columns):
for row in range(self.rows):
x1 = column*cellwidth
y1 = row * cellheight
x2 = x1 + cellwidth
y2 = y1 + cellheight
tile = self.canvas.create_rectangle(x1,y1,x2,y2, fill="blue", tags="rect")
self.tiles[row,column] = tile
self.canvas.tag_bind(tile, "<1>", lambda event, row=row, column=column: self.clicked(row, column))
def clicked(self, row, column):
tile = self.tiles[row,column]
tile_color = self.canvas.itemcget(tile, "fill")
new_color = "blue" if tile_color == "red" else "red"
self.canvas.itemconfigure(tile, fill=new_color)
self.status.configure(text="you clicked on %s/%s" % (row, column))
if __name__ == "__main__":
app = App()
app.mainloop()
I am thinking of creating a board game in Python, one which will have a grid of spaces, each with different properties, and which may or may not have pieces resting on them. These pieces should be able to move between spaces, though subject to various rules. (Chess or checkers would be good examples of what I'm thinking of, though my game would have different/more complicated rules, and the grid may not be square, even if the spaces are).
I wrote a Java implementation of something similar for a data structures class, using a modified version of linked lists. But this is Python, so I imagine there's a better way to do it (maybe even a library out there?)
Drawing a chessboard is pretty trivial with Tkinter. Here's a really simple example:
import Tkinter as tk
class GameBoard(tk.Frame):
def __init__(self, parent, rows=8, columns=8, size=32, color1="white", color2="blue"):
'''size is the size of a square, in pixels'''
self.rows = rows
self.columns = columns
self.size = size
self.color1 = color1
self.color2 = color2
self.pieces = {}
canvas_width = columns * size
canvas_height = rows * size
tk.Frame.__init__(self, parent)
self.canvas = tk.Canvas(self, borderwidth=0, highlightthickness=0,
width=canvas_width, height=canvas_height, background="bisque")
self.canvas.pack(side="top", fill="both", expand=True, padx=2, pady=2)
# this binding will cause a refresh if the user interactively
# changes the window size
self.canvas.bind("<Configure>", self.refresh)
def addpiece(self, name, image, row=0, column=0):
'''Add a piece to the playing board'''
self.canvas.create_image(0,0, image=image, tags=(name, "piece"), anchor="c")
self.placepiece(name, row, column)
def placepiece(self, name, row, column):
'''Place a piece at the given row/column'''
self.pieces[name] = (row, column)
x0 = (column * self.size) + int(self.size/2)
y0 = (row * self.size) + int(self.size/2)
self.canvas.coords(name, x0, y0)
def refresh(self, event):
'''Redraw the board, possibly in response to window being resized'''
xsize = int((event.width-1) / self.columns)
ysize = int((event.height-1) / self.rows)
self.size = min(xsize, ysize)
self.canvas.delete("square")
color = self.color2
for row in range(self.rows):
color = self.color1 if color == self.color2 else self.color2
for col in range(self.columns):
x1 = (col * self.size)
y1 = (row * self.size)
x2 = x1 + self.size
y2 = y1 + self.size
self.canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill=color, tags="square")
color = self.color1 if color == self.color2 else self.color2
for name in self.pieces:
self.placepiece(name, self.pieces[name][0], self.pieces[name][1])
self.canvas.tag_raise("piece")
self.canvas.tag_lower("square")
# image comes from the silk icon set which is under a Creative Commons
# license. For more information see http://www.famfamfam.com/lab/icons/silk/
imagedata = '''
R0lGODlhEAAQAOeSAKx7Fqx8F61/G62CILCJKriIHM+HALKNMNCIANKKANOMALuRK7WOVLWPV9eR
ANiSANuXAN2ZAN6aAN+bAOCcAOKeANCjKOShANKnK+imAOyrAN6qSNaxPfCwAOKyJOKyJvKyANW0
R/S1APW2APW3APa4APe5APm7APm8APq8AO28Ke29LO2/LO2/L+7BM+7BNO6+Re7CMu7BOe7DNPHA
P+/FOO/FO+jGS+/FQO/GO/DHPOjBdfDIPPDJQPDISPDKQPDKRPDIUPHLQ/HLRerMV/HMR/LNSOvH
fvLOS/rNP/LPTvLOVe/LdfPRUfPRU/PSU/LPaPPTVPPUVfTUVvLPe/LScPTWWfTXW/TXXPTXX/XY
Xu/SkvXZYPfVdfXaY/TYcfXaZPXaZvbWfvTYe/XbbvHWl/bdaPbeavvadffea/bebvffbfbdfPvb
e/fgb/Pam/fgcvfgePTbnfbcl/bfivfjdvfjePbemfjelPXeoPjkePbfmvffnvbfofjlgffjkvfh
nvjio/nnhvfjovjmlvzlmvrmpvrrmfzpp/zqq/vqr/zssvvvp/vvqfvvuPvvuvvwvfzzwP//////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////yH+FUNyZWF0ZWQgd2l0aCBU
aGUgR0lNUAAh+QQBCgD/ACwAAAAAEAAQAAAIzAD/CRxIsKDBfydMlBhxcGAKNIkgPTLUpcPBJIUa
+VEThswfPDQKokB0yE4aMFiiOPnCJ8PAE20Y6VnTQMsUBkWAjKFyQaCJRYLcmOFipYmRHzV89Kkg
kESkOme8XHmCREiOGC/2TBAowhGcAyGkKBnCwwKAFnciCAShKA4RAhyK9MAQwIMMOQ8EdhBDKMuN
BQMEFPigAsoRBQM1BGLjRIiOGSxWBCmToCCMOXSW2HCBo8qWDQcvMMkzCNCbHQga/qMgAYIDBQZU
yxYYEAA7
'''
if __name__ == "__main__":
root = tk.Tk()
board = GameBoard(root)
board.pack(side="top", fill="both", expand="true", padx=4, pady=4)
player1 = tk.PhotoImage(data=imagedata)
board.addpiece("player1", player1, 0,0)
root.mainloop()
You can consider your underlying board implementation as different kind of datastructures.
List of lists - l1 = [[1,2,3],[4,5,6],[7,8,9]]
Dict with values as list = d1 = {a:[1,2,3],b:[4,5,6],c:[7,8,9]}
Dict with keys are coordinates and values which you can assign.
As a Graph
Here is a design of an empty chessboard.
>>> chessboard = {}
>>> for row in range(8):
... for col in range(8):
... chessboard[(row,col)] = 0
...
>>>
You can use any of these and design the logic of your game. For higher level elements you can tie these objects or elements within to spites of pygame