Metadata-Version: 2.4
Name: othellopy
Version: 0.2.4
Summary: Small Othello/Reversi utilities and sample players for Python lessons.
Project-URL: Homepage, https://github.com/Hietan/othellopy
Project-URL: Repository, https://github.com/Hietan/othellopy
Project-URL: PyPI, https://pypi.org/project/othellopy/
Project-URL: Issues, https://github.com/Hietan/othellopy/issues
Project-URL: Changelog, https://github.com/Hietan/othellopy/blob/main/CHANGELOG.md
Project-URL: Security, https://github.com/Hietan/othellopy/security/policy
Author: Hietan
License-Expression: Apache-2.0
License-File: LICENSE
License-File: NOTICE
Keywords: board-game,othello,reversi
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Games/Entertainment :: Board Games
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: mypy>=1.14; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

# othellopy

[![PyPI](https://img.shields.io/pypi/v/othellopy.svg)](https://pypi.org/project/othellopy/)
[![Python](https://img.shields.io/pypi/pyversions/othellopy.svg)](https://pypi.org/project/othellopy/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](https://github.com/Hietan/othellopy/blob/main/LICENSE)
[![CI](https://github.com/Hietan/othellopy/actions/workflows/ci.yml/badge.svg)](https://github.com/Hietan/othellopy/actions/workflows/ci.yml)

`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`](https://pypi.org/project/othellopy/)
- Source repository: [`Hietan/othellopy`](https://github.com/Hietan/othellopy)
- Issues: <https://github.com/Hietan/othellopy/issues>

## Requirements

- Python 3.10 or later
- No runtime dependencies

## Installation

Install with pip:

```bash
pip install othellopy
```

Or add it to a uv project:

```bash
uv add othellopy
```

## Quick Start

Run a complete game between two sample players:

```python
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)
```

## Writing a Player

Subclass `BasePlayer` and implement `next_move()`.

```python
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.

## Runtime Player Check

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.

```python
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 two seconds 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.

Normal games also limit each `next_move()` call to two seconds by default. A
player that exceeds the limit forfeits, and the opponent wins. Pass
`move_timeout_seconds=None` when you intentionally need no timeout, such as
manual terminal play.

On Linux environments such as Google Colab, the timeout can interrupt
`next_move()` when it exceeds the limit. On Windows, a running `next_move()`
cannot always be stopped forcibly; if it eventually returns, elapsed time is
checked afterward, but an infinite loop may hang the check. Evaluation servers
should run submitted players in an isolated process or sandbox with their own
timeout.

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.

## Manual CLI Play

Use `ManualPlayer` to enter moves interactively from a terminal. Input is
two digits in row-column order, such as `07`.

```python
from othellopy.game import OthelloGame
from othellopy.players import BeginnerPlayer, ManualPlayer

result = OthelloGame(
    ManualPlayer,
    BeginnerPlayer,
    move_timeout_seconds=None,
).play()
```

Equivalent one-off command:

```bash
uv run python - <<'PY'
from othellopy.game import OthelloGame
from othellopy.players import BeginnerPlayer, ManualPlayer

result = OthelloGame(
    ManualPlayer,
    BeginnerPlayer,
    move_timeout_seconds=None,
).play()
print(result.winner_name, result.black_score, result.white_score)
PY
```

## Sample Players

Import sample players from `othellopy.players`:

```python
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 Display

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.

```python
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.

```python
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.

## Invalid Moves and Forfeits

If a player returns an invalid move, the player forfeits and the opponent wins.
The same happens when `next_move()` exceeds the two-second default timeout.
The game still returns a `GameResult`.

```python
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)
```

## API Overview

### `othellopy.core`

`Cell`
: `IntEnum` representing board cell values.

```python
Cell.EMPTY
Cell.BLACK
Cell.WHITE
```

`Board`
: 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`.

### `othellopy.board`

`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.

### `othellopy.players`

`BasePlayer`
: Base class for custom players. Provides:

- `color`
- `opponent_color`
- `get_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.

### `othellopy.game`

`OthelloGame(black_player, white_player, *, move_timeout_seconds=2.0)`
: Runs one game between two `BasePlayer` subclasses. Keyword arguments
  `black_player=...` and `white_player=...` are recommended for notebooks.
  `move_timeout_seconds=None` disables the per-move timeout.

`GameResult`
: Return value from `OthelloGame(...).play()`.

Fields:

- `winner: Cell`
- `winner_name: str`
- `black_score: int`
- `white_score: int`
- `board: Board`
- `moves: list[tuple[Cell, int, int]]`
- `turns: list[TurnRecord]`
- `forfeit: ForfeitRecord | None`

`TurnRecord`
: Per-turn debug information.

Fields:

- `color: Cell`
- `board: Board`
- `valid_moves: list[tuple[int, int]]`
- `move: tuple[int, int] | None`
- `black_score: int`
- `white_score: int`

`ForfeitRecord`
: Invalid-move or timeout forfeit information.

Fields:

- `color: Cell`
- `move: object`
- `valid_moves: list[tuple[int, int]]`
- `board: Board`
- `message: str`

### `othellopy.validation`

`test_player(player_class, *, max_seconds=2.0) -> bool`
: Returns `True` when a submitted player class passes runtime player tests.

`test_player_detail(player_class, *, max_seconds=2.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: bool`
- `issues: list[ValidationIssue]`
- `errors: list[ValidationIssue]`
- `warnings: list[ValidationIssue]`
- `details: dict[str, object]`

`ValidationIssue`
: One validation issue.

Fields:

- `code: str`
- `message: str`
- `severity: ValidationSeverity`

## Development

```bash
uv venv
uv pip install -e ".[dev]"
```

Run checks:

```bash
uv run --extra dev ruff format --check .
uv run --extra dev ruff check .
uv run --extra dev mypy .
uv run --extra dev pytest
uv build
```

## Release Policy

`othellopy` 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`](https://github.com/Hietan/othellopy/blob/main/RELEASE.md).

## Contributing

Contributions must follow the GitFlow rules in
[`CONTRIBUTING.md`](https://github.com/Hietan/othellopy/blob/main/CONTRIBUTING.md):

- `feat/*` pull requests target `dev`.
- `release/*` pull requests target `main`.
- Direct pushes to `main` and `dev` are not allowed.
- Required checks must pass before merge.

## Security

Please do not report vulnerabilities in public issues. See
[`SECURITY.md`](https://github.com/Hietan/othellopy/blob/main/SECURITY.md).

## License

Licensed under the Apache License, Version 2.0. See
[`LICENSE`](https://github.com/Hietan/othellopy/blob/main/LICENSE).
