Skip to content

Instantly share code, notes, and snippets.

@morganmcg1
Last active December 27, 2019 21:43
Show Gist options
  • Save morganmcg1/bde2a0eae8a147ae65345cbd18ffed88 to your computer and use it in GitHub Desktop.
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
#!/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