Skip to content

Instantly share code, notes, and snippets.

@quadrupleslap
Last active June 25, 2018 14:53
Show Gist options
  • Save quadrupleslap/668be519b608df4ac575ed4e31ed0756 to your computer and use it in GitHub Desktop.
Save quadrupleslap/668be519b608df4ac575ed4e31ed0756 to your computer and use it in GitHub Desktop.
Command-line noughts and crosses.
import random
BLANK = 0
P1 = 1
P2 = 2
TRANSLATE = { BLANK: '.', P1: 'O', P2: 'X' }
def main():
'''Play noughts and crosses!'''
# Configure the players.
p1_human = yesno('Player 1 human?', True)
p2_human = yesno('Player 2 human?', False)
print()
# Print some introduction text.
print('The match begins!')
print()
# Make a completely blank grid.
grid = [[BLANK] * 3 for _ in range(3)]
# Play the game.
while True:
play(grid, P1, p1_human)
winner = end(grid)
if winner is not None: break
play(grid, P2, p2_human)
winner = end(grid)
if winner is not None: break
# Print the final grid.
print_grid(grid)
# Announce the winner.
if winner == P1:
print('Player 1 wins!')
elif winner == P2:
print('Player 2 wins!')
else:
print('It\'s a draw!')
def print_grid(grid):
'''Display the grid to the user.'''
for row in grid:
print(' '.join(TRANSLATE[c] for c in row))
print()
def play(grid, player, is_human):
'''Make a move.'''
if is_human : human(grid, player)
else : cpu(grid, player)
def cpu(grid, player):
'''Make a move that leads to the best worst-case scenario.'''
# Research 'minimax' for more information on this algorithm.
best_moves = []
best_worst_case = float('-inf')
for y in range(3):
for x in range(3):
if grid[y][x] == BLANK:
grid[y][x] = player
wc = worst_case(grid, player)
if wc > best_worst_case:
best_moves = [(x, y)]
best_worst_case = wc
elif wc == best_worst_case:
best_moves.append((x, y))
grid[y][x] = BLANK
bx, by = random.choice(best_moves)
grid[by][bx] = player
def worst_case(grid, target, player=None):
'''Get the worst-case scenario of the game, given its current state.'''
# Initially, we assume that the target has just moved.
if player is None:
player = opponent(target)
# If the game is over, there is only one case!
winner = end(grid)
if winner is not None:
if winner == target : return 2
elif winner == BLANK : return 1
else : return 0
# If it's our (the target's) turn, we can choose the best move.
# Otherwise, assume that the opponent makes the best move for them, which,
# because we only have two players, is also the worst move for us.
if player == target : best, fun = float('-inf'), max
else : best, fun = float('inf'), min
for y in range(3):
for x in range(3):
if grid[y][x] == BLANK:
grid[y][x] = player
best = fun(best, worst_case(grid, target, opponent(player)))
grid[y][x] = BLANK
# A small optimization to return early at extrema.
if (best == 2 and fun is max) or (best == 0 and fun is min):
return best
return best
def human(grid, player):
'''Ask a human player to make a move.'''
# Create the key map and print the grid.
n = 0
keymap = {}
for y, row in enumerate(grid):
out = []
for x, cell in enumerate(row):
if cell == BLANK:
n += 1
keymap[str(n)] = (x, y)
# The weird codes are for color.
# Research 'ANSI color codes' for more information.
out.append('\033[91m{}\033[0m'.format(n))
else:
out.append(TRANSLATE[cell])
print(' '.join(out))
print()
# Select a message to show to the player.
message = random.choice([
'Player {}, it\'s your turn.',
'Player {}, make your move!',
'Player {}, what will you do?',
'Good luck, player {}.',
'Let\'s see what you got, player {}.'
] if len(keymap) > 1 else [
'This match is just about over, player {}.',
'This has been an interesting match, player {}!'
]).format('1' if player == P1 else '2')
# Ask the player for their choice of move, and make that move.
while True:
move = input('{} [1-{}] '.format(message, n))
if move in keymap:
x, y = keymap[move]
grid[y][x] = player
break
print()
def end(grid):
'''Returns who won, None if it's not over, and BLANK if it's a draw.'''
for row in grid:
if row[0] != BLANK and row[0] == row[1] == row[2]:
return row[0]
for col in zip(*grid):
if col[0] != BLANK and col[0] == col[1] == col[2]:
return col[0]
if grid[0][0] != BLANK and grid[0][0] == grid[1][1] == grid[2][2]:
return grid[0][0]
if grid[0][2] != BLANK and grid[0][2] == grid[1][1] == grid[2][0]:
return grid[0][2]
return None if any(BLANK in row for row in grid) else BLANK
def opponent(player):
'''The other player.'''
return 3 - player
def yesno(q, default=False):
'''Pose to the user a yes-or-no question.'''
while True:
i = input('{} [{}] '.format(q, 'Yn' if default else 'yN')).lower()
if i == '' : return default
elif 'yes'.startswith(i) : return True
elif 'no'.startswith(i) : return False
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment