diff --git a/board.py b/board.py index 3e6e15d..12f5128 100644 --- a/board.py +++ b/board.py @@ -1,24 +1,19 @@ -from cup import Cup 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 ] + # TODO: Remember to handle pushing other pieces to home # TODO: Also remember that a player can't move backwards and the one # player goes from 1-47 while the other goes from 47-1 - def __init__(self): - self.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, 0, 0 ] - - - def get_state(self): - return self.state - # Remember to handle edge case when we're on the last moves and you may go # from position 22 -> 24 on a 6, if you have no pieces behind 22. Simply # check if any are behind if you're circle or if any are higher if you are @@ -35,64 +30,97 @@ class Board: # TODO: handle barring in a more elengant way # TODO: allow bearing off with non-exact die roll if this is only possible # move - def check_move(self, move, player, roll): - from_idx = int(move[0]) - to_idx = int(move[1]) - to_state = self.state[to_idx] - from_state = self.state[from_idx] + # TODO: Allow not doing anything if and only if no alternatives + @staticmethod + def is_move_valid(board, player, face_value, move): + board = list(board) + from_idx = move[0] + to_idx = move[1] + to_state = board[to_idx] + from_state = board[from_idx] if from_idx == 26: from_idx = 1 - roll -= 1 + face_value -= 1 elif from_idx == 27: from_idx = 24 - roll -= 1 + face_value -= 1 if not (1 <= from_idx <= 24 and - 1 <= to_idx <= 24): + 1 <= to_idx <= 24): return False - elif ( abs( from_idx - to_idx ) != roll ): + elif ( abs( from_idx - to_idx ) != face_value ): return False elif (from_state * player >= 1 and # Is moving player's own checker? (to_state * player >= 0 or # Is 'to' empty or has player's own checkers? to_state * -player == 1)): # Can opponent checker be hit? return True - - def find_pieces_for_player(self, player): - idxs = [] - for idx, pip in enumerate(self.state): - if pip * player >= 1: - idxs.append(idx) - return idxs - - def is_winner(self, player): + + @staticmethod + def is_winner(board, player): for i in range(1,25): - if player * self.state[i] > 0: + if player * board[i] > 0: return False return True - - def find_legal_moves(self, player, roll): + @staticmethod + def calculate_legal_states(board, player, roll): # Find all pips with things on them belonging to the player # Iterate through each index and check if it's a possible move given the roll # If player is O, then check for idx + roll # If player is X, then check for idx - roll - - # Rewrite this, it's shit. - idxs_with_thing = self.find_pieces_for_player(player) - legal_moves = [] + print("Find legal moves: ",roll,"-"*20) - for index in idxs_with_thing: - from_idx = index - to_idx = index+(roll*player) - if self.check_move([from_idx,to_idx], player, roll): - legal_moves.append([from_idx,to_idx]) + def idxs_with_checkers_of_current_player(board): + idxs = [] + for idx, checker_count in enumerate(board): + if checker_count * player >= 1: + idxs.append(idx) + return idxs + + def calc_moves(board, face_value): + idxs_with_checkers = idxs_with_checkers_of_current_player(board) + boards = [(do_move(board, + player, + (idx, idx + (face_value * player))) if + is_move_valid(board, + player, + face_value, + (idx, idx + (face_value * player))) else None) + for idx + in idxs_with_checkers] + + # If no move can be made, make sure to include current board + if is_move_valid(board, player, face_value, None): + boards.append(board) + + return list(filter(None, boards)) # Remove None-values + + # ------------------ + # 1. Determine if dice have identical face value + # 2. Iterate through remaining dice + + legal_moves = set() + + dice_permutations = list(itertools.permutations(roll)) if roll[0] != roll[1] else [[roll[0]]*4] + + for pair in dice_permutations: + # Calculate boards resulting from first move + boards = calc_moves(board, pair[0]) + + for x in dice_permutations[1:None]: + # Calculate boards resulting from second move + nested_boards = [calc_moves(board, x) for board in boards] + boards = [board for boards in nested_boards for board in boards] + # Add resulting unique boards to set of legal boards resulting from roll + legal_moves = legal_moves | set(boards) return legal_moves - - def pretty(self): + + @staticmethod + def pretty(board): temp = [] - for x in self.state: + for x in board: if x >= 0: temp.append(" {}".format(x)) else: @@ -101,28 +129,46 @@ class Board: return """ 13 14 15 16 17 18 19 20 21 22 23 24 -------------------------------------------------------------------------- -| {12}| {11}| {10}| {9}| {8}| {7}| bar 1: {26} | {6}| {5}| {4}| {3}| {2}| {1}| end -1: {0}| +| {11}| {10}| {9}| {8}| {7}| {6}| bar -1: {24} | {5}| {4}| {3}| {2}| {1}| {0}| end -1: TODO| |---|---|---|---|---|---|-----------|---|---|---|---|---|---| -| {13}| {14}| {15}| {16}| {17}| {18}| bar -1: {27} | {19}| {20}| {21}| {22}| {23}| {24}| end 1: {25}| +| {12}| {13}| {14}| {15}| {16}| {17}| bar 1: {25} | {18}| {19}| {20}| {21}| {22}| {23}| end 1: TODO| -------------------------------------------------------------------------- 12 11 10 9 8 7 6 5 4 3 2 1 """.format(*temp) - def move_to_bar(self, to_idx): - # Find the owner of the hit checker - player = self.state[to_idx] - # FIXME: find better bar solution - if player == 1: - self.state[26] += player - else: - self.state[27] += player - - self.state[to_idx] = 0 - - def move_thing(self, player, from_idx, to_idx): - self.state[from_idx] -= player - - if self.state[to_idx] * player == -1: - self.move_to_bar(to_idx) + @staticmethod + def do_move(board, player, move): + # Implies that move is valid; make sure to check move validity before calling do_move(...) + + def move_to_bar(board, to_idx): + board = list(board) + if player == 1: + board[0] += player + else: + board[25] += player - self.state[to_idx] += player + board[to_idx] = 0 + return board + + # TODO: Moving in from bar is handled by the representation + # TODONE: Handle bearing off + + from_idx = move[0] + to_idx = move[1] + board = list(board) # Clone board + + # 'Lift' checker + board[from_idx] -= player + + # Handle bearing off + if to_idx < 1 or to_idx > 24: + return board + + # Handle hitting checkers + if board[to_idx] * player == -1: + board = move_to_bar(board, to_idx) + + # Put down checker + board[to_idx] += player + + return board diff --git a/bot.py b/bot.py index c853df0..db2f30c 100644 --- a/bot.py +++ b/bot.py @@ -1,13 +1,12 @@ from cup import Cup +from board import Board import random class Bot: - def __init__(self, board, sym, network): + def __init__(self, sym): self.cup = Cup() - self.board = board self.sym = sym - self.network = network def roll(self): print("{} rolled: ".format(self.sym)) @@ -22,20 +21,20 @@ class Bot: def get_sym(self): return self.sym - def do_move(self, roll): - print(self.board.pretty()) - print(self.board.find_legal_moves(self.sym, roll[0])) - moves_1 = self.board.find_legal_moves(self.sym,roll[0]) + def do_move(board, roll): + print(Board.pretty(board)) + print(Board.find_legal_moves(board, self.sym, roll[0])) + moves_1 = Board.find_legal_moves(board, self.sym,roll[0]) move = random.choice(moves_1) print("{} was picked as move".format(move)) - self.board.move_thing(self.sym, int(move[0]), int(move[1])) + new_board = Board.move_thing(self.sym, int(move[0]), int(move[1])) - print(self.board.pretty()) - print(self.board.find_legal_moves(self.sym, roll[1])) - moves_2 = self.board.find_legal_moves(self.sym,roll[1]) + print(Board.pretty(new_board)) + print(Board.find_legal_moves(new_board, self.sym, roll[1])) + moves_2 = Board.find_legal_moves(new_board, self.sym,roll[1]) move = random.choice(moves_2) print("{} was picked as move".format(move)) - self.board.move_thing(self.sym, int(move[0]), int(move[1])) + return Board.move_thing(new_board, self.sym, int(move[0]), int(move[1])) diff --git a/game.py b/game.py index 12ebfab..575aa8b 100644 --- a/game.py +++ b/game.py @@ -6,10 +6,9 @@ from cup import Cup class Game: def __init__(self): - self.board = Board() - self.network = Network() - self.p1 = Human(self.board, 1, self.network) - self.p2 = Bot(self.board, -1, self.network) + self.board = Board.initial_state + self.p1 = Human(1) + self.p2 = Bot(-1) self.cup = Cup() def roll(self): @@ -19,15 +18,15 @@ class Game: while True: roll = self.roll() print("{} rolled: {}".format(self.p1.get_sym(), roll)) - self.p1.do_move(roll) - if self.board.is_winner(self.p1.get_sym()): + self.board = self.p1.do_move(self.board, roll) + if Board.is_winner(board, self.p1.get_sym()): print("{} won!".format(self.p1.get_sym())) break roll = self.roll() print("{} rolled: {}".format(self.p1.get_sym(), roll)) - self.p2.do_move(roll) - if self.board.is_winner(self.p2.get_sym()): + self.board = self.p2.do_move(self.board, roll) + if Board.is_winner(board, self.p2.get_sym()): print("{} won!".format(self.p2.get_sym())) break diff --git a/human.py b/human.py index 731ebfb..89c97ad 100644 --- a/human.py +++ b/human.py @@ -1,7 +1,7 @@ +from board import Board + class Human: - def __init__(self, board, sym, network): - self.network = network - self.board = board + def __init__(self, sym): self.sym = sym def switch(self,cur): @@ -10,30 +10,30 @@ class Human: def get_sym(self): return self.sym - def do_move(self, roll): - print(self.board.pretty()) - print(self.board.find_legal_moves(self.sym,roll[0])) - cur_state = self.board.get_state() + def do_move(self, board, roll): + print(Board.pretty(board)) + print("Human: ",roll,"-"*20) + print(Board.find_legal_moves(board,self.sym,roll[0])) print("What to do with the first roll?") cmds_1 = input().split(",") - - while not self.board.check_move(cmds_1, self.sym, roll[0]): + + while not Board.is_move_valid(board, roll[0], self.sym, cmds_1): print("Invalid move, try again.") cmds_1 = input().split(",") - self.board.move_thing(self.sym, int(cmds_1[0]), int(cmds_1[1])) + new_board = Board.move_thing(board, self.sym, int(cmds_1[0]), int(cmds_1[1])) - print(self.board.pretty()) - print(self.board.find_legal_moves(self.sym,roll[1])) + print(Board.pretty(board)) + print(Board.find_legal_moves(new_board, self.sym,roll[1])) print("What to do with the second roll?") cmds_2 = input().split(",") - while not self.board.check_move(cmds_2, self.sym, roll[1]): + while not Board.is_move_valid(new_board, roll[1], self.sym, cmds_2): print("Invalid move") cmds_2 = input().split(",") - self.board.move_thing(self.sym, int(cmds_2[0]), int(cmds_2[1])) + return Board.move_thing(new_board, self.sym, int(cmds_2[0]), int(cmds_2[1]))