106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
from functools import lru_cache
|
|
from collections import namedtuple
|
|
|
|
|
|
def get_sudoku_moves():
|
|
PLAYER_1 = "X"
|
|
PLAYER_2 = "O"
|
|
EMPTY = "_"
|
|
Move = namedtuple("Move", ["ps", "n"])
|
|
Position = namedtuple("Position", ["t", "i"])
|
|
|
|
def all_rows(field):
|
|
""" Returns all rows of a Sudoku field. """
|
|
f = field
|
|
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 e in row:
|
|
if e != value:
|
|
return False
|
|
return True
|
|
|
|
def player_won(field, player):
|
|
for row in all_rows(field):
|
|
if all_equal_to(row, player):
|
|
return True
|
|
return False
|
|
|
|
def player_lost(field, player):
|
|
other_player = PLAYER_2 if player == PLAYER_1 else PLAYER_1
|
|
return player_won(field, other_player)
|
|
|
|
def game_over(field):
|
|
for f in field:
|
|
if f == EMPTY:
|
|
return False
|
|
return True
|
|
|
|
def possible_moves(field):
|
|
return [i for i in range(len(field)) if field[i] == EMPTY]
|
|
|
|
@lru_cache(maxsize=2**16)
|
|
def get_equity(field, player):
|
|
if player_won(field, player):
|
|
return 1
|
|
elif player_lost(field, player):
|
|
return -1
|
|
elif game_over(field):
|
|
return 0
|
|
|
|
other_player = PLAYER_2 if player == PLAYER_1 else PLAYER_1
|
|
equities = []
|
|
for possible_move in possible_moves(field):
|
|
new_field = list(field)
|
|
new_field[possible_move] = player
|
|
new_field = tuple(new_field)
|
|
equity = get_equity(new_field, other_player)
|
|
equities.append(equity)
|
|
return min(equities) * -1
|
|
|
|
def get_moves(field, turn, moves, moves_acc, bots_turn):
|
|
|
|
if player_won(field, PLAYER_1) or player_won(field, PLAYER_2):
|
|
return
|
|
|
|
if game_over(field):
|
|
return
|
|
|
|
if not bots_turn:
|
|
for move in possible_moves(field):
|
|
new_field = list(field)
|
|
new_field[move] = PLAYER_1
|
|
new_field = tuple(new_field)
|
|
new_moves = Move(moves.ps + [Position(turn, move)], None)
|
|
get_moves(new_field, turn + 1, new_moves, moves_acc, True)
|
|
else:
|
|
equities = []
|
|
for move in possible_moves(field):
|
|
new_field = list(field)
|
|
new_field[move] = PLAYER_2
|
|
new_field = tuple(new_field)
|
|
equity = get_equity(new_field, PLAYER_2)
|
|
equities.append((equity, move))
|
|
move = max(equities)[1]
|
|
new_field = list(field)
|
|
new_field[move] = PLAYER_2
|
|
new_field = tuple(new_field)
|
|
bot_move = Move(moves.ps, Position(turn, move))
|
|
moves_acc.append(bot_move)
|
|
get_moves(new_field, turn + 1, moves, moves_acc, False)
|
|
return moves_acc
|
|
|
|
EMPTY_FIELD = tuple([EMPTY for _ in range(9)])
|
|
return get_moves(EMPTY_FIELD, 0, Move([], None), [], False)
|