Skip to content

Dummy

patiencepilot.solvers.dummy

Trivial solver implementations.

DummySolver dataclass

Deterministic solver that recommends the first visible legal move.

Source code in src/patiencepilot/solvers/dummy.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@dataclass(frozen=True, slots=True)
class DummySolver:
    """Deterministic solver that recommends the first visible legal move."""

    name: str = "dummy"

    def suggest(self, view: PlayerView, *, limit: SearchLimit | None = None) -> Advice:
        """Return the first legal move available in ``view``.

        The solver derives legal moves only from player-visible information:
        foundations, waste, visible tableau cards, stock count, and redeal
        metadata. It does not reconstruct or inspect hidden card identities.
        """
        moves = visible_klondike_moves(view)
        if not moves:
            return Advice(recommendations=(), solver_name=self.name, limit=limit, nodes_searched=0, depth_reached=0)
        return Advice(
            recommendations=(RankedMove(move=moves[0], rank=1),),
            solver_name=self.name,
            limit=limit,
            nodes_searched=len(moves),
            depth_reached=0,
        )

suggest

suggest(
    view: PlayerView, *, limit: SearchLimit | None = None
) -> Advice

Return the first legal move available in view.

The solver derives legal moves only from player-visible information: foundations, waste, visible tableau cards, stock count, and redeal metadata. It does not reconstruct or inspect hidden card identities.

Source code in src/patiencepilot/solvers/dummy.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
def suggest(self, view: PlayerView, *, limit: SearchLimit | None = None) -> Advice:
    """Return the first legal move available in ``view``.

    The solver derives legal moves only from player-visible information:
    foundations, waste, visible tableau cards, stock count, and redeal
    metadata. It does not reconstruct or inspect hidden card identities.
    """
    moves = visible_klondike_moves(view)
    if not moves:
        return Advice(recommendations=(), solver_name=self.name, limit=limit, nodes_searched=0, depth_reached=0)
    return Advice(
        recommendations=(RankedMove(move=moves[0], rank=1),),
        solver_name=self.name,
        limit=limit,
        nodes_searched=len(moves),
        depth_reached=0,
    )

visible_klondike_moves

visible_klondike_moves(
    view: PlayerView,
) -> tuple[Move, ...]

Return Klondike moves legal from player-visible state.

Source code in src/patiencepilot/solvers/dummy.py
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
def visible_klondike_moves(view: PlayerView) -> tuple[Move, ...]:
    """Return Klondike moves legal from player-visible state."""
    if view.variant != "klondike":
        msg = f"dummy solver only supports 'klondike', got {view.variant!r}"
        raise UnsupportedVariantError(msg)

    moves: list[Move] = []
    if view.stock_count:
        moves.append(DrawFromStock())
    elif view.waste and _can_recycle(view):
        moves.append(RecycleWaste())

    if view.waste:
        waste_card = view.waste[-1]
        if _can_move_to_foundation(waste_card, view.foundation(waste_card.suit)):
            moves.append(WasteToFoundation())
        for destination in range(N_TABLEAU_COLUMNS):
            if _can_place_on_tableau(waste_card, view.tableau[destination]):
                moves.append(WasteToTableau(destination=destination))

    for source, column in enumerate(view.tableau):
        top_card = _top_visible_card(column)
        if top_card is not None and _can_move_to_foundation(top_card, view.foundation(top_card.suit)):
            moves.append(TableauToFoundation(source=source))

        for start_index in range(len(column)):
            moving_stack = column[start_index:]
            if not _is_movable_tableau_stack(moving_stack):
                continue
            moving_card = moving_stack[0].visible_card
            if moving_card is None:
                continue
            count = len(moving_stack)
            for destination in range(N_TABLEAU_COLUMNS):
                if source == destination:
                    continue
                if _can_place_on_tableau(moving_card, view.tableau[destination]):
                    moves.append(TableauToTableau(source=source, destination=destination, count=count))

    return tuple(moves)