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 # 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! # 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): 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 face_value -= 1 elif from_idx == 27: from_idx = 24 face_value -= 1 if not (1 <= from_idx <= 24 and 1 <= to_idx <= 24): return False 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 @staticmethod def is_winner(board, player): for i in range(1,25): if player * board[i] > 0: return False return True @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 print("Find legal moves: ",roll,"-"*20) 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 @staticmethod def pretty(board): temp = [] for x in board: if x >= 0: temp.append(" {}".format(x)) else: temp.append("{}".format(x)) return """ 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| -------------------------------------------------------------------------- 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(...) def move_to_bar(board, to_idx): board = list(board) if player == 1: board[0] += player else: board[25] += 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