Home > Blockchain >  An error while trying to change the list and show the result in python
An error while trying to change the list and show the result in python

Time:10-04

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.
  • Related