File tree

1 file changed

+386
-0
lines changed

1 file changed

+386
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,386 @@
1+
"""
2+
tic-tac-toe Game using PySimpleGUI
3+
4+
An update to the initial version where once the current board is
5+
Won/Complete, a Yes/No popup would let the players decide
6+
if they would want to continue playing.
7+
8+
YES would let them continue playing the game.
9+
NO would take the control back to the game
10+
initialization window to start a fresh session with
11+
new players.
12+
13+
Design pattern used for implementing multiple windows
14+
Using read_all_windows() @
15+
https://.com/PySimpleGUI/PySimpleGUI/blob/master/DemoPrograms/Demo_Design_Pattern_Multiple_Windows1.py
16+
17+
Only 1 window at a time is visible/active on the screen.
18+
19+
1. INIT_WINDOW opens the GAME_BOARD screen.
20+
21+
2. Closing the GAME_BOARD exits the game and
22+
returns to the INIT_WINDOW with the details
23+
from the previous session intact.
24+
25+
3. Exiting the INIT_WINDOW would exit the game.
26+
27+
"""
28+
29+
import os
30+
import numpy as np
31+
import PySimpleGUI as sg
32+
33+
INIT_WINDOW = None
34+
GAME_BOARD = None
35+
36+
CURRENT_WORKING_DIRECTORY = os.path.dirname(os.path.realpath(__file__))
37+
X_IMAGE = CURRENT_WORKING_DIRECTORY + '\\X.png'
38+
X_RED = CURRENT_WORKING_DIRECTORY + '\\X_Red.png'
39+
O_IMAGE = CURRENT_WORKING_DIRECTORY + '\\O.png'
40+
O_RED = CURRENT_WORKING_DIRECTORY + '\\O_Red.png'
41+
GAME_ICON = CURRENT_WORKING_DIRECTORY + '\\tictactoe.ico'
42+
43+
START_GAME: bool = False
44+
CHECK_FOR_WINNER: bool = False
45+
MAIN_DIAGONAL_IS_WINNER: bool = False
46+
CURENT_BOARD_WON: bool = False
47+
CONTINUE_WITH_NEXT_GAME: str = ''
48+
STEP_COUNTER: int = 0
49+
PLAYER_SWITCH = True
50+
51+
PLAYER1_NAME: str = ''
52+
PLAYER1_MARKER: str = ''
53+
PLAYER2_NAME: str = ''
54+
PLAYER2_MARKER: str = ''
55+
56+
ROWS, COLS = (3, 3)
57+
GAME_PROGRESS_ARRAY = [['' for i in range(COLS)] for j in range(ROWS)]
58+
GAME_PROGRESS_ARRAY = np.array(GAME_PROGRESS_ARRAY, dtype=str)
59+
# WINNING_PATTERNS: list = [['00', '10', '20'], ['01', '11', '21'], ['02', '12', '22'], # rows
60+
# ['00', '01', '02'], ['10', '11', '12'], ['20', '21', '22'], # columns
61+
# ['00', '11', '22'], ['00', '11', '02']] # diagonals
62+
63+
def split(word):
64+
'''splits a string to constituents chars.'''
65+
return [int(char) for char in word]
66+
67+
def progress_game(key: str, player_marker: str):
68+
'''populated the 'GAME_PROGRESS_ARRAY' and
69+
checks for is winning condition.'''
70+
71+
global GAME_PROGRESS_ARRAY
72+
global CURENT_BOARD_WON
73+
74+
continue_with_next_game: str = ''
75+
76+
row, column = split(key)
77+
GAME_PROGRESS_ARRAY[row][column] = player_marker
78+
79+
if CHECK_FOR_WINNER:
80+
game_won, winning_marker = is_winning()
81+
if game_won:
82+
CURENT_BOARD_WON = True
83+
continue_with_next_game = display_winner_and_continue(winning_marker=winning_marker)
84+
85+
return continue_with_next_game
86+
87+
def is_row_column_diagonal_complete(row_col_num: int = -1, is_row: bool = True, is_diagonal: bool = False):
88+
'''checks if the given row or column is complete
89+
to proceed with a winner.'''
90+
91+
if is_diagonal is False and row_col_num != -1:
92+
if is_row:
93+
row = row_col_num
94+
if GAME_PROGRESS_ARRAY[row][0] != '' and \
95+
GAME_PROGRESS_ARRAY[row][1] != '' and \
96+
GAME_PROGRESS_ARRAY[row][2] != '':
97+
return True
98+
else:
99+
return False
100+
else:
101+
col = row_col_num
102+
if GAME_PROGRESS_ARRAY[0][col] != '' and \
103+
GAME_PROGRESS_ARRAY[1][col] != '' and \
104+
GAME_PROGRESS_ARRAY[2][col] != '':
105+
return True
106+
else:
107+
return False
108+
else:
109+
if GAME_PROGRESS_ARRAY[0][0] != '' and \
110+
GAME_PROGRESS_ARRAY[1][1] != '' and \
111+
GAME_PROGRESS_ARRAY[2][2] != '':
112+
return True
113+
elif GAME_PROGRESS_ARRAY[2][0] != '' and \
114+
GAME_PROGRESS_ARRAY[1][1] != '' and \
115+
GAME_PROGRESS_ARRAY[0][2] != '':
116+
return True
117+
118+
119+
def mark_the_winner(row_is_winner: bool, row_column_index: int = -1, diagonal_is_winner: bool = False):
120+
'''marks the winner row/column by updating
121+
the button row/column.'''
122+
123+
if not diagonal_is_winner and row_column_index != -1:
124+
if row_is_winner:
125+
row = row_column_index
126+
if GAME_PROGRESS_ARRAY[row][0] == 'X':
127+
GAME_BOARD.Element(str(row)+str(0)).update(image_filename=X_RED)
128+
GAME_BOARD.Element(str(row)+str(1)).update(image_filename=X_RED)
129+
GAME_BOARD.Element(str(row)+str(2)).update(image_filename=X_RED)
130+
else:
131+
GAME_BOARD.Element(str(row)+str(0)).update(image_filename=O_RED)
132+
GAME_BOARD.Element(str(row)+str(1)).update(image_filename=O_RED)
133+
GAME_BOARD.Element(str(row)+str(2)).update(image_filename=O_RED)
134+
else:
135+
col = row_column_index
136+
if GAME_PROGRESS_ARRAY[0][col] == 'X':
137+
GAME_BOARD.Element(str(0)+str(col)).update(image_filename=X_RED)
138+
GAME_BOARD.Element(str(1)+str(col)).update(image_filename=X_RED)
139+
GAME_BOARD.Element(str(2)+str(col)).update(image_filename=X_RED)
140+
else:
141+
GAME_BOARD.Element(str(0)+str(col)).update(image_filename=O_RED)
142+
GAME_BOARD.Element(str(1)+str(col)).update(image_filename=O_RED)
143+
GAME_BOARD.Element(str(2)+str(col)).update(image_filename=O_RED)
144+
else:
145+
if MAIN_DIAGONAL_IS_WINNER:
146+
if GAME_PROGRESS_ARRAY[1][1] == 'X':
147+
GAME_BOARD.Element(str(0)+str(0)).update(image_filename=X_RED)
148+
GAME_BOARD.Element(str(1)+str(1)).update(image_filename=X_RED)
149+
GAME_BOARD.Element(str(2)+str(2)).update(image_filename=X_RED)
150+
else:
151+
GAME_BOARD.Element(str(0)+str(0)).update(image_filename=O_RED)
152+
GAME_BOARD.Element(str(1)+str(1)).update(image_filename=O_RED)
153+
GAME_BOARD.Element(str(2)+str(2)).update(image_filename=O_RED)
154+
else:
155+
if GAME_PROGRESS_ARRAY[1][1] == 'X':
156+
GAME_BOARD.Element(str(0)+str(2)).update(image_filename=X_RED)
157+
GAME_BOARD.Element(str(1)+str(1)).update(image_filename=X_RED)
158+
GAME_BOARD.Element(str(2)+str(0)).update(image_filename=X_RED)
159+
else:
160+
GAME_BOARD.Element(str(0)+str(2)).update(image_filename=O_RED)
161+
GAME_BOARD.Element(str(1)+str(1)).update(image_filename=O_RED)
162+
GAME_BOARD.Element(str(2)+str(0)).update(image_filename=O_RED)
163+
164+
def is_winning():
165+
'''evaluated the current state of the gameboard
166+
and checks if there is a winner currently.'''
167+
168+
global GAME_PROGRESS_ARRAY
169+
global CHECK_FOR_WINNER
170+
global MAIN_DIAGONAL_IS_WINNER
171+
172+
# check for the row wise sequence.
173+
for row in range(ROWS):
174+
if is_row_column_diagonal_complete(row_col_num=row, is_row=True):
175+
if GAME_PROGRESS_ARRAY[row][0] == GAME_PROGRESS_ARRAY[row][1] == GAME_PROGRESS_ARRAY[row][2]:
176+
mark_the_winner(row_is_winner=True, row_column_index=row)
177+
# continue_game = display_winner_and_continue(winning_marker=GAME_PROGRESS_ARRAY[row][0])
178+
CHECK_FOR_WINNER = False
179+
return True, GAME_PROGRESS_ARRAY[row][0]
180+
181+
# check for the column wise sequence.
182+
for col in range(COLS):
183+
if is_row_column_diagonal_complete(row_col_num=col, is_row=False):
184+
if GAME_PROGRESS_ARRAY[0][col] == GAME_PROGRESS_ARRAY[1][col] == GAME_PROGRESS_ARRAY[2][col]:
185+
mark_the_winner(row_is_winner=False, row_column_index=col)
186+
# display_winner_and_continue(winning_marker=GAME_PROGRESS_ARRAY[0][col])
187+
CHECK_FOR_WINNER = False
188+
return True, GAME_PROGRESS_ARRAY[0][col]
189+
190+
# check for the 2 diagonals for a winning sequence.
191+
if is_row_column_diagonal_complete(is_diagonal=True):
192+
if GAME_PROGRESS_ARRAY[0][0] == GAME_PROGRESS_ARRAY[1][1] == GAME_PROGRESS_ARRAY[2][2]:
193+
MAIN_DIAGONAL_IS_WINNER = True
194+
mark_the_winner(row_column_index=-1, row_is_winner=False, diagonal_is_winner=True)
195+
# display_winner_and_continue(winning_marker=GAME_PROGRESS_ARRAY[1][1])
196+
CHECK_FOR_WINNER = False
197+
return True, GAME_PROGRESS_ARRAY[1][1]
198+
199+
elif GAME_PROGRESS_ARRAY[2][0] == GAME_PROGRESS_ARRAY[1][1] == GAME_PROGRESS_ARRAY[0][2]:
200+
mark_the_winner(row_column_index=-1, row_is_winner=False, diagonal_is_winner=True)
201+
# display_winner_and_continue(winning_marker=GAME_PROGRESS_ARRAY[1][1])
202+
CHECK_FOR_WINNER = False
203+
return True, GAME_PROGRESS_ARRAY[1][1]
204+
205+
return False, ''
206+
207+
def display_winner_and_continue(winning_marker: str):
208+
'''display the winner of the current board.'''
209+
210+
if winning_marker == PLAYER1_MARKER:
211+
popup_result = sg.PopupYesNo('The Winner is ' + PLAYER1_NAME + '.\nDo you want to play another game with the current players?',
212+
title='Board Winner!', text_color='darkblue', icon=GAME_ICON,
213+
grab_anywhere=True, font=('Blackadder ITC', 20))
214+
elif winning_marker == PLAYER2_MARKER:
215+
popup_result = sg.PopupYesNo('The Winner is ' + PLAYER2_NAME + '.\nDo you want to play another game with the current players?',
216+
title='Board Winner!', text_color='darkblue', icon=GAME_ICON,
217+
grab_anywhere=True, font=('Blackadder ITC', 20))
218+
219+
return popup_result
220+
221+
def init_game_window():
222+
'''Initializes and creates the game options window.'''
223+
init_game_layout = [[sg.Text('Player 1 Name: ', size=(12, 1)),
224+
sg.InputText('', key='-P1_NAME-')],
225+
[sg.Text('Player 2 Name: ', size=(12, 1)),
226+
sg.InputText('', key='-P2_NAME-')],
227+
[sg.Frame(layout=[[sg.Radio('X', group_id="P1_PREF", key='-P1_MARK-',
228+
default=True, size=(10, 1)),
229+
sg.Radio('O', group_id="P1_PREF", key='-P2_MARK-')]],
230+
title='Player 1 Preference', relief=sg.RELIEF_GROOVE,
231+
tooltip='Set Player 1 Preference')],
232+
[sg.Button("Start Game", key='-START-'), sg.Button('Exit', key='-EXIT-')]]
233+
234+
return sg.Window('Tic Tac Toe Options', init_game_layout, icon=GAME_ICON, finalize=True)
235+
236+
def reset_game_board():
237+
'''Resets the current game board and re-initializes all the
238+
game parameters to continue playing the game with the same players.'''
239+
240+
global GAME_PROGRESS_ARRAY
241+
global STEP_COUNTER
242+
global CONTINUE_WITH_NEXT_GAME
243+
global CHECK_FOR_WINNER
244+
global CURENT_BOARD_WON
245+
global GAME_BOARD
246+
global PLAYER_SWITCH
247+
248+
GAME_BOARD = initialize_game_board()
249+
GAME_PROGRESS_ARRAY = [['' for i in range(COLS)] for j in range(ROWS)]
250+
GAME_PROGRESS_ARRAY = np.array(GAME_PROGRESS_ARRAY, dtype=str)
251+
STEP_COUNTER = 0
252+
CHECK_FOR_WINNER = False
253+
CONTINUE_WITH_NEXT_GAME = ''
254+
CURENT_BOARD_WON = False
255+
PLAYER_SWITCH = True
256+
257+
def initialize_game_board():
258+
'''initialize the game board.'''
259+
260+
global PLAYER1_NAME
261+
global PLAYER1_NAME
262+
global PLAYER1_MARKER
263+
global PLAYER2_MARKER
264+
265+
GAME_BOARD_LAYOUT = [[sg.Text('Player 1: ' + PLAYER1_NAME, key='-P1-', text_color='darkblue')],
266+
[sg.Text('Player 2: ' + PLAYER2_NAME, key='-P2-', text_color='white')],
267+
[sg.Text(PLAYER1_NAME + "'s Marker: " + PLAYER1_MARKER)],
268+
[sg.Text(PLAYER2_NAME + "'s Marker: " + PLAYER2_MARKER)],
269+
[sg.Text('')]]
270+
271+
GAME_BOARD_LAYOUT += [[sg.Button(' ', size=(8, 4), key=str(j)+str(i))
272+
for i in range(3)] for j in range(3)]
273+
274+
BOARD = sg.Window('Tic Tac Toe', GAME_BOARD_LAYOUT, icon=GAME_ICON, finalize=True)
275+
276+
BOARD.TKroot.protocol("WM_DESTROY_WINDOW", lambda: BOARD.write_event_value("WIN_CLOSE", ()))
277+
BOARD.TKroot.protocol("WM_DELETE_WINDOW", lambda: BOARD.write_event_value("WIN_CLOSE", ()))
278+
279+
return BOARD
280+
281+
# Design pattern 1 - First window does not remain active
282+
GAME_BOARD = None
283+
INIT_WINDOW = init_game_window()
284+
285+
while True:
286+
287+
WINDOW, EVENT, VALUES = sg.read_all_windows()
288+
289+
if EVENT in (sg.WIN_CLOSED, '-EXIT-') and WINDOW == INIT_WINDOW:
290+
break
291+
292+
if EVENT == '-START-' and not GAME_BOARD:
293+
294+
if VALUES['-P1_NAME-'] == '' and VALUES['-P2_NAME-'] == '':
295+
sg.popup_ok("Error initializing players name. Enter both the players name before proceeding.",
296+
title='Tic Tac Toe', icon=GAME_ICON)
297+
298+
else:
299+
PLAYER1_NAME, PLAYER2_NAME, PLAYER1_X, PLAYER2_X = VALUES['-P1_NAME-'], VALUES['-P2_NAME-'], VALUES['-P1_MARK-'], VALUES['-P2_MARK-']
300+
301+
# Get the PLayer Markers to start with.
302+
if PLAYER1_X:
303+
PLAYER1_MARKER, PLAYER2_MARKER = ("X", "O")
304+
else:
305+
PLAYER1_MARKER, PLAYER2_MARKER = ("O", "X")
306+
307+
# set the flag to start the game as once the
308+
# window is closed the event loop will be destroyed.
309+
if EVENT == '-START-':
310+
if VALUES['-P1_NAME-'] is not None and VALUES['-P2_NAME-'] is not None:
311+
START_GAME = True
312+
# Close the options window and start the game.
313+
INIT_WINDOW.close()
314+
GAME_BOARD = initialize_game_board()
315+
316+
if WINDOW == GAME_BOARD and (EVENT in ('WIN_CLOSE', 'Exit')):
317+
GAME_BOARD.close()
318+
GAME_BOARD = None
319+
INIT_WINDOW = init_game_window()
320+
321+
if START_GAME:
322+
323+
if EVENT not in ('-START-', 'WIN_CLOSE'):
324+
CURRENT_MARKER = GAME_BOARD.Element(EVENT).get_text()
325+
GAME_BOARD.Element(EVENT).update(PLAYER1_MARKER if CURRENT_MARKER == ' ' and\
326+
PLAYER_SWITCH is True else PLAYER2_MARKER if CURRENT_MARKER == ' ' and\
327+
PLAYER_SWITCH is False else PLAYER1_MARKER if CURRENT_MARKER == PLAYER1_MARKER
328+
else PLAYER2_MARKER if CURRENT_MARKER == PLAYER2_MARKER else ' ')
329+
330+
# Change the color of the player text to mark
331+
# the next player's turn. 'DarkBlue indicates
332+
# the player who should make the next move.'
333+
# Additionally, Once the player has made a move,
334+
# disable the button.
335+
if GAME_BOARD.Element(EVENT).get_text() == PLAYER1_MARKER:
336+
# increase the step counter.
337+
# The minimum number of steps required to win the game is 5
338+
STEP_COUNTER += 1
339+
PLAYER_SWITCH = False
340+
341+
GAME_BOARD.Element('-P1-').update(text_color='white')
342+
GAME_BOARD.Element('-P2-').update(text_color='darkblue')
343+
344+
if PLAYER1_MARKER == 'X':
345+
GAME_BOARD.Element(EVENT).update(image_filename=X_IMAGE)
346+
else:
347+
GAME_BOARD.Element(EVENT).update(image_filename=O_IMAGE)
348+
349+
GAME_BOARD.Element(EVENT).update(disabled=True)
350+
351+
CONTINUE_WITH_NEXT_GAME = progress_game(EVENT, PLAYER1_MARKER)
352+
if CONTINUE_WITH_NEXT_GAME == 'Yes':
353+
GAME_BOARD.Close()
354+
reset_game_board()
355+
elif CONTINUE_WITH_NEXT_GAME == 'No':
356+
GAME_BOARD.Close()
357+
INIT_WINDOW = init_game_window()
358+
359+
elif GAME_BOARD.Element(EVENT).get_text() == PLAYER2_MARKER:
360+
# increase the step counter.
361+
# The minimum number of steps required to win the game is 5
362+
STEP_COUNTER += 1
363+
PLAYER_SWITCH = True
364+
365+
GAME_BOARD.Element('-P1-').update(text_color='darkblue')
366+
GAME_BOARD.Element('-P2-').update(text_color='white')
367+
368+
if PLAYER2_MARKER == 'X':
369+
GAME_BOARD.Element(EVENT).update(image_filename=X_IMAGE)
370+
else:
371+
GAME_BOARD.Element(EVENT).update(image_filename=O_IMAGE)
372+
373+
GAME_BOARD.Element(EVENT).update(disabled=True)
374+
375+
CONTINUE_WITH_NEXT_GAME = progress_game(EVENT, PLAYER2_MARKER)
376+
if CONTINUE_WITH_NEXT_GAME == 'Yes':
377+
GAME_BOARD.Close()
378+
reset_game_board()
379+
elif CONTINUE_WITH_NEXT_GAME == 'No':
380+
GAME_BOARD.Close()
381+
INIT_WINDOW = init_game_window()
382+
383+
# The minimum number of steps required
384+
# to win the game board is 5.
385+
if STEP_COUNTER == 4:
386+
CHECK_FOR_WINNER = True

0 commit comments

Comments
 (0)