1"""Agent interface definitions for Connect-4.
3This module defines the minimal, structural interface that Connect-4 agents must
4implement in order to be compatible with the interactive GUI and other
5high-level components. The interface is expressed using ``typing.Protocol`` to
6enable static type checking without requiring inheritance or tight coupling
7between agents and consumers.
10from __future__
import annotations
12from typing
import Protocol, runtime_checkable
19 """Minimal interface a Connect-4 agent must implement to work with ``GuiC4``.
21 This interface is intentionally aligned with the public ``BitBully`` API,
22 but excludes BitBully-specific engine features such as opening-book handling,
23 node counters, transposition tables, and specialized search entry points.
26 - ``score_all_moves``: Provide integer evaluations for all *legal* moves.
27 - ``best_move``: Select one legal move using BitBully-compatible
28 tie-breaking semantics.
31 - Scores are integers where **larger values are better** for the side to move.
32 - The absolute scale is agent-defined.
33 - The GUI only relies on *relative ordering* and legality.
36 Minimal agent compatible with the `Connect4Agent` protocol:
40 from bitbully import Board
42 # Importing the Protocol is optional at runtime, but useful for:
43 # - static type checking (mypy / pyright)
44 # - documenting that this class satisfies the agent interface
45 from bitbully.agent_protocols import Connect4Agent
49 '''Agent that plays a random legal move.
51 This class does NOT inherit from ``Connect4Agent``.
52 It is compatible because it implements the required methods
53 with matching signatures (structural typing).
56 def score_all_moves(self, board: Board) -> dict[int, int]:
57 # Only legal columns may appear in the result.
58 # The GUI and other consumers rely on this contract.
59 return {c: 0 for c in board.legal_moves()}
61 def best_move(self, board: Board) -> int:
62 # Consumers may call only ``best_move`` if they are
63 # not interested in individual move scores.
64 return random.choice(board.legal_moves())
67 board = Board("332311")
69 # The variable annotation enforces the protocol at type-check time.
70 agent: Connect4Agent = RandomAgent()
72 move = agent.best_move(board)
78 """Score all legal moves for the given board state.
82 Current Connect-4 board position.
86 Mapping ``{column: score}`` for all *legal* columns (0..6).
87 Columns that are full or illegal **must not** appear in the mapping.
90 - Higher scores indicate better moves.
91 - The returned dictionary may contain between 0 and 7 entries.
99 """Return the best legal move (column index) for the side to move.
103 Current Connect-4 board position.
107 Selected column index in the range ``0..6``.
111 def score_move(self, board: Board, column: int, first_guess: int = 0) -> int:
112 """Evaluate a single legal move for the given board state.
114 This method is optional and not required by the GUI, but can be useful
115 for agents that support fine-grained move evaluation.
119 Current Connect-4 board position.
121 Column index (0..6) of the move to evaluate.
123 Optional initial guess for iterative or search-based agents.
124 Implementations may safely ignore this parameter.
128 Evaluation score for the given move.
int score_move(self, Board board, int column, int first_guess=0)
dict[int, int] score_all_moves(self, Board board)
int best_move(self, Board board)