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, moves): for move in moves: from_idx, to_idx = move.split("/") board[int(from_idx)] -= int(player) board[int(to_idx)] += int(player) return 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): 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("Dice permuts:",dice_permutations) for roll in dice_permutations: # Calculate boards resulting from first move #print("initial board: ", board) #print("roll:", roll) boards = calc_moves(board, 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)))