285 lines
10 KiB
Python
285 lines
10 KiB
Python
import quack
|
|
import numpy as np
|
|
import itertools
|
|
|
|
class Board:
|
|
|
|
initial_state = ( 0,
|
|
2, 0, 0, 0, 0, -5,
|
|
0, -3, 0, 0, 0, 5,
|
|
-5, 0, 0, 0, 3, 0,
|
|
5, 0, 0, 0, 0, -2,
|
|
0 )
|
|
|
|
@staticmethod
|
|
def idxs_with_checkers_of_player(board, player):
|
|
return quack.idxs_with_checkers_of_player(board, player)
|
|
|
|
|
|
# TODO: Write a test for this
|
|
# TODO: Make sure that the bars fit, 0 represents the -1 player and 25 represents the 1 player
|
|
# index 26 is player 1 home, index 27 is player -1 home
|
|
@staticmethod
|
|
def board_features_to_pubeval(board, player):
|
|
|
|
if player == -1:
|
|
board = Board.flip(board)
|
|
|
|
board = list(board)
|
|
positives = [x if x > 0 else 0 for x in board]
|
|
negatives = [x if x < 0 else 0 for x in board]
|
|
board.append( 15 - sum(positives))
|
|
board.append(-15 - sum(negatives))
|
|
return tuple(board)
|
|
|
|
# quack
|
|
@staticmethod
|
|
def board_features_quack(board, player):
|
|
board = list(board)
|
|
board += ([1, 0] if np.sign(player) > 0 else [0, 1])
|
|
return np.array(board).reshape(1,28)
|
|
|
|
# quack-fat
|
|
@staticmethod
|
|
def board_features_quack_fat(board, player):
|
|
return np.array(quack.board_features_quack_fat(board,player)).reshape(1,30)
|
|
# board = list(board)
|
|
# positives = [x if x > 0 else 0 for x in board]
|
|
# negatives = [x if x < 0 else 0 for x in board]
|
|
# board.append( 15 - sum(positives))
|
|
# board.append(-15 - sum(negatives))
|
|
# board += ([1, 0] if np.sign(player) > 0 else [0, 1])
|
|
# return np.array(board).reshape(1,30)
|
|
|
|
|
|
# quack-fatter
|
|
@staticmethod
|
|
def board_features_quack_norm(board, player):
|
|
board = list(board)
|
|
positives = [x if x > 0 else 0 for x in board]
|
|
negatives = [x if x < 0 else 0 for x in board]
|
|
board[0] = board[0] / 2
|
|
board[25] = board[25] / 2
|
|
|
|
board = [board[x] if x == 0 or 25 else board[x] / 15 for x in range(0, 26)]
|
|
|
|
board.append(15 - sum(positives))
|
|
board.append(-15 - sum(negatives))
|
|
board += ([1, 0] if np.sign(player) > 0 else [0, 1])
|
|
return np.array(board).reshape(1,30)
|
|
|
|
# tesauro
|
|
@staticmethod
|
|
def board_features_tesauro(board, cur_player):
|
|
def ordinary_trans(val, player):
|
|
abs_val = val * player
|
|
if abs_val <= 0: return (0,0,0,0)
|
|
elif abs_val == 1: return (1,0,0,0)
|
|
elif abs_val == 2: return (1,1,0,0)
|
|
elif abs_val == 3: return (1,1,1,0)
|
|
else: return (1,1,1, (abs_val - 3) / 2)
|
|
|
|
def bar_trans(board, player):
|
|
if player == 1: return (abs(board[0]/2),)
|
|
elif player == -1: return (abs(board[25]/2),)
|
|
|
|
# def ordinary_trans_board(board, player):
|
|
# return np.array(
|
|
# [ordinary_trans(x, player) for x in board[1:25]]
|
|
# ).flatten()
|
|
|
|
board_rep = []
|
|
for player in [1,-1]:
|
|
for x in board[1:25]:
|
|
board_rep += ordinary_trans(x, player)
|
|
board_rep += bar_trans(board, player)
|
|
board_rep += (15 - Board.num_of_checkers_for_player(board, player),)
|
|
|
|
board_rep += ([1,0] if cur_player == 1 else [1,0])
|
|
|
|
return np.array(board_rep).reshape(1,198)
|
|
|
|
|
|
@staticmethod
|
|
def board_features_tesauro_wrong(board, cur_player):
|
|
features = []
|
|
for player in [-1,1]:
|
|
sum = 0.0
|
|
for board_range in range(1,25):
|
|
pin = board[board_range]
|
|
#print("PIIIN:",pin)
|
|
feature = [0.0]*4
|
|
if np.sign(pin) == np.sign(player):
|
|
sum += abs(pin)
|
|
for i in range(min(abs(pin), 3)):
|
|
feature[i] = 1
|
|
if (abs(pin) > 3):
|
|
feature[3] = (abs(pin)-3)/2
|
|
features += feature
|
|
#print("SUUUM:",sum)
|
|
# Append the amount of men on the bar of the current player divided by 2
|
|
features.append((board[0] if np.sign(player) < 0 else board[25]) / 2.0)
|
|
# Calculate how many pieces there must be in the home state and divide it by 15
|
|
features.append((15 - sum) / 15)
|
|
features += ([1,0] if np.sign(cur_player) > 0 else [0,1])
|
|
test = np.array(features)
|
|
#print("TEST:",test)
|
|
return test.reshape(1,198)
|
|
|
|
|
|
|
|
@staticmethod
|
|
def is_move_valid(board, player, face_value, move):
|
|
return quack.is_move_valid(board, player, face_value, move)
|
|
|
|
@staticmethod
|
|
def any_move_valid(board, player, roll):
|
|
for die in roll:
|
|
idxs = Board.idxs_with_checkers_of_player(board, player)
|
|
for idx in idxs:
|
|
if Board.is_move_valid(board, player, die,
|
|
(idx, idx + (die * player))):
|
|
return True
|
|
return False
|
|
|
|
@staticmethod
|
|
def num_of_checkers_for_player(board,player):
|
|
return player * sum([board[idx] for idx in Board.idxs_with_checkers_of_player(board, player)])
|
|
|
|
@staticmethod
|
|
def outcome(board):
|
|
def all_checkers_in_first_quadrant(player):
|
|
checker_idxs = Board.idxs_with_checkers_of_player(board, player)
|
|
if player == 1:
|
|
return all([(idx <= 6) for idx in checker_idxs])
|
|
else:
|
|
return all([(idx >= 19) for idx in checker_idxs])
|
|
winner = None
|
|
|
|
for player in [1, -1]:
|
|
if Board.idxs_with_checkers_of_player(board, player) == []:
|
|
winner = player
|
|
|
|
if winner == None:
|
|
return None
|
|
|
|
#backgammon = all_checkers_in_first_quadrant(-winner)
|
|
gammon = Board.num_of_checkers_for_player(board, -winner) == 15
|
|
score = 2 if gammon else 1
|
|
return {winner: score, -winner: -score}
|
|
|
|
|
|
@staticmethod
|
|
def apply_moves_to_board(board, player, move):
|
|
from_idx = move[0]
|
|
to_idx = move[1]
|
|
board = list(board)
|
|
board[from_idx] -= player
|
|
|
|
if (to_idx < 1 or to_idx > 24):
|
|
return
|
|
|
|
if (board[to_idx] * player == -1):
|
|
|
|
if (player == 1):
|
|
board[25] -= player
|
|
else:
|
|
board[0] -= player
|
|
|
|
board[to_idx] = 0
|
|
|
|
board[to_idx] += player
|
|
|
|
return tuple(board)
|
|
|
|
@staticmethod
|
|
def calculate_legal_states(board, player, roll):
|
|
# Find all points with checkers on them belonging to the player
|
|
# Iterate through each index and check if it's a possible move given the roll
|
|
|
|
# TODO: make sure that it is not possible to do nothing on first part of
|
|
# turn and then do something with the second die
|
|
|
|
def calc_moves(board, face_value):
|
|
if face_value == 0:
|
|
return [board]
|
|
return quack.calc_moves(board, player, face_value)
|
|
|
|
# Problem with cal_moves: Method can return empty list (should always contain at least same board).
|
|
# *Update*: Seems to be fixed.
|
|
|
|
# ------------------
|
|
# 1. Determine if dice have identical face value
|
|
# 2. Iterate through remaining dice
|
|
|
|
legal_moves = set()
|
|
|
|
if not Board.any_move_valid(board, player, roll):
|
|
return { board }
|
|
dice_permutations = list(itertools.permutations(roll)) if roll[0] != roll[1] else [[roll[0]]*4]
|
|
#print("Permuts:",dice_permutations)
|
|
# print("Dice permuts:",dice_permutations)
|
|
for roll in dice_permutations:
|
|
# Calculate boards resulting from first move
|
|
#print("initial board: ", board)
|
|
#print("roll:", roll)
|
|
#print("Rest of roll:",roll[1:])
|
|
boards = calc_moves(board, roll[0])
|
|
#print("Boards:",boards)
|
|
#print("Roll:",roll[0])
|
|
#print("boards after first die: ", boards)
|
|
|
|
for die in roll[1:]:
|
|
# Calculate boards resulting from second move
|
|
nested_boards = [calc_moves(board, die) for board in boards]
|
|
#print("nested boards: ", nested_boards)
|
|
boards = [board for boards in nested_boards for board in boards]
|
|
# What the fuck
|
|
#for board in boards:
|
|
# print(board)
|
|
# print("type__:",type(board))
|
|
# Add resulting unique boards to set of legal boards resulting from roll
|
|
|
|
#print("printing boards from calculate_legal_states: ", boards)
|
|
legal_moves = legal_moves | set(boards)
|
|
# print("legal moves: ", legal_moves)
|
|
if len(legal_moves) == 0:
|
|
legal_moves = { tuple(board) }
|
|
|
|
return legal_moves
|
|
|
|
@staticmethod
|
|
def pretty(board):
|
|
def black(count):
|
|
return "\033[0;30m\033[47m{}\033[0m\033[47m".format(count)
|
|
def white(count):
|
|
return "\033[0;31m\033[47m{}\033[0m\033[47m".format(count)
|
|
|
|
temp = []
|
|
for x in board:
|
|
if x > 0:
|
|
temp.append(" {}".format(x))
|
|
elif x < 0:
|
|
temp.append("{}".format(x))
|
|
else: temp.append(" ")
|
|
|
|
return """
|
|
13 14 15 16 17 18 19 20 21 22 23 24
|
|
+--------------------------------------------------------------------------+
|
|
| {13}| {14}| {15}| {16}| {17}| {18}| bar -1: {25} | {19}| {20}| {21}| {22}| {23}| {24}| end 1: TODO|
|
|
|---|---|---|---|---|---|------------|---|---|---|---|---|---| |
|
|
| {12}| {11}| {10}| {9}| {8}| {7}| bar 1: {0} | {6}| {5}| {4}| {3}| {2}| {1}| end -1: TODO|
|
|
+--------------------------------------------------------------------------+
|
|
12 11 10 9 8 7 6 5 4 3 2 1
|
|
""".format(*temp)
|
|
|
|
@staticmethod
|
|
def do_move(board, player, move):
|
|
# Implies that move is valid; make sure to check move validity before calling do_move(...)
|
|
return quack.do_move(board, player, move)
|
|
|
|
|
|
@staticmethod
|
|
def flip(board):
|
|
return tuple((-x for x in reversed(board)))
|