class Board: # First we need to initialize our object with a 'state.' def __init__(self,): # While at the moment, we have a fixed initial state, in the future # we may want players to be able to choose where the amazons start, # or perhaps include an option for a random start. # In order to build in this future flexibility, we will use another # function to determine go get the initial state whih we can edit later. self.state = self.get_initial_state() # Note also that these functions are of the class, and so need to be # prefixed with a 'self.' so that our script will know we mean the # function that is defined within the our class structure. def get_initial_state(self): # For now we will use a 4x4 board and place the amazons in the middle # four squares, diagonlally from eachother for symmetry. # Note that because the board is symettrical, one of the players must # have an advantage... but is it player 1 or player 2? return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]] def get_active_player_code(self): # It's going to be useful to know whose turn it is at any given time. # Luckily, because a player burns away a square each turn, we can tell # whose turn it is by counting how many open squares are left! They act # like a kind of timer, coutning down to the end of the game. # On turn 1 (initial state), it is player 1's turn and there are 12 # open spaces at the start of the turn... so if the number of open # spaces is even, then it's player 1's turn, and if odd then player 2's. free_spaces = 0 for row in self.state: #remember the state is a list of four 'rows'... for box in row: if box == 0: free_spaces += 1 # The logic above only worked because we had a 4x4 board, but if we had # a 5x5 board, then we would start with 21 open spaces, so the logic is # reversed with player 1 being odd and player 2 even, so... if len(self.state[0])%2 == 0: # If the length of the rows is even... if free_spaces%2 == 0: # then an even number of free spaces... return (2) # means it's player 1's turn. else: return (3) # And an odd number makes it player 2's. else: # If the length of the rows is even... if free_spaces%2 == 0: # then an even number of free spaces... return (3) # means it's player 2's turn. else: return (2) # And an odd number makes it player 1's. def get_active_amazons_positions(self): code = self.get_active_player_code() #First of all, whose turn is it? positions=[] # This will contain the (x,y) for each of the two amazons. for y, row in enumerate(self.state): # 'enumerate' takes a list like: # ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')] # So in this case 'y' refers to these numbers (the enumeration) # which correspond to the column we are interested in, and 'row' # refers to the row we are interested in. for x, box in enumerate(row): # This time we hone in on the x coordinate # of each entry in the row. if box == code: # If the actual value at that (x,y) matches whose # turn it is, i.e. they have an amazon there... positions.append((x, y)) # We add that (x,y) pair to the list. return positions def get_reachable_squares(self, origin): directions = [[-1,1],[0,1],[1,1],[-1,0],[1,0],[-1,-1],[0,-1],[1,-1]] # From each square on the board, there are eight directions amazons can # move in or shoot in. These represent those eight directions. For example, # (1,1) refers to going to the right once and up once and (-1,-1) means # left and down respectively. reachables = [] for direction in directions: # For each of the 8 directions... print("origin: "+str(origin)) move_option = [origin[0], origin[1]] # center ourselves on the amazon. hit_something = False # We will make this false if we find an obstacle. while hit_something == False: # Until we hit something.... # move in the specified direction: move_option[0] += direction[0] move_option[1] += direction[1] print("move option: "+str(move_option)) print("It's in bounds: "+str(self.in_bounds(move_option))) if self.in_bounds(move_option): print("It's empty: "+str(self.is_empty(move_option))) if self.is_empty(move_option): # if we are still on the board and if the square is empty... addition = move_option.copy() print("once again, the move option is:"+str(addition)) reachables.append(addition) # add it to the list. print("reaching: "+str(reachables)) else: hit_something = True else: # If we hit the edge of the board or an obstacle... hit_something = True print("reachables:"+str(reachables)) return reachables def possible_moves(self): # Note that a "move" consists of both moving an amazon and shooting. # This means that a move has three values: chosen amazon's starting position, # the amazon's new position, and the position of the burned square. move_options=[] amazons = self.get_active_amazons_positions() # Find the amazons. for amazon in amazons: amazon_move_options = self.get_reachable_squares(amazon) # And where they can go... for move_option in amazon_move_options: #For each move, we see also what squares we can burn: burn_options = self.get_reachable_squares(move_option) for burn_option in burn_options: # Now that we have an amazon, each square it can go to, and # each square it can burn from each move, we have a (potentially large) # list of triples. Let's add them to the list and move to the # next amazon. move_options.append((amazon, move_option, burn_option)) return move_options def get_successor_state(self, move_and_burn): # We need to update the board based on the move, so let's grab it: new_state = self.state.copy() # These are the (x,y) coordinates of the chosen amazon's start. ai, aj = [move_and_burn[0][0]], [move_and_burn[0][1]] # These are the (x,y) coordinates of the chosen amazon's move. mi, mj = [move_and_burn[1][0]], [move_and_burn[1][1]] # These are the (x,y) corrdinates of the chosen burn square. bi, bj = [move_and_burn[2][0]], [move_and_burn[2][1]] new_state[aj][ai] = 0 # The amazon's start square is emptied. new_state[mj][mi] = self.get_active_player_code() # The move square is filled. new_state[bj][bi] = 1 # The burn square is burned. return (new_state) def get_possible_successor_states(self): # This just uses the other function and applies is to every possible move. return [self.get_successor_state(move) for move in self.possible_moves()] def is_empty(self, square): # Here's the function referenced in "get_reachable_squares." # Recall that the input is the (x,y) position of an amazon. x, y = square # We isolate the x and y. # Don't forget that while we say (x,y), the indices are refernced as [y][x] # since the rows are above and below eachother (y) and columns adjacent (x): if (x in range(len(self.state[0]))) and (y in range(len(self.state[0]))): if self.state[y][x] == 0: return True else: return False else: return False def in_bounds(self, square): # Here's the other function refernced in "get_reachable_squares." x, y = square # We need to make sure all (x,y) values are between 0 and the length of a row. if (x < 0) or (y < 0) or (x > len(self.state[0])) or (y > len(self.state[0])): return False else: return True board = Board() print(Board.possible_moves(board))