othellopy is a small Python package for Othello/Reversi exercises. It
provides a board model, a game runner, player test helpers, and sample
players ranging from random play to alpha-beta search.
The package is currently 0.x alpha software. Public APIs may change before
1.0.0.
Official distribution:
- PyPI package:
othellopy - Source repository:
Hietan/othellopy - Issues: https://github.com/Hietan/othellopy/issues
- Python 3.10 or later
- No runtime dependencies
Install with pip:
pip install othellopyOr add it to a uv project:
uv add othellopyRun a complete game between two sample players:
from othellopy.board import display_board
from othellopy.game import OthelloGame
from othellopy.players import BeginnerPlayer, IntermediatePlayer
result = OthelloGame(BeginnerPlayer, IntermediatePlayer).play()
print(result.winner_name)
print(result.black_score, result.white_score)
display_board(result.board)Subclass BasePlayer and implement next_move().
from othellopy.core import Board, Cell, Move
from othellopy.players import BasePlayer
class MyPlayer(BasePlayer):
def next_move(self, board: Board) -> Move:
return self.get_moves(board)[0]Coordinates are zero-based (row, col) pairs. next_move() is called only
when the player has at least one legal move.
Use test_player() in Google Colab to check that a custom player actually runs
correctly. This is the dynamic part of validation; browser-side static analysis
can separately check imports and source-code rules before students run Colab.
from othellopy.validation import test_player, test_player_detail
if test_player(MyPlayer):
print("Basic player tests passed")
else:
result = test_player_detail(MyPlayer)
for issue in result.errors:
print(issue.code, issue.message)The runtime player tests check that the class inherits from BasePlayer, can be
constructed for black and white, returns legal moves on many board states, and
returns within one second by default. print() and display_board() are
allowed, so students can debug in Google Colab while running the tests.
The board passed to next_move() is an isolated copy during player tests and game
play, so accidental board edits do not change the real game state. Students
should still treat the board as read-only because only the returned move is used.
Runtime player tests do not inspect source code and do not enforce import
policy. Use a separate static analyzer, for example in a Next.js client, to
reject external packages such as numpy or risky APIs such as open(),
input(), eval(), and exec().
This is a runtime screen, not a security sandbox. Evaluation servers should run the same runtime player tests again and execute submitted players in an isolated sandbox.
Use ManualPlayer to enter moves interactively from a terminal. Input is
two digits in row-column order, such as 07.
from othellopy.game import OthelloGame
from othellopy.players import BeginnerPlayer, ManualPlayer
result = OthelloGame(ManualPlayer, BeginnerPlayer).play()Equivalent one-off command:
uv run python - <<'PY'
from othellopy.game import OthelloGame
from othellopy.players import BeginnerPlayer, ManualPlayer
result = OthelloGame(ManualPlayer, BeginnerPlayer).play()
print(result.winner_name, result.black_score, result.white_score)
PYImport sample players from othellopy.players:
from othellopy.players import (
AdvancedPlayer,
BeginnerPlayer,
IntermediatePlayer,
ManualPlayer,
)BeginnerPlayer: chooses a legal move randomly.IntermediatePlayer: scores legal moves with a simple one-ply heuristic.AdvancedPlayer: searches ahead with alpha-beta pruning.ManualPlayer: asks for row-column input in a CLI.
Board helpers render stones as ⚫️ and ⚪️ when the output encoding supports
them. If the output cannot encode those emoji, display falls back to B and
W.
Use display_board() for both notebooks and terminals. In Google Colab or
Jupyter, it renders a fixed HTML table so emoji stones stay aligned even when
the notebook font gives emoji a different display width from ASCII characters.
In a terminal, it falls back to readable text output.
from othellopy.board import display_board, initial_board
display_board(initial_board())print_board() is kept as a compatibility helper when you explicitly want text
output.
from othellopy.board import initial_board, print_board
print_board(initial_board())Use board_to_str(..., use_emoji=False) when you need stable ASCII output.
If a player returns an invalid move, the player forfeits and the opponent wins.
The game still returns a GameResult.
result = OthelloGame(
black_player=MyPlayer,
white_player=BeginnerPlayer,
).play()
if result.forfeit is not None:
print(result.winner_name)
print(result.forfeit.color)
print(result.forfeit.move)
print(result.forfeit.valid_moves)Cell
: IntEnum representing board cell values.
Cell.EMPTY
Cell.BLACK
Cell.WHITEBoard
: Type alias for list[list[Cell]].
Move
: Type alias for tuple[int, int].
opponent(cell: Cell) -> Cell
: Returns Cell.WHITE for Cell.BLACK, and Cell.BLACK for Cell.WHITE.
Passing Cell.EMPTY raises ValueError.
initial_board() -> Board
: Returns the standard 8x8 initial Othello board.
copy_board(board: Board) -> Board
: Returns a row-by-row copy of a board.
board_to_str(board: Board, *, use_emoji: bool | None = None) -> str
: Converts a board to readable text.
board_to_html(board: Board, *, use_emoji: bool | None = None) -> str
: Converts a board to a fixed-cell HTML table for notebook display.
display_board(board: Board, *, use_emoji: bool | None = None) -> None
: Displays a board as HTML in IPython notebooks, falling back to text output in
terminals. Prefer this for examples and student code.
print_board(board: Board, *, use_emoji: bool | None = None) -> None
: Prints a readable board as text. Kept for compatibility and explicit text
output.
BasePlayer
: Base class for custom players. Provides:
coloropponent_colorget_moves(board)is_valid_move(board, row, col)get_flips(board, row, col)
BeginnerPlayer
: Random legal move player.
IntermediatePlayer
: Simple heuristic player.
AdvancedPlayer
: Alpha-beta search player. Accepts depth= in the constructor.
ManualPlayer
: Interactive CLI player. Accepts optional input_func, output, and
use_emoji parameters for testing or custom IO.
OthelloGame(black_player, white_player)
: Runs one game between two BasePlayer subclasses. Keyword arguments
black_player=... and white_player=... are recommended for notebooks.
GameResult
: Return value from OthelloGame(...).play().
Fields:
winner: Cellwinner_name: strblack_score: intwhite_score: intboard: Boardmoves: list[tuple[Cell, int, int]]turns: list[TurnRecord]forfeit: ForfeitRecord | None
TurnRecord
: Per-turn debug information.
Fields:
color: Cellboard: Boardvalid_moves: list[tuple[int, int]]move: tuple[int, int] | Noneblack_score: intwhite_score: int
ForfeitRecord
: Invalid-move forfeit information.
Fields:
color: Cellmove: objectvalid_moves: list[tuple[int, int]]board: Boardmessage: str
test_player(player_class, *, max_seconds=1.0) -> bool
: Returns True when a submitted player class passes runtime player tests.
test_player_detail(player_class, *, max_seconds=1.0) -> ValidationResult
: Returns detailed runtime errors and runtime case details.
validate(...) / validate_detail(...)
: Compatibility aliases for test_player(...) and test_player_detail(...).
ValidationResult
: Detailed validation result.
Fields:
passed: boolissues: list[ValidationIssue]errors: list[ValidationIssue]warnings: list[ValidationIssue]details: dict[str, object]
ValidationIssue
: One validation issue.
Fields:
code: strmessage: strseverity: ValidationSeverity
uv venv
uv pip install -e ".[dev]"Run checks:
uv run --extra dev ruff check .
uv run --extra dev mypy src/othellopy
uv run --extra dev pytest
uv buildothellopy is published on PyPI from GitHub tags named vX.Y.Z.
Published PyPI files are immutable, so a broken release is fixed by publishing
a newer version instead of replacing an existing one.
The release checklist is documented in
RELEASE.md.
Contributions must follow the GitFlow rules in
CONTRIBUTING.md:
feat/*pull requests targetdev.release/*pull requests targetmain.- Direct pushes to
mainanddevare not allowed. - Required checks must pass before merge.
Please do not report vulnerabilities in public issues. See
SECURITY.md.
Licensed under the Apache License, Version 2.0. See
LICENSE.