121 lines
4.0 KiB
Python
121 lines
4.0 KiB
Python
from functools import lru_cache
|
|
from collections import namedtuple
|
|
|
|
|
|
def get_tictactoe_rules():
|
|
PLAYER_1 = "X"
|
|
PLAYER_2 = "O"
|
|
UNCHECKED = "_"
|
|
Rule = namedtuple("Rule", ["moves", "next_move"])
|
|
Move = namedtuple("Move", ["turn", "field"])
|
|
|
|
def all_rows(board):
|
|
""" Returns all rows of a Sudoku field. """
|
|
f = board
|
|
return [
|
|
f[0:3], f[3:6], f[6:9], # horizontals
|
|
f[0:7:3], f[1:8:3], f[2:9:3], # verticals
|
|
f[0:9:4], f[2:7:2] # diagonals
|
|
]
|
|
|
|
assert(all_rows(list(range(9))) == [
|
|
[0, 1, 2], [3, 4, 5], [6, 7, 8],
|
|
[0, 3, 6], [1, 4, 7], [2, 5, 8],
|
|
[0, 4, 8], [2, 4, 6]])
|
|
|
|
def all_equal_to(row, value):
|
|
""" Returns True if all elements in row are
|
|
equal to value. Returns False otherwise. """
|
|
for elem in row:
|
|
if elem != value:
|
|
return False
|
|
return True
|
|
|
|
def player_won(board, player):
|
|
for row in all_rows(board):
|
|
if all_equal_to(row, player):
|
|
return True
|
|
return False
|
|
|
|
def player_lost(board, player):
|
|
other_player = PLAYER_2 if player == PLAYER_1 else PLAYER_1
|
|
return player_won(board, other_player)
|
|
|
|
def game_over(board):
|
|
for field in board:
|
|
if field == UNCHECKED:
|
|
return False
|
|
return True
|
|
|
|
def get_unchecked_fields(board):
|
|
return [field_index for field_index in range(len(board))
|
|
if board[field_index] == UNCHECKED]
|
|
|
|
def get_new_board(board, field_index, player):
|
|
new_board = list(board)
|
|
new_board[field_index] = player
|
|
return tuple(new_board)
|
|
|
|
def get_empty_board():
|
|
return tuple([UNCHECKED for _ in range(9)])
|
|
|
|
@lru_cache(maxsize=2**16)
|
|
def get_equity(board, player):
|
|
if player_won(board, player):
|
|
return 1
|
|
elif player_lost(board, player):
|
|
return -1
|
|
elif game_over(board):
|
|
return 0
|
|
|
|
other_player = PLAYER_2 if player == PLAYER_1 else PLAYER_1
|
|
equities = []
|
|
for field_index in get_unchecked_fields(board):
|
|
new_board = get_new_board(board, field_index, player)
|
|
equity = get_equity(new_board, other_player)
|
|
equities.append(equity)
|
|
return min(equities) * -1
|
|
|
|
bot_move_rules = []
|
|
draw_rules = []
|
|
win_rules = []
|
|
|
|
def get_rules(current_board, turn, current_rule, bots_turn):
|
|
|
|
if player_won(current_board, PLAYER_1):
|
|
raise Exception("If we got here our bot screwed up.")
|
|
|
|
if player_won(current_board, PLAYER_2):
|
|
rule = Rule(current_rule.moves, Move(turn, None))
|
|
win_rules.append(rule)
|
|
return
|
|
|
|
if game_over(current_board):
|
|
rule = Rule(current_rule.moves, Move(turn, None))
|
|
draw_rules.append(rule)
|
|
return
|
|
|
|
if not bots_turn:
|
|
for field_index in get_unchecked_fields(current_board):
|
|
new_board = get_new_board(current_board, field_index, PLAYER_1)
|
|
new_moves = current_rule.moves + [Move(turn, field_index)]
|
|
new_current_rule = Rule(new_moves, None)
|
|
get_rules(new_board, turn + 1, new_current_rule, True)
|
|
else:
|
|
equities = []
|
|
for field_index in get_unchecked_fields(current_board):
|
|
new_board = get_new_board(current_board, field_index, PLAYER_2)
|
|
equity = get_equity(new_board, PLAYER_1)
|
|
equities.append((equity, field_index))
|
|
new_index = min(equities)[1]
|
|
new_board = get_new_board(current_board, new_index, PLAYER_2)
|
|
|
|
bot_rule = Rule(current_rule.moves, Move(turn, new_index))
|
|
bot_move_rules.append(bot_rule)
|
|
get_rules(new_board, turn + 1, current_rule, False)
|
|
|
|
EMPTY_BOARD = get_empty_board()
|
|
get_rules(EMPTY_BOARD, 0, Rule([], None), False)
|
|
|
|
return (bot_move_rules, draw_rules, win_rules)
|