def parse_move_string(board, move_string) -> Move: if board is None: raise ValueError("board cannot be None") if move_string is None or move_string.isspace(): raise ValueError("move_string cannot be None") move_string = move_string.strip() try: # Attempt to parse as an algebraic move return Move(move_string=move_string) except ValueError: pass move_string_parts = list(filter(None, move_string.split(' '))) moving_piece = EnumUtils.parse_short_name(move_string_parts[0]) if board.board_state == "NotStarted": # First move is on the origin return Move(moving_piece, Position.origin) target_string = move_string_parts[1].replace('-', '').replace('/', '').replace( '\\', '') target_piece = EnumUtils.parse_short_name(target_string) separator_idx = index_of_any(move_string_parts[1], ['-', '/', '\\']) if separator_idx < 0: # Putting a piece on top of another return Move(moving_piece, board.get_piece_position(target_piece).get_above()) separator = move_string_parts[1][separator_idx] target_position = board.get_piece_position(target_piece) if separator_idx == 0: # Moving piece on the left-hand side of the target piece if separator == '-': return Move(moving_piece, target_position.neighbour_at(directions["UpLeft"])) elif separator == '/': return Move(moving_piece, target_position.neighbour_at(directions["DownLeft"])) elif separator == '\\': return Move(moving_piece, target_position.neighbour_at(directions["Up"])) elif separator_idx == len(target_string): # Moving piece on the right-hand side of the target piece if separator == '-': return Move(moving_piece, target_position.neighbour_at(directions["DownRight"])) elif separator == '/': return Move(moving_piece, target_position.neighbour_at(directions["UpRight"])) elif separator == '\\': return Move(moving_piece, target_position.neighbour_at(directions["Down"])) return None
def _get_valid_placements(self, target_piece): valid_moves = MoveSet() target_colour = self.current_turn_colour if target_piece.colour != target_colour: return valid_moves if self._cached_valid_placement_positions is None or len( self._cached_valid_placement_positions) == 0: self._cached_valid_placement_positions = set() self._visited_placements.clear() for i in range(EnumUtils.num_piece_names): piece = self._pieces[i] valid_piece = piece is not None and piece.in_play # Piece is in play, on the top and is the right color, look through neighbors if valid_piece and self.piece_is_on_top( piece) and piece.colour == target_colour: bottom_position = self.get_piece_on_bottom(piece).position self._visited_placements.add(bottom_position) for j in range(EnumUtils.num_directions): neighbor = bottom_position.neighbour_at(j) # Neighboring position is a potential, verify its neighbors are empty or same color old_len = len(self._visited_placements) self._visited_placements.add(neighbor) if len(self._visited_placements ) > old_len and not self.has_piece_at(neighbor): valid_placement = True for k in range(EnumUtils.num_directions): surrounding_position = neighbor.neighbour_at(k) surrounding_piece = self.get_piece_on_top_internal( surrounding_position) if surrounding_piece is not None and surrounding_piece.colour != target_colour: valid_placement = False break if valid_placement: self._cached_valid_placement_positions.add( neighbor) self.valid_move_cache_metrics_set["ValidPlacements"].miss() else: self.valid_move_cache_metrics_set["ValidPlacements"].hit() for valid_placement in self._cached_valid_placement_positions: valid_moves.add( Move(piece_name=target_piece.piece_name, position=valid_placement)) return valid_moves
def __init__(self, size=None, move_set_string=None, moves_list=None): self.is_locked = None self._moves = [] if moves_list: self._moves = [m for m in moves_list] return if not move_set_string: self._moves = [Move()] * size if size else [] self.is_locked = False return if move_set_string.isspace(): raise ValueError("Invalid move_set_string.") split = move_set_string.split(';') for s in split: parse_move = Move(move_string=s) self._moves.append(parse_move)
def get_valid_beetle_movements(self, target_piece): valid_moves = MoveSet() # Look in all directions for direction in EnumUtils.directions.keys(): new_position = target_piece.position.neighbour_at(direction) top_neighbor = self.get_piece_on_top_internal(new_position) # Get positions to left and right or direction we're heading left_of_target = EnumUtilsCls.left_of(direction) right_of_target = EnumUtilsCls.right_of(direction) left_neighbor_position = target_piece.position.neighbour_at( left_of_target) right_neighbor_position = target_piece.position.neighbour_at( right_of_target) top_left_neighbor = self.get_piece_on_top_internal( left_neighbor_position) top_right_neighbor = self.get_piece_on_top_internal( right_neighbor_position) # At least one neighbor is present current_height = target_piece.position.stack + 1 destination_height = top_neighbor.position.stack + 1 if top_neighbor is not None else 0 top_left_neighbor_height = top_left_neighbor.position.stack + 1 if top_left_neighbor is not None else 0 top_right_neighbor_height = top_right_neighbor.position.stack + 1 if top_right_neighbor is not None else 0 # "Take-off" beetle current_height -= 1 same_tier = current_height == 0 and destination_height == 0 go_down = destination_height < top_left_neighbor_height and destination_height < top_right_neighbor_height are_down = current_height < top_left_neighbor_height and current_height < top_right_neighbor_height if not (same_tier and top_left_neighbor_height == 0 and top_right_neighbor_height == 0): # Logic from http:#boardgamegeek.com/wiki/page/Hive_FAQ#toc9 if not (go_down and are_down): up_one_tier = new_position.stack == destination_height target_position = new_position if up_one_tier else top_neighbor.position.get_above( ) target_move = Move(piece_name=target_piece.piece_name, position=target_position) valid_moves.add(target_move) return valid_moves
def get_valid_slides_rec(self, target, current_pos, visited_positions, current_range, valid_moves, max_range=None): if max_range is None or current_range < max_range: # Optimize loop: neighbour_at = current_pos.neighbour_at right_of = EnumUtilsCls.right_of left_of = EnumUtilsCls.left_of has_piece_at = self.has_piece_at vm_add = valid_moves.add vp_add = visited_positions.add get_valid_slides_rec = self.get_valid_slides_rec for slide_direction in EnumUtils.directions: slide_position = neighbour_at(slide_direction) if slide_position not in visited_positions and not has_piece_at( slide_position): # Slide position is open right = right_of(slide_direction) left = left_of(slide_direction) right_occupied = has_piece_at(neighbour_at(right)) left_occupied = has_piece_at(neighbour_at(left)) if right_occupied != left_occupied: # Hive is not "tight" # Can slide into slide position move = Move(piece_name=target, position=slide_position) old_len = valid_moves.count vm_add(move) if valid_moves.count > old_len: # Sliding from this position has not been tested yet vp_add(move.position) get_valid_slides_rec(target, slide_position, visited_positions, current_range + 1, valid_moves, max_range)
def get_valid_grasshopper_movements(self, target_piece): valid_moves = MoveSet() starting_position = target_piece.position for direction in EnumUtils.directions: landing_position = starting_position.neighbour_at(direction) distance = 0 while self.has_piece_at(landing_position): # Jump one more in the same direction landing_position = landing_position.neighbour_at(direction) distance += 1 if distance > 0: # Can only move if there's at least one piece in the way move = Move(piece_name=target_piece.piece_name, position=landing_position) valid_moves.add(move) return valid_moves
def get_valid_moves_internal(self, target_piece): # Optimize: bug_type = target_piece.bug_type colour = target_piece.colour in_hand = target_piece.in_hand in_play = target_piece.in_play piece_name = target_piece.piece_name if target_piece is not None and self.game_in_progress: if colour == self.current_turn_colour and self.placing_piece_in_order( target_piece): not_white_queen = in_hand and piece_name != "WhiteQueenBee" not_black_queen = in_hand and piece_name != "BlackQueenBee" not_last_moved = piece_name != self.last_piece_moved and in_play # Optimize: valid_moves = MoveSet() add = valid_moves.add neighbour_at = PositionCls.origin.neighbour_at origin = PositionCls.origin # First move must be at the origin and not the White Queen Bee if self.current_turn == 0 and colour == "White" and not_white_queen: add(Move(piece_name=piece_name, position=origin)) return valid_moves # Second move must be around the origin and not the Black Queen Bee elif self.current_turn == 1 and colour == "Black" and not_black_queen: for i in range(EnumUtils.num_directions): neighbor = neighbour_at(i) add(Move(piece_name=piece_name, position=neighbor)) return valid_moves elif ( in_hand and (self.current_player_turn != 4 or # Normal turn OR ( self.current_player_turn == 4 and # Turn 4 and AND ( self.current_turn_queen_in_play or # Queen is in play or you're trying to play it (not self.current_turn_queen_in_play and target_piece.bug_type == "QueenBee"))))): # Look for valid new placements return self._get_valid_placements(target_piece) elif not_last_moved and self.current_turn_queen_in_play and self.piece_is_on_top( target_piece): if self.can_move_without_breaking_hive(target_piece): # Look for basic valid moves of played pieces who can move if bug_type == "QueenBee": add( self.get_valid_queen_bee_movements( target_piece)) elif bug_type == "Spider": add(self.get_valid_spider_movements(target_piece)) elif bug_type == "Beetle": add(self.get_valid_beetle_movements(target_piece)) elif bug_type == "Grasshopper": add( self.get_valid_grasshopper_movements( target_piece)) elif bug_type == "SoldierAnt": add( self.get_valid_soldier_ant_movements( target_piece)) return valid_moves return MoveSet()