From f2000deb407cd8e02491d89e1509699716270c03 Mon Sep 17 00:00:00 2001 From: finn Date: Wed, 28 Jun 2023 15:03:32 -0400 Subject: [PATCH] I'm doing poorly on making good tests, but the bad testing im doing is working. Still in progress. --- amazons/board.py | 146 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 110 insertions(+), 36 deletions(-) diff --git a/amazons/board.py b/amazons/board.py index c7ac35b..4f21e9f 100644 --- a/amazons/board.py +++ b/amazons/board.py @@ -1,83 +1,157 @@ 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: + for row in self.state: #remember the state is a list of four 'rows'... for box in row: if box == 0: free_spaces += 1 - if len(self.state[0])%2 == 0: - return (free_spaces%2+2) - else: - return (free_spaces%2+3) + # 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() - positions=[] - for y, row in enumerate(self.state): - for x, box in enumerate(row): - if box == code: - positions.append((x, y)) + 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 = [] + print('amazon= '+str(origin)) + for direction in directions: # For each of the 8 directions... + 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('----------------') + print('move_option = '+str(move_option)) + # move in the specified direction... + print('in bounds = '+str(self.in_bounds(move_option))) + print('is empty = '+str(self.is_empty(move_option))) + if self.in_bounds(move_option) and self.is_empty(move_option): + # if we are still on the board and if the square is empty... + reachables.append(move_option) # add it to the list. + else: # If we hit the edge of the board or an obstacle... + hit_something = True + print('I hit something = '+str(hit_something)) + print('ok found em') + return reachables + def possible_moves(self): - directions = [(-1,1),(0,1),(1,1),(-1,0),(1,0),(-1,-1),(0,-1),(1,-1)] + # 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=[] - for amazon in self.get_active_amazons_positions(): + amazons = self.get_active_amazons_positions() # Find the amazons. + print('amazons are '+str(amazons)) + for amazon in amazons: + print('we are on '+str(amazon)) amazon_move_options = self.get_reachable_squares(amazon) + # And where they can go... + print('move options are: '+str(amazon_move_options)) 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)) + print('one down') 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 - new_state[mj][mi] = self.get_active_player_code() - new_state[bj][bi] = 1 - next_game_states.append(new_state) - return next_game_states + 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): - i, j = square - if self.state[j][i] == 0: + # 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 self.state[y][x] == 0: return True else: return False def in_bounds(self, square): - i, j = square - if (i < 0) or (j < 0) or (i > len(self.state[0])) or (j > len(self.state[0])): + # Here's the other function refernced in "get_reachable_squares." + print('square = '+str(square)) + 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 - def get_reachable_squares(self, amazon): - directions = [(-1,1),(0,1),(1,1),(-1,0),(1,0),(-1,-1),(0,-1),(1,-1)] - reachables = [] - for direction in directions: - move_option = amazon - hit_something = False - while hit_something == False: - move_option += direction - if self.in_bounds(move_option) and self.is_empty(move_option): - reachables.append(move_option) - else: - hit_something = True - return reachables - board = Board() print(Board.possible_moves(board))