Skip to content

Commit da95fe7

Browse files
authored
Merge branch 'master' into queue
2 parents ddb8b6f + 20a6526 commit da95fe7

File tree

7 files changed

+882
-0
lines changed

7 files changed

+882
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Build a Tic-Tac-Toe Game With Python and Tkinter
2+
3+
This is the code for the Real Python tutorial [Build a Tic-Tac-Toe Game With Python and Tkinter](https://realpython.com/tic-tac-toe-python/). You learn to implement a tic-tac-toe game with Python and Tkinter. It allows two players to play a tic-tac-toe game on a shared device.
4+
5+
## Installation
6+
7+
The game uses Tkinter to build its user interface. Since Tkinter is part of the Python standard library, you don't need to install anything to run the game. Just make sure that you have a version of Tkinter greater than or equal to 8.6.
8+
9+
## Run the Game
10+
11+
To run the game, just execute the following command on your command line:
12+
13+
```sh
14+
$ python tic_tac_toe.py
15+
```
16+
17+
## About the Author
18+
19+
Leodanis Pozo Ramos - Email: [email protected]
20+
21+
## License
22+
23+
Distributed under the MIT license. See `LICENSE` in the root directory of this `materials` repo for more information.
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
"""A tic-tac-toe game built with Python and Tkinter."""
2+
3+
import tkinter as tk
4+
from itertools import cycle
5+
from tkinter import font
6+
from typing import NamedTuple
7+
8+
9+
class Player(NamedTuple):
10+
label: str
11+
color: str
12+
13+
14+
class Move(NamedTuple):
15+
row: int
16+
col: int
17+
label: str = ""
18+
19+
20+
BOARD_SIZE = 3
21+
DEFAULT_PLAYERS = (
22+
Player(label="X", color="blue"),
23+
Player(label="O", color="green"),
24+
)
25+
26+
27+
class TicTacToeGame:
28+
def __init__(self, players=DEFAULT_PLAYERS, board_size=BOARD_SIZE):
29+
self._players = cycle(players)
30+
self.board_size = board_size
31+
self.current_player = next(self._players)
32+
self.winner_combo = []
33+
self._current_moves = []
34+
self._has_winner = False
35+
self._winning_combos = []
36+
self._setup_board()
37+
38+
def _setup_board(self):
39+
self._current_moves = [
40+
[Move(row, col) for col in range(self.board_size)]
41+
for row in range(self.board_size)
42+
]
43+
self._winning_combos = self._get_winning_combos()
44+
45+
def _get_winning_combos(self):
46+
rows = [
47+
[(move.row, move.col) for move in row]
48+
for row in self._current_moves
49+
]
50+
columns = [list(col) for col in zip(*rows)]
51+
first_diagonal = [row[i] for i, row in enumerate(rows)]
52+
second_diagonal = [col[j] for j, col in enumerate(reversed(columns))]
53+
return rows + columns + [first_diagonal, second_diagonal]
54+
55+
def is_valid_move(self, move):
56+
"""Return True if move is valid, and False otherwise."""
57+
row, col = move.row, move.col
58+
move_was_not_played = self._current_moves[row][col].label == ""
59+
no_winner = not self._has_winner
60+
return no_winner and move_was_not_played
61+
62+
def process_move(self, move):
63+
"""Process the current move and check if it's a win."""
64+
row, col = move.row, move.col
65+
self._current_moves[row][col] = move
66+
for combo in self._winning_combos:
67+
results = set(self._current_moves[n][m].label for n, m in combo)
68+
is_win = (len(results) == 1) and ("" not in results)
69+
if is_win:
70+
self._has_winner = True
71+
self.winner_combo = combo
72+
break
73+
74+
def has_winner(self):
75+
"""Return True if the game has a winner, and False otherwise."""
76+
return self._has_winner
77+
78+
def is_tied(self):
79+
"""Return True if the game is tied, and False otherwise."""
80+
no_winner = not self._has_winner
81+
played_moves = (
82+
move.label for row in self._current_moves for move in row
83+
)
84+
return no_winner and all(played_moves)
85+
86+
def toggle_player(self):
87+
"""Return a toggled player."""
88+
self.current_player = next(self._players)
89+
90+
def reset_game(self):
91+
"""Reset the game state to play again."""
92+
for row, row_content in enumerate(self._current_moves):
93+
for col, _ in enumerate(row_content):
94+
row_content[col] = Move(row, col)
95+
self._has_winner = False
96+
self.winner_combo = []
97+
98+
99+
class TicTacToeBoard(tk.Tk):
100+
def __init__(self, game):
101+
super().__init__()
102+
self.title("Tic-Tac-Toe Game")
103+
self._cells = {}
104+
self._game = game
105+
self._create_menu()
106+
self._create_board_display()
107+
self._create_board_grid()
108+
109+
def _create_menu(self):
110+
menu_bar = tk.Menu(master=self)
111+
self.config(menu=menu_bar)
112+
file_menu = tk.Menu(master=menu_bar)
113+
file_menu.add_command(label="Play Again", command=self.reset_board)
114+
file_menu.add_separator()
115+
file_menu.add_command(label="Exit", command=quit)
116+
menu_bar.add_cascade(label="File", menu=file_menu)
117+
118+
def _create_board_display(self):
119+
display_frame = tk.Frame(master=self)
120+
display_frame.pack(fill=tk.X)
121+
self.display = tk.Label(
122+
master=display_frame,
123+
text="Ready?",
124+
font=font.Font(size=28, weight="bold"),
125+
)
126+
self.display.pack()
127+
128+
def _create_board_grid(self):
129+
grid_frame = tk.Frame(master=self)
130+
grid_frame.pack()
131+
for row in range(self._game.board_size):
132+
self.rowconfigure(row, weight=1, minsize=50)
133+
self.columnconfigure(row, weight=1, minsize=75)
134+
for col in range(self._game.board_size):
135+
button = tk.Button(
136+
master=grid_frame,
137+
text="",
138+
font=font.Font(size=36, weight="bold"),
139+
fg="black",
140+
width=3,
141+
height=2,
142+
highlightbackground="lightblue",
143+
)
144+
self._cells[button] = (row, col)
145+
button.bind("<ButtonPress-1>", self.play)
146+
button.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
147+
148+
def play(self, event):
149+
"""Handle a player's move."""
150+
clicked_btn = event.widget
151+
row, col = self._cells[clicked_btn]
152+
move = Move(row, col, self._game.current_player.label)
153+
if self._game.is_valid_move(move):
154+
self._update_button(clicked_btn)
155+
self._game.process_move(move)
156+
if self._game.is_tied():
157+
self._update_display(msg="Tied game!", color="red")
158+
elif self._game.has_winner():
159+
self._highlight_cells()
160+
msg = f'Player "{self._game.current_player.label}" won!'
161+
color = self._game.current_player.color
162+
self._update_display(msg, color)
163+
else:
164+
self._game.toggle_player()
165+
msg = f"{self._game.current_player.label}'s turn"
166+
self._update_display(msg)
167+
168+
def _update_button(self, clicked_btn):
169+
clicked_btn.config(text=self._game.current_player.label)
170+
clicked_btn.config(fg=self._game.current_player.color)
171+
172+
def _update_display(self, msg, color="black"):
173+
self.display["text"] = msg
174+
self.display["fg"] = color
175+
176+
def _highlight_cells(self):
177+
for button, coordinates in self._cells.items():
178+
if coordinates in self._game.winner_combo:
179+
button.config(highlightbackground="red")
180+
181+
def reset_board(self):
182+
"""Reset the game's board to play again."""
183+
self._game.reset_game()
184+
self._update_display(msg="Ready?")
185+
for button in self._cells.keys():
186+
button.config(highlightbackground="lightblue")
187+
button.config(text="")
188+
button.config(fg="black")
189+
190+
191+
def main():
192+
"""Create the game's board and run its main loop."""
193+
game = TicTacToeGame()
194+
board = TicTacToeBoard(game)
195+
board.mainloop()
196+
197+
198+
if __name__ == "__main__":
199+
main()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
"""A tic-tac-toe game built with Python and Tkinter."""
2+
3+
import tkinter as tk
4+
from tkinter import font
5+
6+
7+
class TicTacToeBoard(tk.Tk):
8+
def __init__(self):
9+
super().__init__()
10+
self.title("Tic-Tac-Toe Game")
11+
self._cells = {}
12+
self._create_board_display()
13+
self._create_board_grid()
14+
15+
def _create_board_display(self):
16+
display_frame = tk.Frame(master=self)
17+
display_frame.pack(fill=tk.X)
18+
self.display = tk.Label(
19+
master=display_frame,
20+
text="Ready?",
21+
font=font.Font(size=28, weight="bold"),
22+
)
23+
self.display.pack()
24+
25+
def _create_board_grid(self):
26+
grid_frame = tk.Frame(master=self)
27+
grid_frame.pack()
28+
for row in range(3):
29+
self.rowconfigure(row, weight=1, minsize=50)
30+
self.columnconfigure(row, weight=1, minsize=75)
31+
for col in range(3):
32+
button = tk.Button(
33+
master=grid_frame,
34+
text="",
35+
font=font.Font(size=36, weight="bold"),
36+
fg="black",
37+
width=3,
38+
height=2,
39+
highlightbackground="lightblue",
40+
)
41+
self._cells[button] = (row, col)
42+
button.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
43+
44+
45+
def main():
46+
"""Create the game's board and run its main loop."""
47+
board = TicTacToeBoard()
48+
board.mainloop()
49+
50+
51+
if __name__ == "__main__":
52+
main()
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
"""A tic-tac-toe game built with Python and Tkinter."""
2+
3+
import tkinter as tk
4+
from itertools import cycle
5+
from tkinter import font
6+
from typing import NamedTuple
7+
8+
9+
class Player(NamedTuple):
10+
label: str
11+
color: str
12+
13+
14+
class Move(NamedTuple):
15+
row: int
16+
col: int
17+
label: str = ""
18+
19+
20+
BOARD_SIZE = 3
21+
DEFAULT_PLAYERS = (
22+
Player(label="X", color="blue"),
23+
Player(label="O", color="green"),
24+
)
25+
26+
27+
class TicTacToeGame:
28+
def __init__(self, players=DEFAULT_PLAYERS, board_size=BOARD_SIZE):
29+
self._players = cycle(players)
30+
self.board_size = board_size
31+
self.current_player = next(self._players)
32+
self.winner_combo = []
33+
self._current_moves = []
34+
self._has_winner = False
35+
self._winning_combos = []
36+
self._setup_board()
37+
38+
def _setup_board(self):
39+
self._current_moves = [
40+
[Move(row, col) for col in range(self.board_size)]
41+
for row in range(self.board_size)
42+
]
43+
self._winning_combos = self._get_winning_combos()
44+
45+
def _get_winning_combos(self):
46+
rows = [
47+
[(move.row, move.col) for move in row]
48+
for row in self._current_moves
49+
]
50+
columns = [list(col) for col in zip(*rows)]
51+
first_diagonal = [row[i] for i, row in enumerate(rows)]
52+
second_diagonal = [col[j] for j, col in enumerate(reversed(columns))]
53+
return rows + columns + [first_diagonal, second_diagonal]
54+
55+
56+
class TicTacToeBoard(tk.Tk):
57+
def __init__(self):
58+
super().__init__()
59+
self.title("Tic-Tac-Toe Game")
60+
self._cells = {}
61+
self._create_board_display()
62+
self._create_board_grid()
63+
64+
def _create_board_display(self):
65+
display_frame = tk.Frame(master=self)
66+
display_frame.pack(fill=tk.X)
67+
self.display = tk.Label(
68+
master=display_frame,
69+
text="Ready?",
70+
font=font.Font(size=28, weight="bold"),
71+
)
72+
self.display.pack()
73+
74+
def _create_board_grid(self):
75+
grid_frame = tk.Frame(master=self)
76+
grid_frame.pack()
77+
for row in range(3):
78+
self.rowconfigure(row, weight=1, minsize=50)
79+
self.columnconfigure(row, weight=1, minsize=75)
80+
for col in range(3):
81+
button = tk.Button(
82+
master=grid_frame,
83+
text="",
84+
font=font.Font(size=36, weight="bold"),
85+
fg="black",
86+
width=3,
87+
height=2,
88+
highlightbackground="lightblue",
89+
)
90+
self._cells[button] = (row, col)
91+
button.grid(row=row, column=col, padx=5, pady=5, sticky="nsew")
92+
93+
94+
def main():
95+
"""Create the game's board and run its main loop."""
96+
board = TicTacToeBoard()
97+
board.mainloop()
98+
99+
100+
if __name__ == "__main__":
101+
main()

0 commit comments

Comments
 (0)