def bless(self): player = self.get_current_player() old_aura = player.hex.aura n_actions = self.current_board.actions if old_aura == self.current_board.faction: raise InvalidMove( 'You cannot bless a hex that already has your aura') elif old_aura == None: if n_actions < 1: raise InvalidMove( 'You cannot bless because you have no more actions') self.current_board.actions -= 1 player.hex.aura = self.current_board.faction else: if n_actions < 2: raise InvalidMove( 'You cannot bless because you have {} action{} remaining'. format( n_actions, '' if n_actions == 1 else 's', )) self.current_board.actions -= 2 player.hex.aura = self.current_board.faction return True
def _validate_artwork_status(self, board): # if the spell has an artwork, check if it's placed on the board, on your aura if self.artwork: if self.artwork.hex: if self.artwork.hex.aura != board.faction: raise InvalidMove('{} artwork is not on player\'s aura'.format(self.name)) else: raise InvalidMove('{} artwork is not placed on board'.format(self.name))
def drop(self): if self.current_board.actions < 1: raise InvalidMove( 'You cannot drop because you have no more actions') # get list of eligible hexes player = self.get_current_player() adj_hexes = find_adjacent_hexes(self.current_board, player.hex) adj_hexes_wo_objs = [h for h in adj_hexes if h.occupant == None] if len(adj_hexes_wo_objs) == 0: raise InvalidMove('There is no adjacent hex where you can drop') # get list of eligible artworks faction = self.current_board.faction eligible_artworks = [] for artwork in self.current_board.artworks: if artwork.faction == faction and artwork.hex == None: eligible_artworks.append(artwork) if len(eligible_artworks) == 0: raise InvalidMove( '{} does not have any unplaced artworks'.format(faction)) artwork = self.screen.choice(0) if artwork == None: # set artwork based on click if it's been chosen if 'click_spell_idx' in self.screen.data: spell_idx = self.screen.data['click_spell_idx'] click_artwork = self.current_board.spells[spell_idx].artwork self.screen.data.pop('click_spell_idx') if click_artwork in eligible_artworks: artwork = click_artwork self.screen.choices.append(artwork) # otherwise, get user input else: artwork = self.screen_input.choose_from_list( self.screen, eligible_artworks, 'Choose artwork to drop:', '{} does not have any unplaced artworks'.format(faction)) if artwork == None: return False # prompt for hex choice if needed hex = self.screen_input.choose_hexes( self.screen, adj_hexes_wo_objs, 'Click where to drop {}'.format(artwork), 'There is no adjacent hex where you can drop', ) if hex == None: return False self.current_board.actions -= 1 self.current_board.move_object(artwork, to_hex=hex) return True
def pick_up(self): if self.current_board.actions < 1: raise InvalidMove( 'You cannot pick up because you have no more actions') # get list of eligible hexes player = self.get_current_player() adj_hexes = find_adjacent_hexes(self.current_board, player.hex) # get list of eligible artworks faction = self.current_board.faction eligible_artworks = [] coords = [] # coordinates of eligible_artworks for artwork in self.current_board.artworks: if artwork.faction == faction and artwork.hex in adj_hexes: eligible_artworks.append(artwork) coords.append(artwork.hex) # prompt for artwork choice if needed artwork_idx = self.screen_input.choose_hexes( self.screen, coords, 'Click artwork to pick up', '{} does not have any adjacent artworks to pick up'.format( faction), return_index=True, ) if artwork_idx == None: return False artwork = eligible_artworks[artwork_idx] self.current_board.actions -= 1 self.current_board.move_object(artwork, from_hex=artwork.hex) return True
def choose_from_list(screen, ls, prompt_text='Choose one:', error_text='No valid choices', all_spells=None): if len(ls) == 0: raise InvalidMove(error_text) elif len(ls) == 1: return ls[0] prompt = prompt_text for idx, obj in enumerate(ls, start=1): prompt += ' ({}) {}'.format(idx, obj) screen.info.text = prompt screen.toggle_action_buttons() while True: choice = get_keypress(screen) try: ret = ls[int(choice) - 1] screen.toggle_action_buttons() screen.info.error = None return ret except (ValueError, IndexError): screen.info.error = 'Please enter a number 1-{}'.format(len(ls))
def cast(self, board): self._validate_spell_status_and_tap(board) # get list of linked objects linked_hexes = location.linked_hexes(board, self.artwork.hex) linked_objects = [hex.occupant for hex in linked_hexes if hex.occupant] # get list of hexes to move to from the board target_hexes = [hex for hex in board.get_all_hexes() if not(hex.occupant)] if target_hexes == []: # this cannot happen since there are only 9 possible occupants :) raise InvalidMove('All hexes are occupied.') # choose a linked object to move. There should always be at least one, # since we've validated that the Locksmith is on an aura target_object = board.screen.choice(1) or choose_from_list( board.screen, linked_objects, prompt_text='Choose object to move:', ) if target_object == None: return self._exit_cast(done=False) target_hex = choose_hexes( board.screen, target_hexes, prompt_text='Click where to move {}'.format(target_object) ) if target_hex == None: return self._exit_cast(done=False) board.move_object(target_object, from_hex = target_object.hex, to_hex = target_hex) return self._exit_cast(done=True)
def _validate_spell_status_and_tap(self, board): # current player must own spell if self.faction != board.faction: raise InvalidMove("{} cannot cast {} because it is owned by {}".format( board.faction, self.name, self.faction, )) # spell cannot already have been used if self.tapped: raise InvalidMove("{} already cast".format(self.name)) # artwork must also have correct status self._validate_artwork_status(board) # mark spell used self.tapped = True
def move_object(self, occupant, from_hex=None, to_hex=None): # order matters here, updating occupant.hex last makes it ok for # from_hex to be occupant.hex initially if from_hex != None: from_hex.set_object(None) if to_hex != None: if to_hex.occupant != None: raise InvalidMove('You\'re trying to move onto an occupied hex.') to_hex.set_object(occupant) occupant.hex = to_hex
def set_object(self, occupant): # validate occupant type try: if occupant != None: occupant.get_obj_type( ) # only objects (artworks and players) have this method except AttributeError: raise InvalidMove('TypeError placing {}'.format(self, occupant)) self.occupant = occupant
def end_turn(self): if self.current_board.actions < 0: raise InvalidMove( 'You cannot end turn with negative actions, please reset turn and try again' ) if self.maybe_claim_spell(): self.current_board.end_turn( ) # update faction, untap spells, reset # actions self.sync_boards() return True else: return False
def cast(self, board): self._validate_spell_status_and_tap(board) # temp room represents all the places that the shovel can be placed temp_is_placed = any([room.name == "Temp" for room in board.rooms]) shovel_room = next((room for room in board.rooms if room.name == "Shovel"), None) if not temp_is_placed: player_on_shovel = board.get_current_player().hex.room == shovel_room if player_on_shovel: # shovel can move anywhere - get neighbors of the whole board temp_locations = location.find_unoccupied_neighbors(board, board.get_all_hexes()) else: # shovel can move to adjacent spots that are empty - get player's neighbors player_hex = board.get_current_player().hex temp_locations = location.find_unoccupied_neighbors(board,[player_hex]) if temp_locations == []: raise InvalidMove("There's nowhere to place the Shovel") # make a temporary room with these locations board.rooms.append(Room( name = "Temp", root = None, shape = temp_locations, a_spell = None, b_spell = None, relative_shape = False, )) # get the (possibly first-ever) location for the Shovel board.flush_hex_data() shovel_hex = choose_hexes( board.screen, board.rooms[-1].hexes, prompt_text = "Choose where the Shovel will go" ) if shovel_hex == None: return self._exit_cast(done=False) shovel_location = shovel_hex.location # get rid of the temporary room board.rooms.pop() if shovel_room: shovel_room.hexes[0].location = shovel_location else: board.rooms.append(self.create_Shovel_room(shovel_location)) board.flush_hex_data() return self._exit_cast(done=True)
def move(self): if self.current_board.actions < 1: raise InvalidMove( 'You cannot move because you have no more actions') player = self.get_current_player() adj_hexes = find_adjacent_hexes(self.current_board, player.hex) adj_hexes_wo_objs = [h for h in adj_hexes if h.occupant == None] hex = self.screen_input.choose_hexes( self.screen, adj_hexes_wo_objs, 'Click hex to move to', 'There is no adjacent hex for you to move', ) if hex == None: return False self.current_board.actions -= 1 self.current_board.move_object(player, from_hex=player.hex, to_hex=hex) return True
def choose_location(screen, axial_pos, prompt_text="Click a location", error_text="No valid locations"): if len(axial_pos) == 0: raise InvalidMove(error_text) elif len(axial_pos) == 1: return 0 # set prompt screen.info.text = prompt_text screen.toggle_action_buttons() while True: pos = get_click(screen) if pos in axial_pos: ret = axial_pos.index(pos) screen.info.error = None screen.toggle_action_buttons() return ret else: screen.info.error = 'Please click one of: {}'.format(' '.join( map(str, axial_pos)))
def choose_from_list(screen, ls, prompt_text='Choose one:', error_text='No valid choices', all_spells=None): if len(ls) == 0: raise InvalidMove(error_text) elif len(ls) == 1: return complete_choice(screen, ls[0]) if 'choice_idx' in screen.data: choice = screen.data['choice_idx'] screen.data.pop('choice_idx') try: ret = ls[int(choice) - 1] return complete_choice(screen, ret) except (ValueError, IndexError): screen.info.error = 'Please enter a number 1-{}'.format(len(ls)) return None elif 'click_spell_idx' in screen.data and all_spells: # This is a bit hacky, but will work for now # This depends on the order of board.spells matching the order on the frontend spell = all_spells[screen.data['click_spell_idx']] screen.data.pop('click_spell_idx') if spell not in ls: screen.info.error = 'Cannot cast {}'.format(spell) return None return complete_choice(screen, spell) else: prompt = prompt_text for idx, obj in enumerate(ls, start=1): prompt += ' ({}) {}'.format(idx, obj) screen.info.text = prompt screen.action_buttons_on = False
def choose_hexes(screen, hex_list, prompt_text="Choose a hex:", error_text="No valid hexes", return_index=False): if len(hex_list) == 0: raise InvalidMove(error_text) elif len(hex_list) == 1: if return_index: return complete_choice(screen, 0) else: return complete_choice(screen, hex_list[0]) # get a list of axial coordinates for the hexes screen.active_hexes = hex_list axial_coordinates = [location_to_axial(x.location) for x in hex_list] chosen_index = location_helper(screen, axial_coordinates, prompt_text) if return_index: return complete_choice(screen, chosen_index) elif chosen_index != None: return complete_choice(screen, hex_list[chosen_index]) else: return None # just being explicit
def cast_spell(self): eligible_spells = self.current_board.get_eligible_spells() # TODO[idea]: consider also allowing clicking on artwork to choose spell spell = self.screen.choice(0) or self.screen_input.choose_from_list( self.screen, eligible_spells, 'Choose spell to cast:', '{} does any spells that can be cast'.format( self.current_board.faction), all_spells=self.current_board.spells) if spell == None: return False try: done = spell.cast(self.current_board) except InvalidMove as error: spell.untap() # mark spell untapped since it did not complete raise InvalidMove(str(error)) # TODO: make sure no spells raise after mutating game state # TODO[idea]: store a 3rd version of board to reset action and use # that here return done