Python Forum
Tic-Tac-Toe - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: Homework (https://python-forum.io/forum-9.html)
+--- Thread: Tic-Tac-Toe (/thread-11590.html)



Tic-Tac-Toe - juliabrushett - Jul-17-2018

Hello!

I am trying to make a Tic-Tac-Toe game, and I am having trouble with the 'X' and 'O' characters showing up in my program. My code is below:

from tkinter import *

grid = [[0 for i in range(3)] for j in range(3)]
numClicks = 0
isDone = False
isXTurn = True

def restart() :
    print("Restart Game")

def resetGame() :
    global isXTurn
    global isDone
    global numClicks
    for i in range(3) :
        for j in range(3) :
            grid[rw][col].config(text = " ")

def markSpace(rw, col) :
    global isXTurn
    global isDone
    global numClicks

    numClicks = 0
    isXTurn = True
    isDone = False

    space = grid[rw][col].cget("text")
    if(isDone == True) :
        return
    elif (space == " ") :
        if (space == isXTurn) :
            grid[rw][col].config(text = "X", fg = "red")
            lblStatus.config(text = "O\'s Turn")
        else :
            grid[rw][col].config(text = "O", fg = "blue")
            lblStatus.config(text = "X\'s Turn")
    else :
        lblStatus.config(text = "Invalid Move")
        return

def gameOver(rw, col) :
    global numClicks
    global isDone
    winner = ' '

    if(grid[0][0].cget('text')  == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[2][2].cget('text')) :
        winner = grid[0][0].cget('text')
    elif(grid[2][0].cget('text')  == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[0][2].cget('text')) :
        winner = grid[2][1].cget('text')

    else :
        for r in range(0, 3) :
            if(grid[r][0].cget('text') != ' ' and grid[r][0].cget('text') == grid[r][1].cget('text') and grid[r][1].cget('text') == grid[r][2].cget('text')):
                winner = grid[r][0].cget('text')
            elif(grid[0][r].cget('text') != ' ' and grid[0][r].cget('text') == grid[1][r].cget('text') and grid[1][r].cget('text') == grid[2][r].cget('text')):
                winner = grid[0][r].cget('text')

    isDone = True
    if(winner == ' ' and numClicks >= 9) :
       lblStatus.config(text = 'Tie Game')
    elif(winner != ' ') :
        lblStatus.config(text = winner + ' Wins!')
    else :
        lblStatus.config(text = 'X\'s Turn' if isXTurn else 'O\'s Turn')
        isDone = False

main = Tk()
main.title("Tic-Tac-Toe")
main.geometry("355x420")

topFrame = Frame(main, width = 320, height = 40)
topFrame.place(x = 12, y = 12)

lablStatus = Label(topFrame, text = "O's Turn")
lablStatus.config(fg = "blue", bg = "yellow", font = "serif 16 bold")
lablStatus.pack(side = TOP)

mainFrame = Frame(main, width = 330, height = 330)
mainFrame.config(bg = "black")
mainFrame.place(x = 10, y = 42)

for rw in range(0, 3) :
    for col in range(0, 3) :
        grid[rw][col] =  Button(mainFrame, text=" ", relief = 'solid' )
        grid[rw][col].config(font = "monospace 36 bold", fg = 'red', height = 1,  width = 3)
        grid[rw][col].place(x = rw*105 + 10, y=col*105+10)

btnRestart = Button(main, text = "Restart", command = resetGame, width = 30)
btnRestart.place(x = 45, y = 380)


main.mainloop()

small change in code: lines 24-26 are now

 numClicks += 1
 isXTurn = not isXTurn
 gameOver(rw, col)



RE: Tic-Tac-Toe - ichabod801 - Jul-17-2018

You are way too heavy on global variables. Since you are using tkinter to do this as a GUI, you should switch to an object oriented programming paradigm (see the classes tutorial in my signature if you don't know what I'm talking about). Everything that is a global variable should either be an object attribute, or should be passed as a parameter and/or returned from a method.

In terms of your current problem, lines 31 & 32 look like they would be a problem. From line 31 we know that space is " ". But isXTurn appears to be a boolean variable. So when is " " == True and when is " " == False?


RE: Tic-Tac-Toe - anickone - Jul-17-2018

Hello!
I wrote this game too.
My code:
# python3

# Игра "Крестики - Нолики"

from tkinter import *
import random

class Square():

    """ задает квадраты(клетки) """

    def __init__(self, master, xi, yi, num, b=5, w=137, h=137, padx=0, pady=160):
        """ b - border """
        self.master = master
        self.square_num = num
        self.msg = StringVar()
        x = padx + xi * (w + b)
        y = pady + yi * (h + b)
        self.lbl = Label(self.master, textvariable=self.msg, font="FreeSerifBold 75", bg="white")
        self.lbl.place(x=x, y=y, width=w, height=h, anchor=NW)

    def unbind(self):
        self.lbl.unbind("<Button-1>")

    def bind(self):
        self.lbl.bind("<Button-1>", self.choice)

    def set_msg(self, txt):
        self.msg.set(txt)

    def choice(self, event):
        self.master.turn_user(self.square_num)

    def cfg(self, color):
        self.lbl.configure(bg=color)

class Application(Frame):
    def __init__(self, master):
        super(Application, self).__init__(master, width=421, height=582)  
        self.pack()

        # константы
        self.wins = ((0, 1, 2), (3, 4, 5), (6, 7, 8), (0, 3, 6),
                     (1, 4, 7), (2, 5, 8), (0, 4, 8), (2, 4, 6))
        self.board_start = ['_'] * 9
        self.free_squares_start = [x for x in range(9)]
        self.choice_pc1 = [0, 2, 6, 8]
        self.choice_pc2 = [1, 3, 5, 7]

        # статистика игры
        self.drawn_game = 0
        self.user_win = 0
        self.pc_win = 0

        self.create_widgets()

    def create_widgets(self):
        self.squares = {}
        num = 0
        for yi in range(0, 3):
            for xi in range(0, 3):
                self.squares[num] = Square(self, xi, yi, num)
                num += 1

        # кнопка старт
        self.btn = Button(self, text="Старт")
        self.btn.bind("<Button-1>", self.start)
        self.btn.place(x=290, y=77, width=120, anchor=NW)

        # Статус игры
        status = LabelFrame(self, text=" Статус игры ", width=400, height=40)
        status.place(x=10, y=110, anchor=NW)

        self.status_msg = StringVar()
        status_lbl = Label(status, textvariable=self.status_msg)
        status_lbl.place(x=5, y=0, anchor=NW)

        self.status_msg.set('Добро пожаловать в игру "Крестики - Нолики"!')

        # Выбор знака
        sign_choice = LabelFrame(self, text=" Выбор знака ", width=120, height=60)
        sign_choice.place(x=290, y=10, anchor=NW)

        self.user_choice = StringVar()
        self.user_choice.set('0')
        sign0 = Radiobutton(sign_choice, text='Ваш знак "0"', variable=self.user_choice, value='0')
        sign1 = Radiobutton(sign_choice, text='Ваш знак "X"', variable=self.user_choice, value='X')
        sign0.place(x=0, y=0, anchor=NW)
        sign1.place(x=0, y=18, anchor=NW)

        # Выбор хода
        step_choice = LabelFrame(self, text=" Выбрать ход ", width=120, height=95)
        step_choice.place(x=160, y=10, anchor=NW)

        self.step = IntVar()
        self.step.set(0)
        step0 = Radiobutton(step_choice, text='Случайным', variable=self.step, value=0)
        step1 = Radiobutton(step_choice, text='Первым', variable=self.step, value=1)
        step2 = Radiobutton(step_choice, text='Вторым', variable=self.step, value=2)
        step0.place(x=0, y=0, anchor=NW)
        step1.place(x=0, y=25, anchor=NW)
        step2.place(x=0, y=50, anchor=NW)

        # статистика
        statistics = LabelFrame(self, text=" Статистика ", width=140, height=95)
        statistics.place(x=10, y=10, anchor=NW)

        self.stat_msg = StringVar()
        stat_lbl = Label(statistics, textvariable=self.stat_msg, justify=LEFT)
        stat_lbl.place(x=5, y=0, anchor=NW)

        self.stat_msg.set(
            'Сыграно игр: %s.\nИз них:\n    побед - %s;\n    поражений - %s;\n    ничьих - %s.' %
            (self.user_win + self.pc_win + self.drawn_game, self.user_win, self.pc_win, self.drawn_game)
        )
        
        self.widgets = [sign0, sign1, step0, step1, step2, self.btn]

    def start(self, *args):
        self.choice_pc = None
        self.flag_end = False
        self.free_squares = self.free_squares_start[:]
        self.board = self.board_start[:]
        self.status_msg.set('Осторожно! Игра активирована! Удачи!')
        for square_num in range(9):
            square = self.squares[square_num]
            square.bind()
            square.set_msg('')
            square.cfg('white')
        if self.user_choice.get() == '0':
            self.user_sign, self.pc_sign = '0', 'X'
        else:
            self.user_sign, self.pc_sign = 'X', '0'
        uc = self.step.get()
        if uc == 2:
            self.turn_pc()
        elif uc == 0:
            rnd = random.choice([1, 2])
            if rnd == 2:
                self.turn_pc()
        self.widget_state()
        self.btn.unbind("<Button-1>")


    def widget_state(self, wst=DISABLED):
        for widget in self.widgets:
            widget.config(state=wst)


    def unbind_squares(self):
        for square_num in self.free_squares:
            self.squares[square_num].unbind()


    def greet_winner(self, sign):
        """ поздравления """
        if sign == self.user_sign:
            self.status_msg.set('Поздравляю с победой!')
            self.user_win += 1
        else:
            self.status_msg.set('Победа за компьютером! Ну как так?!!!')
            self.pc_win += 1
        self.flag_end = True


    def show_win(self, win, sign):
        if sign == self.user_sign:
            color = 'green'
        else:
            color = 'red'
        for square_num in win:
            square = self.squares[square_num]
            square.cfg(color)


    def game_over(self):
        """ окончание игры """
        self.stat_msg.set(
            'Сыграно игр: %s.\nИз них:\n    побед - %s;\n    поражений - %s;\n    ничьих - %s.' %
            (self.user_win + self.pc_win + self.drawn_game, self.user_win, self.pc_win, self.drawn_game)
        )
        self.widget_state(NORMAL)
        self.btn.bind("<Button-1>", self.start)


    def test_win(self, sign):
        """ определение исхода игры """
        for a, b, c in self.wins:
            if self.board[a] == sign and self.board[b] == sign and self.board[c] == sign:
                self.show_win((a, b, c), sign)
                self.unbind_squares()
                self.greet_winner(sign)
                self.game_over()
                return
        if not self.free_squares:
            self.status_msg.set('Ничья. Вы точно можете лучше!')
            self.flag_end = True
            self.drawn_game += 1
            for square_num in range(9):
                square = self.squares[square_num]
                square.cfg('yellow')
            self.game_over()


    def is_win(self, sign, iboard):
        for a, b, c in self.wins:
            if iboard[a] == sign and iboard[b] == sign and iboard[c] == sign:
                return True
        return False


    def find_best_turn(self, sign):
        for iturn in self.free_squares:
            iboard = self.board[:]
            iboard[iturn] = sign
            res = self.is_win(sign, iboard)
            if res:
                return iturn
        return None


    def default_choice(self):
        if not self.choice_pc:
            self.choice_pc=[4]
            random.shuffle(self.choice_pc1)
            random.shuffle(self.choice_pc2)
            self.choice_pc.extend(self.choice_pc1)
            self.choice_pc.extend(self.choice_pc2)
        for iturn in self.choice_pc:
            if iturn in self.free_squares:
                return iturn


    def turn_pc(self):
        """ ход пк """
        square_num = self.find_best_turn(self.pc_sign)
        if square_num == None:
            square_num = self.find_best_turn(self.user_sign)
            if square_num == None:
                square_num = self.default_choice()
        self.turn(square_num, self.pc_sign)


    def change_board(self, square_num, sign):
        """ заполнение клетки знаком """
        self.board[square_num] = sign
        self.free_squares.remove(square_num)


    def turn(self, square_num, sign):
        square = self.squares[square_num]
        square.unbind()
        square.set_msg(sign)
        self.change_board(square_num, sign)
        self.test_win(sign)

    def turn_user(self, square_num):
        """ ход игрока """
        self.turn(square_num, self.user_sign)
        if not self.flag_end:
            self.turn_pc()

def main():
    root = Tk()
    root.title('Игра "Крестики - Нолики"')
    root.geometry('421x582')
    root.resizable(FALSE, FALSE)
    app = Application(root)

    root.mainloop()

if __name__=='__main__':
    main()



RE: Tic-Tac-Toe - woooee - Jul-18-2018

Quote:Since you are using tkinter to do this as a GUI, you should switch to an object oriented programming paradigm (see the classes tutorial in my signature if you don't know what I'm talking about).
+1 You will only frustrate yourself trying to code GUIs without object oriented programs.

Your buttons don't do anything, i.e. there is no command=function_to_call http://effbot.org/tkinterbook/button.htm If you want to send row and column to a function then use partial as well
from functools import partial

## then
grid[rw][col] =  Button(mainFrame, text=" ", relief = 'solid',
                 command=partial(function_to_run_when_button_is_pressed, rw, col))

## then
def function_to_run_when_button_is_pressed(rw, col):
    print(rw, col) 



RE: Tic-Tac-Toe - Larz60+ - Jul-18-2018

he's doing it with bind statements.


RE: Tic-Tac-Toe - woooee - Jul-18-2018

None of the Buttons created under the 2 for statements have any associated bind statements as far as I can tell.


RE: Tic-Tac-Toe - juliabrushett - Jul-19-2018

Yes, unfortunately, my paradigm is very convoluted; but this is the way that I am supposed to write the code for the program. My professor even acknowledges that using the global variables is not desirable but that it is what would be best for what we've learned thus far. I hesitated to include the program instructions at first because of how much there is to read, but here they are. My current code is also pasted below, and the only things I am still having trouble with is getting the "X" to print (switches to "X's Turn," but prints "O") and completely restarting the game (only removes last player action):

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Create your main window and name it root. Set the title to Tic-Tac-Toe and it's size to 355x420

The Status Frame

To make the layout work we are going to have to use a couple of different frames. Create the first Frame and call it topFrame. You want to attach it to the root window, set the width property to 320 and the height property to 40. To locate the Frame on the window we are going to use the place function. Call the place function that is part of the topFrame and set x = 12 and y = 12.

Create a Label called lablStatus that is attached to the topFrame, set the text to "O's Turn", background color to yellow, forground color to blue and use a serif font. Pack the Label using the default side of TOP

The Main Frame

This frame will be used to hold the buttons for the tic-tac-toe game. Create the frame, call it mainFrame. Attach it to the root and set it's width to 330 and height to 330. In addition set the background color to black

Place the frame at x = 10, y = 42

Placing Buttons in the Main Frame

The main game is going to consist of nine Buttons that will display 'X' or 'O' when one of them is clicked and depending on who's turn it is. The easiest way to make these buttons is as a 2D array that is 3 rows by 3 columns. We will call this array grid. To create the Button array we should first create an empty 2D array. This can be accomplished using this code:

grid = [ [ 0 for i in range(3) ] for j in range(3) ]
The next step is to simply use a nested loop to initialize each of the buttons and place them in the mainFrame. This is somewhat complex so I will spare you and give you the code for it. If you are enterprising, fell free to try it on your own.

for rw in range(0, 3) :
for col in range(0, 3) :
grid[rw][col] = Button(mainFrame, text=" ", relief = 'solid' )
grid[rw][col].config(font = "monospace 36 bold", fg = 'red', height = 1, width = 3)
grid[rw][col].place(x = rw*105 + 10, y=col*105+10)
Create The Button

Create a Button called btnRestart and attach it to root, set the text to "Restart" , command to resetGame, and the width to 30. Place the Button at x = 45, and y = 380

Note:

At this point you have not created the function called restart that was used for the button command. You might want to add that now. Simply put a print statement that says 'Restart Code' or some other message. This will all be changed when we get to the game logic portion of the code.

At this point you should have GUI that runs and looks like the code below. One last thing though. Make sure you have the mainloop code at the bottom of this.

Assignment17Steps.png

Needed Functionality
Before getting into the the needed functions there are a few variables that need to be created to make the game function. These should be placed just before the creation of the main window.

grid - this is a 2D array. Created this way: grid = [ [ 0 for i in range(3) ] for j in range(3) ]
numClicks set to 0 - Used to keep track of the number of moves
isDone set to False - Used to keep track of game completion
isXTurn set to True - Used to keep track whose turn it is.
We are going to do something to create this game that I am really against and that is using global variables. Probably the correct way to do this would be to create it as a class but we haven't gotten there yet. In any event we will go this route but keep in mind this is not ideal!!

The resetGame Function

The resetGame function should have been written when you created the GUI. This function is going to be used to restart the game. Here are the steps to complete the function:

define isXTurn, numClicks, isDone as global. You simply put the global keyword in front of them

global isXTurn

Create a nested for loop whose inner and outer loop run three times. Inside the loop you are going to call grid[row][col] config method setting the text to a space: ' '

Set numClicks to 0

Set isXTurn to True

Set isDone to False

The markSpace Function

This function takes two parameters rw and col and is used to mark the appropriate space with an X or an O. Here are the steps to complete it:

define isXTurn, numClicks, isDone as global. This is the same as you did int the previous function

Check to see is the isDone variable is True. If it is the game is over so simply return

Get the space to mark using this code: space = grid[rw][col].cget('text')

Check to see if space is equal to an empty space. If it is then

Check to see it it is XTurn. If it is then do the following:

grid[rw][col].config(text = 'X', fg = 'red')
lblStatus.config(text = 'O\'s Turn')
Otherwise:

grid[rw][col].config(text = 'O', fg = 'blue')
lblStatus.config(text = 'X\'s Turn')
If the space variable is not equal to an empty space then simply set the text of lblStatus to 'Invalid Move' and return

set isXTurn to the not of isXTurn

increment numClicks by 1

call the gameOver function and pass rw and col to it.

The gameOver function

This function is used to check to see if the game has been completed and won. It is somewhat of a tricky function so I am simply going to give you the code.

def gameOver(rw, col) :
global numClicks
global isDone
winner = ' '

if(grid[0][0].cget('text') == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[2][2].cget('text')) :
winner = grid[0][0].cget('text')
elif(grid[2][0].cget('text') == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[0][2].cget('text')) :
winner = grid[2][1].cget('text')

else :
for r in range(0, 3) :
if(grid[r][0].cget('text') != ' ' and grid[r][0].cget('text') == grid[r][1].cget('text') and grid[r][1].cget('text') == grid[r][2].cget('text')):
winner = grid[r][0].cget('text')
elif(grid[0][r].cget('text') != ' ' and grid[0][r].cget('text') == grid[1][r].cget('text') and grid[1][r].cget('text') == grid[2][r].cget('text')):
winner = grid[0][r].cget('text')

isDone = True
if(winner == ' ' and numClicks >= 9) :
lblStatus.config(text = 'Tie Game')
elif(winner != ' ') :
lblStatus.config(text = winner + ' Wins!')
else :
lblStatus.config(text = 'X\'s Turn' if isXTurn else 'O\'s Turn')
isDone = False

At this point you should have a working game. If not then you need to go back and check the logic.

from tkinter import *

def restart() :
    global isXTurn, isDone, numClicks
    for i in range(3) :
        for j in range(3) :
            grid[rw][col].config(text = ' ')
    numClicks = 0
    isXTurn = True
    isDone = False
    print('Restart Game')

def markSpace(rw, col) :
    global isXTurn, isDone, numClicks

    space = grid[rw][col].cget('text')
    
    if(isDone == True) :
        return
    elif (space == ' ') :
        if (space == isXTurn) :
            grid[rw][col].config(text = 'X', fg = 'red')
            lablStatus.config(text = 'O\'s Turn')
        else :
            grid[rw][col].config(text = 'O', fg = 'blue')
            lablStatus.config(text = 'X\'s Turn')
    else :
        lablStatus.config(text = 'Invalid Move')
        return

    numClicks += 1
    isXTurn = not isXTurn
    gameOver(rw, col)

def gameOver(rw, col) :
    global numClicks
    global isDone
    winner = ' '

    if(grid[0][0].cget('text')  == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[2][2].cget('text')) :
        winner = grid[0][0].cget('text')
    elif(grid[2][0].cget('text')  == grid[1][1].cget('text') and grid[1][1].cget('text') == grid[0][2].cget('text')) :
        winner = grid[2][0].cget('text')

    else :
        for r in range(0, 3) :
            if(grid[r][0].cget('text') != ' ' and grid[r][0].cget('text') == grid[r][1].cget('text') and grid[r][1].cget('text') == grid[r][2].cget('text')):
                winner = grid[r][0].cget('text')
            elif(grid[0][r].cget('text') != ' ' and grid[0][r].cget('text') == grid[1][r].cget('text') and grid[1][r].cget('text') == grid[2][r].cget('text')):
                winner = grid[0][r].cget('text')

    isDone = True
    if(winner == ' ' and numClicks >= 9) :
       lablStatus.config(text = 'Tie Game')
    elif(winner != ' ') :
        lablStatus.config(text = winner + ' Wins!')
    else :
        lablStatus.config(text = 'X\'s Turn' if isXTurn else 'O\'s Turn')
        isDone = False

grid = [ [ 0 for i in range(3) ] for j in range(3) ]
numClicks = 0
isDone = False
isXTurn = True

root = Tk()
root.title('Tic-Tac-Toe')
root.geometry('355x420')

topFrame = Frame(root, width = 320, height = 40).place(x = 12, y = 12)

lablStatus = Label(topFrame, text = 'O\'s Turn')
lablStatus.config(fg = 'blue', bg = 'yellow', font = 'serif 16 bold')
lablStatus.pack(side = TOP)

mainFrame = Frame(root, width = 330, height = 330)
mainFrame.config(bg = 'black')
mainFrame.place(x = 10, y = 42)

grid = [ [ 0 for i in range(3) ] for j in range(3) ]
for rw in range(0, 3) :
    for col in range(0, 3) :
        grid[rw][col] =  Button(mainFrame, text=' ', relief = 'solid', command = lambda r = rw, c = col: markSpace(r, c))
        grid[rw][col].config(font = 'monospace 36 bold', fg = 'red', height = 1,  width = 3)
        grid[rw][col].place(x = rw*105 + 10, y=col*105+10)

btnRestart = Button(root, text = 'Restart', command = restart, width = 30)
btnRestart.place(x = 45, y = 380)


root.mainloop()



RE: Tic-Tac-Toe - woooee - Jul-19-2018

This can never be true
    elif (space == ' ') :
        if (space == isXTurn) : 
You have to add some print statements at a minimum to test. The problem is not that you can't get X or O to display, the problem is that you have not done any testing so have no idea what is going on.