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.GameRecord — Type
GameRecordStores the complete move history of a Reversi game.
Fields:
moves::Vector{String}– ordered list of moves in standard notation or"pass".result::Int–BLACK,WHITE,EMPTY(draw), orIN_PROGRESS.
Reversi.load_game — Method
load_game(filepath) -> GameRecordRead 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.
Reversi.replay_game — Method
replay_game(record; verbose=false, strict=false) -> ReversiGameReplay all moves stored in record on a fresh ReversiGame and return the final state.
verbose=true– print each move to stdout.strict=true– throwArgumentErroron the first invalid move instead of silently ignoring it. Recommended when replaying external data.
Reversi.save_game — Method
save_game(record, filepath)Write a GameRecord to filepath.
Format:
MOVES: d3 c5 f4 ...
RESULT: BLACK | WHITE | DRAW | IN_PROGRESSReversi.validate_record — Method
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).
File format
MOVES: f5 d6 c5 f4 e3 d3 c4 ...
RESULT: BLACK | WHITE | DRAW | IN_PROGRESS- Moves are space-separated standard notation (
a1–h8). - Passes are recorded as the token
pass. load_gamethrowsArgumentErrorif either line is missing or the result value is unrecognised.
Recommended workflow
# 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.WThorGame — Type
WThorGameOne game record from a .wtb WTHOR database file.
Reversi.WThorHeader — Type
WThorHeaderMetadata from the 16-byte header of a .wtb WTHOR database file.
Reversi._notation_to_wthor_byte — Method
_notation_to_wthor_byte(s) -> UInt8Encode standard Othello notation to a WTHOR move byte.
Reversi._wthor_byte_to_notation — Method
_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).
Reversi.read_wthor — Method
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)")Reversi.verify_wthor_game — Method
verify_wthor_game(g) -> BoolReplay 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).
Reversi.write_wthor — Method
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.movesReversi.wthor_game_to_record — Method
wthor_game_to_record(g) -> GameRecordConvert a WThorGame to a GameRecord. black_score > 32 → BLACK wins, < 32 → WHITE wins, == 32 → draw.
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 * 68Pass 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