I am trying to build a simple Tic Tac Toe game using python, almost there now except the last visual affect:
I need to be able to modify any of 9 squares showing on the canvas when I click it . Have searched a bunch of example on stack... well...none worked I use a dictionary to map the cursor:
self.map = {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 3, (1, 1): 4, (1, 2): 5, (2, 0): 6, (2, 1): 7,(2, 2): 8}
First square on top left is 0 looks like this won't work: id= self.canvas.create_rectangle() does not work on this. self.canvas.itemconfig(id,fill='bule')
I am seeking a effective way to access the fill or the clicked square not the rest. Thanks
All my codes are attached here:
import tkinter
import random
class Game(object):
"""
Enter the class docstring here
"""
block_size = 100
def __init__(self, parent):
parent.title('Tic Tac Toe')
self.parent = parent
self.initialize_game()
def initialize_game(self):
# These are the initializations that need to happen
# at the beginning and after restarts
self.board = [None, None, None, None, None, None, None, None, None] # game board as a instance variable
self.map = {(0, 0): 0, (0, 1): 1, (0, 2): 2, (1, 0): 3, (1, 1): 4, (1, 2): 5, (2, 0): 6, (2, 1): 7,
(2, 2): 8} # map to self.board
self.top_frame = tkinter.Frame(self.parent)
self.top_frame.pack(side=tkinter.TOP)
# add restart button on top frame
restart_button = tkinter.Button(self.top_frame, text='Restart', width=20,
command=self.restart)
restart_button.pack() # register restart_button with geometry manager
# create bottom frame for group the label below
self.bottom_frame=tkinter.Frame(self.parent)
self.bottom_frame.pack(side=tkinter.BOTTOM)
# create label for displaying game result text
self.my_lbl=tkinter.Label(self.bottom_frame,text=None)
self.my_lbl.pack()
# create a canvas to draw our board on the top frame
self.canvas = tkinter.Canvas(self.top_frame,
width=self.block_size * 3,
height=self.block_size * 3)
# draw 3x3 visible blocks on the canvas
for ro in range(3):
for col in range(3):
tag=self.canvas.create_rectangle(self.block_size * col,
self.block_size * ro,
self.block_size * (col + 1),
self.block_size * (ro + 1))
# bind entire canvas with left click handler (play function)
self.canvas.bind("<Button-1>", self.play)
self.canvas.pack() # register canvas with a geometry manager
def board_full(self):
if None not in self.board:
return True # true for full
else:
return False # false for not full
def possible_moves(self):
"""return: list of possible moves"""
possible_moves = [] # list for possible moves
for i in range(0, 9):
if self.board[i] is None: # if cell un-taken
possible_moves.append(i) # append the cell number to list
else:
pass # cell taken, don't append
return possible_moves # return list of possible moves
def pc_move(self):
m = True
while m:
pc_move = random.randint(0, 8) # random generate a number from 0 to 8
if pc_move in self.possible_moves(): # if the number is a possible move
self.board[pc_move] = 'O' # do it
m = False # exit loop
else: # not a possible movie
continue # re-do
return self
def draw_out(self):
"""to be deleted"""
print(self.board[0:3])
print(self.board[3:6])
print(self.board[6:9])
def play(self, event): # This method is invoked when the user clicks on a square.
"""
when the player clicks on a un-taken square, this method first translate cursor into cell number,
then update game board and check game result based on condition
:parameter: click
:return: updated game object
"""
print('clicked', event.y, event.x) # to be deleted
# after the click: part 1 human play first
my_move = self.map[(event.y // self.block_size, event.x // self.block_size)] # map cursor
if self.board[my_move] is None: # check if cell is empty
self.board[my_move] = 'X' # if cell empty mark X for my play, PC use O
#self.canvas.itemconfigure(fill='blue')
# self.draw_out() # delete this line later
else: # if the cell taken, do nothing until click on empty square
return None
#check game result and board full:
if (self.board_full()is True) or(
self.check_game()is not None):
self.canvas.unbind("<Button-1>") # when win, lost, tie occur,disable handler
print(self.check_game()) # DEBUGGING DELETE
else:
pass
# part 2: while not filled, PC make one move right after my move:
self.possible_moves() # check possible moves for PC
self.pc_move() # pc make move
self.draw_out() # DELETE LATER
# part3: check game result and board full
if (self.board_full()is True) or(
self.check_game()is not None):
self.canvas.unbind("<Button-1>") # when win, lost, tie occur,disable handler
print(self.check_game()) # DEBUGGING DELETE
else:
pass
return self # when board is filled, return
def check_game(self):
"""
Check if the game is won or lost or a tie
Return: win, lose, tie, none
"""
result=None
if (self.board[0] == self.board[1] == self.board[2] == 'X') or (
self.board[3] == self.board[4] == self.board[5] == 'X') or (
self.board[6] == self.board[7] == self.board[8] == 'X') or (
self.board[0] == self.board[3] == self.board[6] == 'X') or (
self.board[1] == self.board[4] == self.board[7] == 'X') or (
self.board[2] == self.board[5] == self.board[8] == 'X') or (
self.board[0] == self.board[4] == self.board[8] == 'X') or (
self.board[2] == self.board[4] == self.board[6] == 'X'):
result = 'You win!' # player win
self.my_lbl.config(text=result)
elif (self.board[0] == self.board[1] == self.board[2] == 'O') or (
self.board[3] == self.board[4] == self.board[5] == 'O') or (
self.board[6] == self.board[7] == self.board[8] == 'O') or (
self.board[0] == self.board[3] == self.board[6] == 'O') or (
self.board[1] == self.board[4] == self.board[7] == 'O') or (
self.board[2] == self.board[5] == self.board[8] == 'O') or (
self.board[0] == self.board[4] == self.board[8] == 'O') or (
self.board[2] == self.board[4] == self.board[6] == 'O'):
result = 'You lost!' # player lose
self.my_lbl.config(text=result)
else:
if self.board_full()is True:
result = "It's a tie!" # tie
self.my_lbl.config(text=result)
else:
pass
return result
def restart(self):
""" Reinitialize the game and board after restart button is pressed """
self.top_frame.destroy()
self.bottom_frame.destroy()
self.initialize_game()
def main():
root = tkinter.Tk() # Instantiate a root window
my_game = Game(root) # Instantiate a Game object
root.mainloop() # Enter the main event loop
if __name__ == '__main__':
main()
There are at least two ways to solve this problem.
First, you can use find_closest
to find the closest object to the cursor. There are two things to keep in mind with this solution. First, you must give it canvas coordinates, not window coordinates (which is what the event object has). Second, you need to either give the rectangles a fill color, or make sure they don't overlap. find_closest
looks for colored pixels, so if there's no fill color it will search until it finds a border. If the borders overlap (which yours do), it may find the wrong square (ie: the left edge of the right-most square overlaps the right edge of the middle square, and due to stacking order it will be considered closest).
So, give your rectangles a fill color (eg: self.canvas.create_rectangle(..., fill='white')
), and then you can find the square under the cursor like this:
def play(self, event):
...
cx = self.canvas.canvasx(event.x)
cy = self.canvas.canvasy(event.y)
cid = self.canvas.find_closest(cx,cy)[0]
self.canvas.itemconfigure(cid, fill="blue")
...
The other thing you can do is to bind to each square individually with tag_bind
, and have the binding pass the number to your function. Again, the rectangle needs a fill color, though for a different reason. The clicks only register on non-transparent regions, so without a fill color the only time it will register a click is when you click on the border:
def initialize_game(self):
...
tag=self.canvas.create_rectangle(self.block_size * col,
self.block_size * ro,
self.block_size * (col + 1),
self.block_size * (ro + 1), fill="white")
self.canvas.tag_bind(tag, "<1>", lambda event, tag=tag: self.play(event, tag))
...
...
def play(self, event, tag):
...
self.canvas.itemconfigure(tag, fill="blue")
...
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.