Posts: 14
Threads: 9
Joined: Jun 2018
Jul-17-2018, 07:57 AM
(This post was last modified: Jul-17-2018, 08:00 AM by juliabrushett.)
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)
Posts: 4,220
Threads: 97
Joined: Sep 2016
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?
Posts: 9
Threads: 2
Joined: Jun 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()
Posts: 536
Threads: 0
Joined: Feb 2018
Jul-18-2018, 12:11 AM
(This post was last modified: Jul-18-2018, 12:12 AM by woooee.)
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)
Posts: 12,034
Threads: 486
Joined: Sep 2016
he's doing it with bind statements.
Posts: 536
Threads: 0
Joined: Feb 2018
None of the Buttons created under the 2 for statements have any associated bind statements as far as I can tell.
Posts: 14
Threads: 9
Joined: Jun 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()
Posts: 536
Threads: 0
Joined: Feb 2018
Jul-19-2018, 06:10 PM
(This post was last modified: Jul-19-2018, 06:10 PM by woooee.)
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.
|