Skip to content

Instantly share code, notes, and snippets.

@tkuriyama
Created July 18, 2020 13:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tkuriyama/a5819dd3b2490d27b89823c9fd0aa1cc to your computer and use it in GitHub Desktop.
Save tkuriyama/a5819dd3b2490d27b89823c9fd0aa1cc to your computer and use it in GitHub Desktop.
# Helpers
def transpose(lst):
"""Transpose list of lists, returning list of lists."""
return [list(row) for row in zip(*lst)]
def diagonals(lst):
"""Return both diagonals of given square list of lists."""
rev_lst = [row[::-1] for row in lst]
return [[lst[i][i] for i in range(len(lst))],
[rev_lst[i][i] for i in range(len(lst))]]
# Board Mgmt
def annotate(board):
"""Add indices to string of board, alpha for cols and nums for rows.
Returns annotated board, list of col indices, list of row indices
"""
size = len(board)
col_ix = [chr(97 + i) for i in range(size)]
row_ix = [str(i) for i in range(size)]
header = [[' ', '|'] + col_ix, ['-'] * (size + 2)]
new_rows = transpose([row_ix, ['|'] * size] + transpose(board))
return header + new_rows, col_ix, row_ix
def valid_moves(board):
"""Return list of string indices of free locations."""
_, col_ix, row_ix = annotate(board)
ixs = [r + c for r in row_ix for c in col_ix]
rows = [v for row in board for v in row]
return [ix for (ix, v) in zip(ixs, rows) if v == '.']
def show(board, status, turn, step_ct, p1, p2):
"""Return string representation of game state."""
s = '\nStatus: {}; Turn: {}; Step: {} | P1 (x): {}; P2 (o): {}\n\n'.\
format(status, turn, step_ct, p1, p2)
show_board, col_ix, row_ix = annotate(board)
s += '\n'.join(' '.join(row) for row in show_board)
s += '\n'
# instructions for human players
human_move = (turn == 'P1' and p1.lower() == 'human' or
turn == 'P2' and p2.lower() == 'human')
if status == 'TBD' and human_move:
s += '\nValid Moves: {}'.format(valid_moves(board))
s += '\nPress q to quit'
s += '\nEnter your move:'
return s
# Game Algos
def engine_human(board, status, turn, v):
"""Game algo: human input."""
valid_mvs = valid_moves(board)
awaiting_mv = True
while awaiting_mv:
mv = input().lower()
if mv == 'q':
next_board = board
next_status = '{} Quits'.format(turn)
awaiting_mv = False
elif mv in valid_mvs:
next_board = apply_mv(board, mv, v)
next_status = eval_status(board)
awaiting_mv = False
else:
print('\nInvalid input... choose a valid move or q.\n')
return next_board, next_status
# Gameplay
def apply_mv(board, mv, v):
"""Mutate board by adding v to location specified by mv index string."""
_, col_ix, row_ix = annotate(board)
r = row_ix.index(mv[0])
c = col_ix.index(mv[1])
board[r][c] = v
return board
def is_win(board, ref_v):
"""Return True if board shows win for given reference value."""
ds, cs, rs = diagonals(board), transpose(board), board
return any(all(v == ref_v for v in vs)
for vs in (ds + cs + rs))
def eval_status(board):
"""Evaluate status of game, possiblities P1/P2, Draw Wins, TBD."""
return ('P1 Wins' if is_win(board, 'x') else
'P2 Wins' if is_win(board, 'o') else
'Draw' if valid_moves(board) == [] else
'TBD')
def step(board, status, turn, step_ct, p1, p2):
"""Advance game state by one step, returning new game state."""
print(show(board, status, turn, step_ct, p1, p2))
if status != 'TBD':
next_board, next_status = board, status
else:
algos = {'human': engine_human}
# assert p1.lower() in algo and p2.lower() in algo
f = algos[p1.lower() if turn == 'P1' else p2.lower()]
v = 'x' if turn == 'P1' else 'o'
next_board, next_status = f(board, status, turn, v)
next_turn = 'P2' if turn == 'P1' else 'P1'
return next_board, next_status, next_turn
def play(board, turn, p1, p2):
"""Main game loop."""
assert all(v in ('.', 'x', 'o') for row in board for v in row)
assert turn in ('P1', 'P2')
status = 'TBD'
i = 0
while status == 'TBD':
board, status, turn = step(board, status, turn, i, p1, p2)
i += 1
print(show(board, status, turn, i, p1, p2))
print('\n\nGame over. {}.\n\n'.format(status))
return board, status, i
# Main
def main(size=3):
"""Start game with default settings.."""
board = [['.'] * size for _ in range(size)]
play(board, 'P1', 'Human', 'Human')
if __name__ == '__main__':
main()
# Tests
def run_tests():
"""Run all unit tests."""
print('Valid Moves: {}'.format(test_valid_moves()))
print('Status eval (Wins): {}'.format(test_eval_wins()))
print('Status eval (Draws): {}'.format(test_eval_draw()))
print('Status eval (TBD): {}'.format(test_eval_tbd()))
def test_valid_moves():
"""Test valid moves."""
b = [['x','0','x'],
['.','.','.'],
['x','.','o']]
mvs = ['1a', '1b', '1c', '2b']
return valid_moves(b) == mvs
def test_eval_wins():
"""Test eval_status for wins."""
boards1 = [[['x',''],
['o','x']],
[['o','.','x'],
['o','x','x'],
['x','.','o']]]
boards2 = [[['o','o'],
['x','']],
[['o','.','x'],
['o','x','x'],
['o','.','o']]]
return (all(eval_status(b) == 'P1 Wins' for b in boards1) and
all(eval_status(b) == 'P2 Wins' for b in boards2))
def test_eval_draw():
"""Test eval_status for draw."""
boards = [[['o','x','x'],
['x','o','o'],
['o','x','x']]]
return all(eval_status(b) == 'Draw' for b in boards)
def test_eval_tbd():
"""Test eval_status for TBD."""
boards = [[['.','.'],
['.','.']],
[['.','.','x'],
['x','.','x'],
['.','x','.']]]
return all(eval_status(b) == 'TBD' for b in boards)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment