Created
July 18, 2020 13:11
-
-
Save tkuriyama/a5819dd3b2490d27b89823c9fd0aa1cc to your computer and use it in GitHub Desktop.
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
# 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