Alexander Munch-Hansen
926a331df0
again and it is possible to play against the ai. There is no flag for this yet, so this has to be added.
287 lines
10 KiB
Python
287 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:]:
|
|
# if die != 0:
|
|
if True:
|
|
# 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)))
|