Board manager¶
BoardState¶
BoardManager¶
- exception BoxGoalSwitchError¶
- exception CellAlreadyOccupiedError¶
- class BoardManager(board: BoardGraph, boxorder: str = '', goalorder: str = '')¶
Memoizes, tracks and updates positions of all pieces.
assigns and maintains piece IDs
manages Sokoban+ piece IDs
moves pieces while preserving their IDs
checks if board is solved
BoardManager
implements efficient means to inspect positions of pushers, boxes and goals. To be able to do that, pieces must be uniquely identified.BoardManager
assigns unique numerical ID to each individual piece. This ID can then be used to refer to that piece in various contexts.How are piece IDs assigned? Start scanning game board from top left corner, going row by row, from left to the right. First encountered box will get
box.id = Config.DEFAULT_ID
, second onebox.id = Config.DEFAULT_ID + 1
, etc… Same goes for pushers and goals.BoardManager
also ensures that piece IDs remain unchanged when pieces are moved on board. This is best illustrated by example. Let’s construct a board with 2 boxes.>>> from sokoenginepy.game import BoardGraph, BoardManager, Config >>> from sokoenginepy.io import SokobanPuzzle >>> data = "\n".join([ ... "######", ... "# #", ... "# $ #", ... "# $.#", ... "#@ . #", ... "######", ... ]) >>> puzzle = SokobanPuzzle(board=data) >>> board = BoardGraph(puzzle) >>> manager = BoardManager(board) >>> manager.boxes_positions {1: 14, 2: 21}
We can edit the board (simulating movement of box ID 2) directly, without using the manager. If we attach manager to that board after edit, we get expected but wrong ID assigned to the box we’d just “moved”:
>>> board[21] = " " >>> board[9] = "$" >>> print(board.to_board_str()) ###### # $ # # $ # # .# #@ . # ###### >>> manager = BoardManager(board) >>> manager.boxes_positions {1: 9, 2: 14}
Moving box through manager (via
BoardManager.move_box_from
) would’ve preserved ID of moved box. Same goes for pushers.Initial board
Box edited without manager
Box moved through manager
Note
Movement methods in
BoardManager
only implement board updates. They don’t implement full game logic. For game logic seeMover
- Parameters
board – board to mange
boxorder – Sokoban+ data (see
SokobanPlus
)goalorder – Sokoban+ data (see
SokobanPlus
)
- See:
- disable_sokoban_plus()¶
Disables using Sokoban+ rules for this board.
See also
- enable_sokoban_plus()¶
Enables using Sokoban+ rules for this board.
Enabling these, changes victory condition for given board (return value of
is_solved
).See also
- move_box(box_id: int, to_new_position: int)¶
Updates board state and board cells with changed box position.
- Raises
KeyError – there is no box on
old_position
CellAlreadyOccupiedError – there is an obstacle ( wall/box/another pusher) on
to_new_position
IndexError – of board
to_new_position
- move_box_from(old_position: int, to_new_position: int)¶
Updates board state and board cells with changed box position.
- Raises
KeyError – there is no box on
old_position
CellAlreadyOccupiedError – there is an obstacle ( wall/box/pusher) on
to_new_position
IndexError – of board
old_position
orto_new_position
- move_pusher(pusher_id: int, to_new_position: int)¶
Updates board state and board cells with changed pusher position.
- Raises
KeyError – there is no pusher with ID
pusher_id
CellAlreadyOccupiedError – there is a pusher already on
to_new_position
IndexError – of board
to_new_position
Note
Allows placing a pusher onto position occupied by box. This is for cases when we switch box/goals positions in reverse solving mode. In this situation it is legal for pusher to end up standing on top of the box. Game rules say that for these situations, first move(s) must be jumps.
- move_pusher_from(old_position: int, to_new_position: int)¶
Updates board state and board cells with changed pusher position.
- Raises
KeyError – there is no pusher on
old_position
CellAlreadyOccupiedError – there is an obstacle ( wall/box/another pusher) on
to_new_position
IndexError – of board
old_position
orto_new_position
- solutions() Iterable[BoardState] ¶
Generator for all configurations of boxes that result in solved board.
Note
Result set depends on
is_sokoban_plus_enabled
.
- switch_boxes_and_goals()¶
Switches positions of boxes and goals pairs. This is used by
Mover
inSolvingMode.REVERSE
.- Raises
BoxGoalSwitchError – when board can’t be switched. These kinds of boards are usually also not
is_playable
.
- property board: BoardGraph¶
- property boxes_positions: Dict[int, int]¶
Mapping of boxes’ IDs to the corresponding board positions, ie.
{1: 42, 2: 24}
- property boxorder: str¶
See Also:
SokobanPlus.boxorder
- property goalorder: str¶
See Also:
SokobanPlus.goalorder
- property goals_positions: Dict[int, int]¶
Mapping of boxes’ IDs to the corresponding board positions, ie.
{1: 42, 2: 24}
- property is_sokoban_plus_enabled: bool¶
Are Sokoban+ rule enabled for current game?
See also
enable_sokoban_plus
- property is_solved: bool¶
Checks for game victory.
Classic
victory is any board position in which each box is positioned on top of each goalSokoban+
victory is board position where each box is positioned on top of each goal with the same Sokoban+ ID as that box
Result depends on
is_sokoban_plus_enabled
.
- property pushers_positions: Dict[int, int]¶
Mapping of pushers’ IDs to the corresponding board positions, ie.
{1: 42, 2: 24}
- property state: BoardState¶
Snapshots current board state.
HashedBoardManager¶
- class HashedBoardManager(board: BoardGraph, boxorder: str = '', goalorder: str = '')¶
Bases:
BoardManager
Board manager that also manages Zobrist hashing.
Adds Zobrist hashing on top of
BoardManager
and keeps it up to date when pieces are moved.Zobrist hash is 64b integer hash derived from positions of all boxes and pushers on board.
This hash is “resistant” to moving board pieces. Moving one pusher and box will change board hash. Undoing that move will return hash to previous value.
This kind of hash is reliable way for identifying board positions. Sokoban solvers might need this to operate.
When hashing, boxes with same Sokoban+ ID are treated as equal meaning that if two of these boxes switch position, hash will not change. This also means that for the same board, hash is different when Sokoban+ is enabled from the one when it is disabled.
Pushers are all treated equal, meaning that if two pushers switch position, hash will not change
Notes
enabling/disabling Sokoban+ rehashes the board state
moving pieces doesn’t need to re-hash whole board, it updates hash incrementally
undoing piece movement also updates hash incrementally with additional feature that returning to previous board state will return to previous hash value
- disable_sokoban_plus()¶
Disables using Sokoban+ rules for this board.
See also
- enable_sokoban_plus()¶
Enables using Sokoban+ rules for this board.
Enabling these, changes victory condition for given board (return value of
is_solved
).See also
- external_state_hash(board_state) int ¶
Calculates Zobrist hash of given
board_state
as if thatboard_state
was applied to initialboard
(to board where no movement happened).board_state
must meet following requirement:len(board_state.boxes_positions) == self.boxes_count and len(board_state.boxes_positions) == self.goals_count
- Returns
Value of hash or
BoardState.NO_HASH
if it can’t be calculated
- property boxorder: str¶
See Also:
SokobanPlus.boxorder
- property goalorder: str¶
See Also:
SokobanPlus.goalorder
- property initial_state_hash: int¶
Zobrist hash of initial board state (before any movement happened).
- property is_solved: bool¶
Checks for game victory.
Classic
victory is any board position in which each box is positioned on top of each goalSokoban+
victory is board position where each box is positioned on top of each goal with the same Sokoban+ ID as that box
Result depends on
is_sokoban_plus_enabled
.
- property state: BoardState¶
Snapshots current board state.
SokobanPlus¶
- exception SokobanPlusDataError¶
- class SokobanPlus(pieces_count: int, boxorder: str = '', goalorder: str = '')¶
Manages Sokoban+ data for game board.
Sokoban+ rules
In this variant of game rules, each box and each goal on board get number tag (color). Game objective changes slightly: board is considered solved only when each goal is filled with box of the same tag. So, for example goal tagged with number 9 must be filled with any box tagged with number 9.
Multiple boxes and goals may share same plus id, but the number of boxes with one plus id must be equal to number of goals with that same plus id. There is also default plus id that represents non tagged boxes and goals.
Sokoban+ ids for given board are defined by two strings called goalorder and boxorder. For example, boxorder “13 24 3 122 1” would give plus_id = 13 to box id = 1, plus_id = 24 to box ID = 2, etc…
Valid Sokoban+ id sequences
Boxorder and goalorder must define ids for equal number of boxes and goals. This means that in case of boxorder assigning plus id “42” to two boxes, goalorder must also contain number 42 twice.
Sokoban+ data parser accepts any positive integer as plus id.
- Parameters
boxorder – Space separated integers describing Sokoban+ IDs for boxes
goalorder – Space separated integers describing Sokoban+ IDs for goals
pieces_count – Total count of boxes/goals on board
- box_plus_id(for_box_id: int) int ¶
Get Sokoban+ ID for box.
- Returns
If Sokoban+ is enabled returns Sokoban+ ID of a box. If not, returns
DEFAULT_PLUS_ID
- Raises
KeyError – No box with ID
for_box_id
, but only if i Sokoban+ is enabled
- goal_plus_id(for_goal_id: int) int ¶
Get Sokoban+ ID for goal.
- Returns
If Sokoban+ is enabled returns Sokoban+ ID of a goal. If not, returns
DEFAULT_PLUS_ID
- Raises
KeyError – No goal with ID
for_goal_id
, but only if Sokoban+ is enabled
- DEFAULT_PLUS_ID: Final[int] = 0¶
Sokoban+ ID for pieces that don’t have one or when Sokoban+ is disabled.
Original Sokoban+ implementation used number 99 for default plus ID. As there can be more than 99 boxes on board, sokoenginepy changes this detail and uses
DEFAULT_PLUS_ID
as default plus ID. When loading older puzzles with Sokoban+, legacy default value is converted transparently.
- property is_enabled: bool¶
Raises:
SokobanPlusDataError
: Trying to enable invalid Sokoban+
- property is_validated¶