I was trying to make a XO Game using tkinter in python using that way and here is my code:
def button_function(name: tkinter.Button, n: str):
global row, column
info = name.grid_info()
row1 = info.get('row')
column1 = info.get('column')
name.grid_remove()
button = Button(root, text=n, font="decorative 9 bold", height=3, width=6, bg="white", state=DISABLED)
button.grid(row=row1, column=column1)
my_list = [cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8, cell9]
for x in my_list:
if x.grid_info():
info = x.grid_info()
row = info.get('row')
column = info.get('column')
x.grid_remove()
if n == "X":
my_list[my_list.index(x)] = Button(root, height=3, width=6, bg="gray",
command=lambda: button_function(x, "O"))
my_list[my_list.index(x)].grid(row=row, column=column)
elif n == "O":
my_list[my_list.index(x)] = Button(root, height=3, width=6, bg="gray",
command=lambda: button_function(x, "X"))
my_list[my_list.index(x)].grid(row=row, column=column)
else:
pass
info.clear()
def xo():
global cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8, cell9
cell1 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell1, "X"))
cell2 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell2, "X"))
cell3 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell3, "X"))
cell4 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell4, "X"))
cell5 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell5, "X"))
cell6 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell6, "X"))
cell7 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell7, "X"))
cell8 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell8, "X"))
cell9 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell9, "X"))
cell1.grid(row=2, column=1)
cell2.grid(row=2, column=3)
cell3.grid(row=2, column=5)
cell4.grid(row=4, column=1)
cell5.grid(row=4, column=3)
cell6.grid(row=4, column=5)
cell7.grid(row=6, column=1)
cell8.grid(row=6, column=3)
cell9.grid(row=6, column=5)
In the first function I am trying to change the contents of the list without change the name of the cells but I saw that it can't be done simply by adding a value to it and grid it because it will be a local variable doesn't belong to the list.
x = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(x, "O"))
So I tried this way above but I got this error
traceback (most recent call last):
File "C:\Users\HP\AppData\Local\Programs\Python\Python39\lib\tkinter\__init__.py", line 1892, in __call__
return self.func(*args)
File "D:\Python projects\python\Better XO.py", line 42, in <lambda>
cell8 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell8, "X"))
File "D:\Python projects\python\Better XO.py", line 23, in button_function
my_list[my_list.index(x)].grid(row=row, column=column)
ValueError: <tkinter.Button object .!button9> is not in list
so is there another way to do it correctly and thanks
CodePudding user response:
You got a ValueError
<tkinter.Button object .!button9> is not in list
Because the button was replaced when you did my_list[my_list.index(x)] = Button(
You can simplify the button_function
by passing the position of the button in the grid and only changing the button attributes using the configure
function.
Example:
def button_function(button, x, y):
global count
if button["text"]=="":
button.configure(font="decorative 9 bold", bg="white", state=DISABLED)
if count % 2 == 0:
button.configure(text= "X")
my_list[x][y] = "X"
else:
button.configure(text= "O")
my_list[x][y] = "O"
count =1
if count > 4 and is_winner(my_list[x][y]):
messagebox.showinfo("Winner",'Player "{}" win'.format(my_list[x][y]))
def xo():
cell1 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell1, 0, 0))
...
You can also add a function to check for winner after five clicks
Code
import tkinter
from tkinter import Button, DISABLED, messagebox
root = tkinter.Tk()
count = 0
my_list = [["", "", ""],
["", "", ""],
["", "", ""]]
def is_winner(mark):
if (my_list[0][0]==my_list[1][0]==my_list[2][0]==mark or
my_list[0][1]==my_list[1][1]==my_list[2][1]==mark or
my_list[0][2]==my_list[1][2]==my_list[2][2]==mark or
my_list[0][0]==my_list[0][1]==my_list[0][2]==mark or
my_list[1][0]==my_list[1][1]==my_list[1][2]==mark or
my_list[2][0]==my_list[2][1]==my_list[2][2]==mark or
my_list[0][0]==my_list[1][1]==my_list[2][2]==mark or
my_list[0][2]==my_list[1][1]==my_list[2][0]==mark):
return True
def button_function(button, x, y):
global count
if button["text"]=="":
button.configure(font="decorative 9 bold", bg="white", state=DISABLED)
if count % 2 == 0:
button.configure(text= "X")
my_list[x][y] = "X"
else:
button.configure(text= "O")
my_list[x][y] = "O"
count =1
if count > 4 and is_winner(my_list[x][y]):
messagebox.showinfo("Winner",'Player "{}" win'.format(my_list[x][y]))
def xo():
cell1 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell1, 0, 0))
cell2 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell2, 0, 1))
cell3 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell3, 0, 2))
cell4 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell4, 1, 0))
cell5 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell5, 1, 1))
cell6 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell6, 1, 2))
cell7 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell7, 2, 0))
cell8 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell8, 2, 1))
cell9 = Button(root, height=3, width=6, bg="gray", command=lambda: button_function(cell9, 2, 2))
cell1.grid(row=2, column=1)
cell2.grid(row=2, column=3)
cell3.grid(row=2, column=5)
cell4.grid(row=4, column=1)
cell5.grid(row=4, column=3)
cell6.grid(row=4, column=5)
cell7.grid(row=6, column=1)
cell8.grid(row=6, column=3)
cell9.grid(row=6, column=5)
xo()
CodePudding user response:
While Kenly already provided the reason for the error you received:
You got a
ValueError
<tkinter.Button object .!button9> is not in list
Because the button was replaced when you did
my_list[my_list.index(x)] = Button(
I think you can simplify the code a lot, for example: (I also added a check for a win state)
import tkinter
from tkinter import messagebox
root = tkinter.Tk()
even_turn = 0 # who's turn it is: 0: first player's turn | 1: second player's turn
# an efficient way to check if a player won the game:
# each counter has two lists, one for each player, that represent the amount of that player's symbol in a specific row/column/diagonal
# a player has won if one of their counters are 3. and we only need to check they updated counters on each turn
row_counter = [[0,0,0],[0,0,0]]
col_counter = [[0,0,0],[0,0,0]]
dia_counter = [[0,0],[0,0]]
# a table for the cells in the grid
cells = [[],[],[]]
def win(even):
tkinter.messagebox.showinfo('GAME', 'Player {} win!'.format('O' if even else 'X'))
root.destroy()
def get_move_func(y: int, x: int):
def func():
global cells, even_turn
# checking if cell is empty
if(cells[y][x].cget('text') == ''):
cells[y][x].config(text = ('O' if even_turn else 'X'))
# updating counter
row_counter[even_turn][y] = 1 # updating row counter
col_counter[even_turn][x] = 1 # updating column counter
if(x == y): # if on (left-up -> right-down) diagonal
dia_counter[even_turn][0] = 1
if(dia_counter[even_turn][0] == 3):
# won with diagonal
win(even_turn)
return
if(y == 2 - x): # if on (left-down -> right-up) diagonal
dia_counter[even_turn][1] = 1
if(dia_counter[even_turn][1] == 3):
# won with diagonal
win(even_turn)
return
if(row_counter[even_turn][y] == 3 or col_counter[even_turn][x] == 3):
# won with row / column
win(even_turn)
even_turn = 1 - even_turn
return func
def xo():
global cells
# some settings:
cell_height = 3
cell_width = 6
cell_bg = "gray"
curr_index = 0 # for insertion of cells into the "cells" table
# setting the grid:
for y in range(3):
for x in range(3):
cell = tkinter.Button(root, text='', font="decorative 9 bold", height=cell_height, width=cell_width, bg=cell_bg, command=get_move_func(y,x))
cell.grid(row=y,column=x)
cells[y].append(cell)
curr_index = 1
# calling the main function:
xo()
# starting the main loop:
root.mainloop()
This implementation fixes the following inaccuracies in the original code:
- Repeating code which looks bad and can cause bugs (for example, the cells declarations are just the same code repeated with slight variation. we have a loop for these kind of problems). repeating code also means that if you want to change it, you'll have to change every occurrence of it.
- Unnecessary deletion and insertion of objects. there's no reason to delete and insert Buttons if you are just going to change one thing about them. just edit them using .config()
- The turn handling: instead of changing all of the Buttons command each turn, you can keep track of the current turn and implement it in the command.