I'm doing poorly on making good tests, but the bad testing im doing is working. Still in progress.
This commit is contained in:
parent
c67ab96a52
commit
f2000deb40
146
amazons/board.py
146
amazons/board.py
|
@ -1,83 +1,157 @@
|
||||||
class Board:
|
class Board:
|
||||||
|
# First we need to initialize our object with a 'state.'
|
||||||
def __init__(self,):
|
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()
|
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):
|
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]]
|
return [[0,0,0,0],[0,2,3,0],[0,3,2,0],[0,0,0,0]]
|
||||||
|
|
||||||
def get_active_player_code(self):
|
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
|
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:
|
for box in row:
|
||||||
if box == 0:
|
if box == 0:
|
||||||
free_spaces += 1
|
free_spaces += 1
|
||||||
if len(self.state[0])%2 == 0:
|
# The logic above only worked because we had a 4x4 board, but if we had
|
||||||
return (free_spaces%2+2)
|
# a 5x5 board, then we would start with 21 open spaces, so the logic is
|
||||||
else:
|
# reversed with player 1 being odd and player 2 even, so...
|
||||||
return (free_spaces%2+3)
|
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):
|
def get_active_amazons_positions(self):
|
||||||
code = self.get_active_player_code()
|
code = self.get_active_player_code() #First of all, whose turn is it?
|
||||||
positions=[]
|
positions=[] # This will contain the (x,y) for each of the two amazons.
|
||||||
for y, row in enumerate(self.state):
|
for y, row in enumerate(self.state): # 'enumerate' takes a list like:
|
||||||
for x, box in enumerate(row):
|
# ['a', 'b', 'c'] and outputs [(0,'a'), (1,'b'), (2,'c')]
|
||||||
if box == code:
|
# So in this case 'y' refers to these numbers (the enumeration)
|
||||||
positions.append((x, y))
|
# 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
|
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):
|
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=[]
|
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)
|
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 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)
|
burn_options = self.get_reachable_squares(move_option)
|
||||||
for burn_option in burn_options:
|
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))
|
move_options.append((amazon, move_option, burn_option))
|
||||||
|
print('one down')
|
||||||
return move_options
|
return move_options
|
||||||
|
|
||||||
def get_successor_state(self, move_and_burn):
|
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()
|
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]]
|
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]]
|
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]]
|
bi, bj = [move_and_burn[2][0]], [move_and_burn[2][1]]
|
||||||
new_state[aj][ai] = 0
|
new_state[aj][ai] = 0 # The amazon's start square is emptied.
|
||||||
new_state[mj][mi] = self.get_active_player_code()
|
new_state[mj][mi] = self.get_active_player_code() # The move square is filled.
|
||||||
new_state[bj][bi] = 1
|
new_state[bj][bi] = 1 # The burn square is burned.
|
||||||
next_game_states.append(new_state)
|
return (new_state)
|
||||||
return next_game_states
|
|
||||||
|
|
||||||
def get_possible_successor_states(self):
|
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()]
|
return [self.get_successor_state(move) for move in self.possible_moves()]
|
||||||
|
|
||||||
def is_empty(self, square):
|
def is_empty(self, square):
|
||||||
i, j = square
|
# Here's the function referenced in "get_reachable_squares."
|
||||||
if self.state[j][i] == 0:
|
# 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
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def in_bounds(self, square):
|
def in_bounds(self, square):
|
||||||
i, j = square
|
# Here's the other function refernced in "get_reachable_squares."
|
||||||
if (i < 0) or (j < 0) or (i > len(self.state[0])) or (j > len(self.state[0])):
|
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
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
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()
|
board = Board()
|
||||||
|
|
||||||
print(Board.possible_moves(board))
|
print(Board.possible_moves(board))
|
||||||
|
|
Loading…
Reference in New Issue