Compare commits

...

2 commits

4 changed files with 62 additions and 15 deletions

View file

@ -89,10 +89,13 @@ class Board:
old_row = self.rows[row]
self.rows[row] = old_row[:col] + value + old_row[col+1:]
def click(self, index: int) -> Self:
def click(self, index: int) -> Self | None:
# check is clickable
row, col = index // self.width, index % self.width
assert self.rows[row][col] == "1", f"{index} is not clickable!"
if self.rows[row][col] != "1":
# button is not clickable
return None
# get neighbor positions
nb_rc = [

View file

@ -1,18 +1,23 @@
#!/usr/bin/python3
from .board import Board
from .solver import solve
def main() -> None:
board = Board.default_puzzle
print(board)
print(board.solution_class, board.binary_repr)
solution = solve(
Board.default_puzzle,
lambda state: state.solution_class == (3, 1, 2),
)
for btn in (2, 4, 5, 7): # 2 is not a regular button, but alas
if solution is None:
print("Unsolvable!")
return
for btn, state in solution:
print(f"clicking button {btn} ...")
board.click(btn)
print(board)
print(board.solution_class, board.binary_repr)
print(state)
print(state.solution_class, state.binary_repr)
if __name__ == "__main__":

View file

@ -0,0 +1,39 @@
from typing import Callable
from .board import Board
def solve(
state: Board,
is_solved: Callable[[Board], bool],
*,
history: list[tuple[int, Board]] | None = None,
) -> list[tuple[int, Board]] | None:
if history is None:
# create starting state
history = [(-1, state)]
if is_solved(state):
return history
for button in state.buttons:
# try to click all buttons
next = state.click(button)
if next is not None:
# click successful
binary_history = (
state.binary_repr
for _, state in history
)
if next.binary_repr not in binary_history:
# "next" is a new state
solution = solve(
next, is_solved,
history=[*history, (button, next)]
)
if solution is not None:
return solution

View file

@ -1,5 +1,3 @@
import pytest
from pigeon_magnet_solver.board import Board
@ -13,9 +11,11 @@ def test_default():
assert board.binary_repr == (4, 11, 3), "Wrong binary repr"
assert board.solution_class == (1, 3, 2), "Wrong solution class"
assert board.click(4).binary_repr == (4, 14, 3)
assert board.click(5).binary_repr == (4, 11, 6)
assert (board2 := board.click(4)) is not None
assert board2.binary_repr == (4, 14, 3)
assert (board2 := board.click(5)) is not None
assert board2.binary_repr == (4, 11, 6)
for idx in (3, 7):
with pytest.raises(AssertionError):
board.click(idx)
assert board.click(idx) is None