Last active
December 27, 2019 21:43
-
-
Save morganmcg1/bde2a0eae8a147ae65345cbd18ffed88 to your computer and use it in GitHub Desktop.
Two player python game of Tic Tac Toe to play in the terminal
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
""" | |
TIC-TAC-TOE | |
A basic two-player game of tic-tac-toe | |
Written by Morgan McGuire | |
www.github.com/morganmcg1 | |
""" | |
# Set intial constants | |
P1_M = 'X' # Marker for Player 1 | |
P2_M = 'O' # Marker for Player 2 | |
SIZE = 3 # Number of rows and columns | |
STALEMATE = 'stalemate' # Stalemate flag | |
# Error Handling | |
class Error(Exception): | |
""" Base class for other exceptions """ | |
pass | |
class PositionNotAvailableError(Error): | |
""" Throw if input position is already taken """ | |
pass | |
class InputNotIntegerError(Error): | |
""" Throw if input is not an integer """ | |
pass | |
# Main Program | |
def setup_board(SIZE): | |
""" Setup the board at the start of the game """ | |
board = [] | |
for r in range(0, SIZE): | |
board.append([(SIZE*r)+c for c in range(1,SIZE+1)]) | |
return board | |
def print_welcome(): | |
""" Welcome text for the game """ | |
print('\n' + ' ' + '-'* 25) | |
print('|' + (' ' * 7) + 'TIC TAC TOE' + (' ' * 7) + '|') | |
print(' ' + '-'* 25 + '\n') | |
print(f'Ready to play tic-tac-toe? Lets Goooo!\n') | |
print('Type "q" at any time to quit\n') | |
def print_ask_inp(name): | |
""" Dialogue asking for the players' input """ | |
m = name_to_mark(name) | |
print(f'{name}, please enter a position to place your "{m}"') | |
def name_to_mark(player): | |
""" Translate the players' name to the marker on the board """ | |
if player == 'Player_1': m = P1_M | |
else: m = P2_M | |
return m | |
def print_board(board): | |
""" Print out the board """ | |
padding = (' ' * 16) | |
cap = padding +'------------' | |
print(cap) | |
for i in range(0, len(board)): | |
tmp_row = padding + '| ' | |
for j in range(0, len(board[i])): | |
tmp_row += str(board[i][j]) | |
tmp_row += ' ' | |
tmp_row += '|' | |
print(tmp_row) | |
print(cap + '\n') | |
def translate_pos(inp_pos): | |
""" | |
Translate the input digit into (row, column) coordinates | |
on the board | |
""" | |
step = 1 / SIZE | |
int_div = inp_pos // SIZE | |
remainder = (inp_pos / SIZE) - int_div | |
if remainder > 0.01: | |
row = int_div + 1 | |
col = int(round(remainder / step)) | |
else: | |
row = int_div | |
col = SIZE | |
return row, col | |
def check_inp_available(inp_pos, board, row, col): | |
""" Check that the select position is available """ | |
if board[row-1][col-1] == inp_pos: return True | |
else: return False | |
def update_positions(board, row, col, m): | |
""" Add the players marker (X or O) to the board """ | |
board[row-1][col-1] = m | |
return board | |
def get_human_input(player, board): | |
""" | |
Asks for a player's input, checks the input is a digit, | |
checks if the position is unoccupied and translates input | |
digit into (row,column) coordinate | |
""" | |
ATTEMPT_LIMIT = 3 | |
for attempt in range(ATTEMPT_LIMIT): | |
try: | |
if attempt > 0: print(f'ATTEMPT {attempt+1}/{ATTEMPT_LIMIT}:') | |
inp_pos = input() | |
print() | |
if inp_pos == 'q': | |
return board, inp_pos | |
elif not inp_pos.isdigit(): | |
raise InputNotIntegerError | |
else: | |
inp_pos = int(inp_pos) | |
row, col = translate_pos(inp_pos) | |
m = name_to_mark(player) | |
if check_inp_available(inp_pos, board, row, col): | |
board = update_positions(board, row, col, m) | |
return board, inp_pos | |
else: | |
raise PositionNotAvailableError | |
except PositionNotAvailableError: | |
print(f'Oops, position {inp_pos} is not available, please try another value, this is what the board currently looks like:\n') | |
print_board(board) | |
except InputNotIntegerError: | |
print(f'Oops, {inp_pos} is not a number, please enter a number from 0-9 or type "q" to quit\n') | |
else: | |
print(f'{player} you KEEP entering an unavailable position :( Skipping your turn \n') | |
return board, inp_pos | |
def check_winner(m, board, r,c, direction): | |
""" | |
Check if there has been a winner along either the rows, columns or diagonals | |
""" | |
row_offset = 0 | |
col_offset = 0 | |
game_over = False | |
mark_count = 1 | |
if direction == 'row': | |
col_offset = 1 | |
elif direction == 'col': | |
row_offset = 1 | |
elif direction == 'left_to_right_diagonal': | |
col_offset = 1 | |
row_offset = 1 | |
elif direction == 'right_to_left_diagonal': | |
col_offset = -1 | |
row_offset = 1 | |
row_search = r+row_offset | |
col_search = c+col_offset | |
# Check in given direction for winning row | |
if row_search < SIZE and col_search < SIZE and board[row_search][col_search] == m: | |
mark_count += 1 | |
# If 2 marks in a row found, drive in that direction to check for winner | |
for i in range(2, SIZE): | |
row_drive = row_search+(int(row_offset/1)*(i-1)) | |
col_drive = col_search+(int(col_offset/1)*(i-1)) | |
if row_drive < SIZE and col_drive < SIZE and board[row_drive][col_drive] == m: | |
mark_count += 1 | |
if mark_count == SIZE: | |
game_over = True | |
return game_over | |
return game_over | |
def check_game_over(player, m, board, r, c, direction, winner): | |
""" | |
Check if there is a winner and print the direction of | |
the winning markers | |
""" | |
game_over = check_winner(m, board, r, c, direction) | |
if game_over == True: | |
print(f'{player} has a winning {direction}!!') | |
winner = player | |
return board, game_over, winner | |
def check_stalemate(board, game_over): | |
""" Check whether there is stalemate """ | |
flat_board = [item for sublist in board for item in sublist] | |
available_pos = [pos for pos in flat_board if isinstance(pos, int)] | |
if game_over == False and len(available_pos) == 0: | |
game_over = True | |
winner = STALEMATE | |
return board, game_over, winner | |
else: | |
winner = '' | |
return board, game_over, winner | |
def evaluate(player, board): | |
""" | |
Check whether the game is over via either a winner or a stalemate | |
""" | |
winner = '' | |
m = name_to_mark(player) | |
game_over = False | |
# Check rows | |
for d in ('row', 'col'): | |
direction = d | |
for j in range(SIZE): | |
if d == 'row': | |
c=0 | |
r=j | |
elif d == 'col': | |
c=j | |
r=0 | |
if board[r][c] == m: | |
board, game_over, winner = check_game_over(player, m, board, r, c, direction, winner) | |
if game_over: return board, game_over, winner | |
if c == 0 and r == 0: | |
direction = 'left_to_right_diagonal' | |
board, game_over, winner = check_game_over(player, m, board, r, c, direction, winner) | |
if game_over: return board, game_over, winner | |
if c == SIZE-1 and r == 0: | |
direction = 'right_to_left_diagonal' | |
board, game_over, winner = check_game_over(player, m, board, r, c, direction, winner) | |
if game_over: return board, game_over, winner | |
board, game_over, winner = check_stalemate(board, game_over) | |
return board, game_over, winner | |
# Run the game | |
def run_game(): | |
""" Control flow for the game """ | |
board = setup_board(SIZE) | |
print_welcome() | |
for i in range(0, SIZE**2): | |
print('-' * 50) | |
if i % 2 == 0: player = 'Player_1' | |
else: player = 'Player_2' | |
if i == 0: print_board(board) | |
print_ask_inp(player) | |
board, inp_pos = get_human_input(player, board) | |
if inp_pos == 'q': | |
print('QUITTING GAME...BYE!\n') | |
return | |
board, game_over, winner = evaluate(player, board) | |
print_board(board) | |
if game_over == True and winner != STALEMATE: | |
print(f'GAME OVER: {player} is the winner, congrats!!!\n') | |
return | |
if game_over == True and winner == STALEMATE: | |
print('STALEMATE, GAME OVER!\n') | |
return | |
run_game() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment