I/O (src/io/)

Serialisation and file-format support. Depends on core/; does not depend on ui/.

Game records (record.jl)

GameRecord is the primary in-memory and on-disk representation of a finished or in-progress game.

Reversi.GameRecordType
GameRecord

Stores the complete move history of a Reversi game.

Fields:

  • moves::Vector{String} – ordered list of moves in standard notation or "pass".
  • result::IntBLACK, WHITE, EMPTY (draw), or IN_PROGRESS.
source
Reversi.load_gameMethod
load_game(filepath) -> GameRecord

Read a GameRecord from a file written by save_game.

Throws ArgumentError if the file is missing required fields or has an unrecognised format, so callers get an explicit error rather than silently receiving an empty record.

source
Reversi.replay_gameMethod
replay_game(record; verbose=false, strict=false) -> ReversiGame

Replay all moves stored in record on a fresh ReversiGame and return the final state.

  • verbose=true – print each move to stdout.
  • strict=true – throw ArgumentError on the first invalid move instead of silently ignoring it. Recommended when replaying external data.
source
Reversi.save_gameMethod
save_game(record, filepath)

Write a GameRecord to filepath.

Format:

MOVES: d3 c5 f4 ...
RESULT: BLACK | WHITE | DRAW | IN_PROGRESS
source
Reversi.validate_recordMethod
validate_record(record) -> Union{Nothing, String}

Replay record on a fresh board and verify every move is legal. Returns nothing if the record is valid, or a String describing the first error found (move number, token, and reason).

source

File format

MOVES: f5 d6 c5 f4 e3 d3 c4 ...
RESULT: BLACK | WHITE | DRAW | IN_PROGRESS
  • Moves are space-separated standard notation (a1h8).
  • Passes are recorded as the token pass.
  • load_game throws ArgumentError if either line is missing or the result value is unrecognised.
# Save
play_game(p1, p2; save_record=true, record_path="game.txt")

# Validate before use
rec = load_game("game.txt")
err = validate_record(rec)
err === nothing || error("Corrupt record: $err")

# Replay (strict mode catches any remaining inconsistency)
final = replay_game(rec; strict=true)

WTHOR binary format (wthor.jl)

WTHOR (.wtb) is the standard database format for professional Othello games, maintained by the Fédération Française d'Othello (FFO).

Reversi._wthor_byte_to_notationMethod
_wthor_byte_to_notation(b) -> Union{String, Nothing}

Decode one WTHOR move byte to standard Othello notation. Returns nothing for 0x00 (end-of-game / padding).

source
Reversi.read_wthorMethod
read_wthor(path) -> (WThorHeader, Vector{WThorGame})

Parse a WTHOR .wtb binary file.

Example

header, games = read_wthor("WTH_2001.wtb")
println("Games: $(header.n_games), year: $(header.game_year)")
source
Reversi.verify_wthor_gameMethod
verify_wthor_game(g) -> Bool

Replay all moves of g on a fresh board and verify the final black disc count matches g.black_score. Returns false on any invalid move (does not throw).

source
Reversi.write_wthorMethod
write_wthor(path, games; year, month, day, game_year, game_type, depth)

Write games to path in WTHOR binary format.

Each game record is exactly 68 bytes: 8 bytes of metadata followed by a 60-byte move array zero-padded to fill the fixed-size slot. Pass moves are silently omitted (WTHOR does not encode passes).

Example

g = WThorGame(1, 42, 99, 34, 36, ["f5","d6","c5"])
write_wthor("out.wtb", [g]; year=2024, game_year=2024)
header, loaded = read_wthor("out.wtb")
@assert loaded[1].moves == g.moves
source
Reversi.wthor_game_to_recordMethod
wthor_game_to_record(g) -> GameRecord

Convert a WThorGame to a GameRecord. black_score > 32 → BLACK wins, < 32 → WHITE wins, == 32 → draw.

source

File layout

Header  (16 bytes)
  byte  0     : creation century
  byte  1     : year within century
  bytes 2-3   : month, day
  bytes 4-7   : n_games  (Int32 LE)
  bytes 8-9   : count    (Int16 LE, same as n_games)
  bytes 10-11 : game_year (Int16 LE)
  byte  12    : board_size (always 8)
  bytes 13-15 : game_type, depth, reserved

Per-game record  (68 bytes × n_games)
  bytes 0-1  : tournament_id  (Int16 LE)
  bytes 2-3  : black_id       (Int16 LE)
  bytes 4-5  : white_id       (Int16 LE)
  byte  6    : black_score    (UInt8, actual disc count)
  byte  7    : best_score     (UInt8, theoretical best)
  bytes 8-67 : moves          (60 × UInt8, 0x00 = padding)

Move encoding: byte = row × 10 + col (row/col 1-indexed, col a=1…h=8).

File-size invariant

filesize(path) == 16 + n_games * 68

Pass handling

WTHOR does not encode pass moves. When replaying a WTHOR file, auto-pass between recorded moves:

for m in g.moves
    while isempty(valid_moves(game)) && !is_game_over(game)
        pass!(game; force=true)
    end
    make_move!(game, m)
end