From 275689c002c04d261fa0894aa54df4c5e8648a7c Mon Sep 17 00:00:00 2001 From: Anders Ladefoged Date: Thu, 22 Feb 2018 14:01:28 +0100 Subject: [PATCH] Bot reimplemented with new representation. Fixed bug where black could not bear off. More tests written. --- board.py | 105 +++++++++++++++++++++++-------------------- bot.py | 21 ++------- game.py | 46 +++++++++++++------ human.py | 2 +- test.py | 133 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 5 files changed, 224 insertions(+), 83 deletions(-) diff --git a/board.py b/board.py index 2e0543c..76ec991 100644 --- a/board.py +++ b/board.py @@ -1,3 +1,4 @@ +import pdb import numpy as np import itertools @@ -10,19 +11,6 @@ class Board: 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 - - # 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 - # X, then it can be allowed. - - # Also, the check_move will also fail when you're attempting to leave a - # bar. A fix of this is of course to check if the from_idx = bar and if so, - # allow some extra stuff! - @staticmethod def idxs_with_checkers_of_player(board, player): idxs = [] @@ -31,14 +19,6 @@ class Board: idxs.append(idx) return idxs - # TODO: write tests - # FIXME: make sure to disallow backwards movement - # TODO: implement double roll feature (4 dice if dice are equal) - # TODO: implement moving checkers into home (bearing off) - # TODO: handle barring in a more elengant way - # TODO: allow bearing off with non-exact die roll if this is only possible - # move - # TODO: Allow not doing anything if and only if no alternatives @staticmethod def is_move_valid(board, player, face_value, move): def sign(a): @@ -105,14 +85,12 @@ class Board: if player == 1: return all([(idx >= 19) for idx in checker_idxs]) else: - return all([(idx >= 6) for idx in checker_idxs]) + return all([(idx <= 6) for idx in checker_idxs]) return all([ is_moving_backmost_checker(), all_checkers_in_last_quadrant() ]) - # TODO # TODO: add switch here instead of wonky ternary in all - return all([ is_forward_move(), face_value_match_move_length(), @@ -161,17 +139,16 @@ class Board: @staticmethod def calculate_legal_states(board, player, roll): - # Find all pips with things on them belonging to the player + # 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 - # If player is O, then check for idx + roll - # If player is X, then check for idx - 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): idxs_with_checkers = Board.idxs_with_checkers_of_player(board, player) + if len(idxs_with_checkers) == 0: + return [board] boards = [(Board.do_move(board, player, (idx, idx + (face_value * player))) @@ -181,8 +158,15 @@ class Board: (idx, idx + (face_value * player))) else None) for idx in idxs_with_checkers] + + board_list = list(filter(None, boards)) # Remove None-values + # if len(board_list) == 0: + # return [board] - return list(filter(None, boards)) # Remove None-values + return board_list + + # 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 @@ -197,35 +181,55 @@ class Board: 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] - # Add resulting unique boards to set of legal boards resulting from roll - legal_moves = legal_moves | set(boards) - return legal_moves + # 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)) - else: - temp.append("{}".format(x)) + if x > 0: + temp.append(" {}".format(white(x))) + elif x < 0: + temp.append("{}".format(black(x))) + else: temp.append(" ") - return """ + return """\033[0;47m 13 14 15 16 17 18 19 20 21 22 23 24 --------------------------------------------------------------------------- -| {11}| {10}| {9}| {8}| {7}| {6}| bar -1: {24} | {5}| {4}| {3}| {2}| {1}| {0}| end -1: TODO| -|---|---|---|---|---|---|-----------|---|---|---|---|---|---| -| {12}| {13}| {14}| {15}| {16}| {17}| bar 1: {25} | {18}| {19}| {20}| {21}| {22}| {23}| end 1: TODO| --------------------------------------------------------------------------- ++--------------------------------------------------------------------------+ +| {11}| {10}| {9}| {8}| {7}| {6}| bar -1: {24} | {5}| {4}| {3}| {2}| {1}| {0}| end -1: TODO| +|---|---|---|---|---|---|------------|---|---|---|---|---|---| | +| {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) + \033[0m""".format(*temp) @staticmethod def do_move(board, player, move): @@ -234,18 +238,21 @@ class Board: def move_to_bar(board, to_idx): board = list(board) if player == 1: - board[0] += player + board[25] -= player else: - board[25] += player + board[0] -= player - board[to_idx] = 0 - return tuple(board) + board[to_idx] = 0 + return board # TODO: Moving in from bar is handled by the representation # TODONE: Handle bearing off from_idx = move[0] + #print("from_idx: ", from_idx) to_idx = move[1] + #print("to_idx: ", to_idx) + # pdb.set_trace() board = list(board) # Make mutable copy of board # 'Lift' checker @@ -253,7 +260,7 @@ class Board: # Handle bearing off if to_idx < 1 or to_idx > 24: - return board + return tuple(board) # Handle hitting checkers if board[to_idx] * player == -1: @@ -263,3 +270,5 @@ class Board: board[to_idx] += player return tuple(board) + + diff --git a/bot.py b/bot.py index db2f30c..6ea98d4 100644 --- a/bot.py +++ b/bot.py @@ -20,22 +20,9 @@ class Bot: def get_sym(self): return self.sym - - 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)) - new_board = Board.move_thing(self.sym, int(move[0]), int(move[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)) - return Board.move_thing(new_board, self.sym, int(move[0]), int(move[1])) - + def make_move(self, board, sym, roll): + # print(Board.pretty(board)) + legal_moves = Board.calculate_legal_states(board, sym, roll) - + return random.choice(list(legal_moves)) diff --git a/game.py b/game.py index 575aa8b..4f3bd9a 100644 --- a/game.py +++ b/game.py @@ -1,3 +1,4 @@ +import time from human import Human from board import Board from bot import Bot @@ -7,7 +8,7 @@ from cup import Cup class Game: def __init__(self): self.board = Board.initial_state - self.p1 = Human(1) + self.p1 = Bot(1) self.p2 = Bot(-1) self.cup = Cup() @@ -15,20 +16,37 @@ class Game: return self.cup.roll() def play(self): - while True: - roll = self.roll() - print("{} rolled: {}".format(self.p1.get_sym(), roll)) - 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 + count = 0 + while Board.outcome(self.board) == None: + count += 1 + + print("Turn:",count) roll = self.roll() - print("{} rolled: {}".format(self.p1.get_sym(), roll)) - 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 -g = Game() + #print("type of board: ", type(self.board)) + print("Board:",self.board) + print("{} rolled: {}".format(self.p1.get_sym(), roll)) + + self.board = self.p1.make_move(self.board, self.p1.get_sym(), roll) + + print(self.board) + + print() + + count += 1 + + roll = self.roll() + print("{} rolled: {}".format(self.p2.get_sym(), roll)) + self.board = self.p2.make_move(self.board, self.p2.get_sym(), roll) + + + if Board.outcome(self.board)[1] > 0: + print_winner = "1: White, " + str(Board.outcome(self.board)) + else: + print_winner = "-1: Black " + str(Board.outcome(self.board)) + print("The winner is {}!".format(print_winner)) + print("Final board:",Board.pretty(self.board)) + +g = Game() g.play() diff --git a/human.py b/human.py index 89c97ad..2372068 100644 --- a/human.py +++ b/human.py @@ -10,7 +10,7 @@ class Human: def get_sym(self): return self.sym - def do_move(self, board, roll): + def make_move(self, board, roll): print(Board.pretty(board)) print("Human: ",roll,"-"*20) print(Board.find_legal_moves(board,self.sym,roll[0])) diff --git a/test.py b/test.py index 4324624..260c8bd 100644 --- a/test.py +++ b/test.py @@ -76,7 +76,7 @@ class TestIsMoveValid(unittest.TestCase): self.assertEqual(Board.is_move_valid(board, 1, 3, (1, 4)), True) self.assertEqual(Board.is_move_valid(board, -1, 3, (24, 21)), True) - def test_bear_off_simple(self): + def test_bear_off_simple_white(self): board = ( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -86,6 +86,16 @@ class TestIsMoveValid(unittest.TestCase): self.assertEqual(Board.is_move_valid(board, 1, 2, (24, 26)), True) self.assertEqual(Board.is_move_valid(board, 1, 1, (24, 25)), True) + def test_bear_off_simple_black(self): + board = ( 0, + -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0 ) + self.assertEqual(Board.is_move_valid(board, -1, 2, (1, -1)), True) + self.assertEqual(Board.is_move_valid(board, -1, 1, (1, 0)), True) + def test_bear_off_with_higher_face_value(self): board = ( 0, @@ -237,7 +247,7 @@ class TestDoMove(unittest.TestCase): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ) - expected_board = [0] * 26 + expected_board = tuple([0] * 26) self.assertEqual(Board.do_move(board, 1, (24, 30)), expected_board) self.assertEqual(Board.do_move(board, 1, (24, 26)), expected_board) self.assertEqual(Board.do_move(board, 1, (24, 25)), expected_board) @@ -248,7 +258,7 @@ class TestDoMove(unittest.TestCase): 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) - expected_board = [0] * 26 + expected_board = tuple([0] * 26) self.assertEqual(Board.do_move(board, -1, (1, 0)), expected_board) self.assertEqual(Board.do_move(board, -1, (1, -1)), expected_board) self.assertEqual(Board.do_move(board, -1, (1, -4)), expected_board) @@ -281,7 +291,70 @@ class TestAnyMoveValid(unittest.TestCase): self.assertEqual(Board.any_move_valid(board, -1, (1,3)), True) self.assertEqual(Board.any_move_valid(board, -1, (4,4)), True) + def test_white_bear_off_odd(self): + board = (0, + -14, -1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0) + + self.assertEqual(Board.any_move_valid(board, 1, (1,2)), True) + self.assertEqual(Board.any_move_valid(board, 1, (2,4)), True) + self.assertEqual(Board.any_move_valid(board, 1, (4,6)), True) + + def test_black_bear_off_odd(self): + board = (0, + -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 14, + 0) + + self.assertEqual(Board.any_move_valid(board, -1, (1,2)), True) + self.assertEqual(Board.any_move_valid(board, -1, (2,4)), True) + self.assertEqual(Board.any_move_valid(board, -1, (4,6)), True) + class TestLegalMoves(unittest.TestCase): + def test_white_bear_off(self): + board = (0, + -14, -1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 1, + 0) + + expected_board_set = {( 0, + -14, -1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0 )} + + self.assertEqual(Board.calculate_legal_states(board, 1, (1,2)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, 1, (2,4)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, 1, (4,2)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, 1, (4,6)), expected_board_set) + + def test_black_bear_off(self): + board = (0, + -1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 14, + 0) + + expected_board_set = {( 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 14, + 0 )} + self.assertEqual(Board.calculate_legal_states(board, -1, (1,2)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, -1, (2,4)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, -1, (4,2)), expected_board_set) + self.assertEqual(Board.calculate_legal_states(board, -1, (4,6)), expected_board_set) + def test_blocked(self): board = ( 0, 0, 0, 0, 0, 0, 0, @@ -425,5 +498,59 @@ class TestLegalMoves(unittest.TestCase): } self.assertEqual(Board.calculate_legal_states(board, 1, (1,1)), expected_board_set_1_1) + + def test_hit_on_bear_in(self): + board = (0, + -2, 0, -2, -1, -3, -2, + 0, -2, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, + 6, 0, 3, 1, 1, -4, + -1) + + expected_board_set = { (1, + -2, 0, -2, -1, -3, -2, + 0, -2, 0, 0, 0, 0, + -1, 0, 0, 0, 0, -1, + 6, 0, 3, 0, 1, -4, + 0), + + (1, + -2, 0, -2, -1, -3, -2, + 0, -2, -1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + 6, 0, 3, -1, 1, -4, + 0), + + (1, + -2, 0, -2, -2, -3, -2, + 0, -1, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, + 6, 0, 3, -1, 1, -4, + 0), + + (1, + -2, -1, -2, -1, -3, -1, + 0, -2, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, + 6, 0, 3, -1, 1, -4, + 0), + + (1, + -3, 0, -2, -1, -2, -2, + 0, -2, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, + 6, 0, 3, -1, 1, -4, + 0), + + (1, + -2, 0, -2, -1, -3, -2, + 0, -2, 0, 0, 0, 0, + -1, 0, 0, 0, 0, 0, + 6, -1, 3, -1, 1, -3, + 0) + } + + self.assertEqual(Board.calculate_legal_states(board, -1, (4,3)), expected_board_set) + if __name__ == '__main__': unittest.main()