NAME

Game::Reversi::Terminal - a playable game on two filehandles

VERSION

Version 0.01

SYNOPSIS

my $terminal = Game::Reversi::Terminal->new(
    level => 3, variant => 'historic', colour => 'b');

my $result = $terminal->start;    # returns, never exits

# and in a test, with no pipe and no subprocess:
my $terminal = Game::Reversi::Terminal->new(in => $in, out => $out);

DESCRIPTION

A person against Game::Reversi::Bot, on a board drawn in text.

The distribution ships something playable because a rules engine nobody can play is a rules engine nobody has checked. This one found two faults during phase 06 that the suite had not.

It is separate from the engine on purpose

Game::Cribbage put about a thousand lines of escape codes in the top level namespace module, which is why only its ::Board ever turned out to be reusable. The top level module here is the engine, and this is a consumer of it exactly as a web adapter would be. The suite asserts that the engine can be loaded without pulling this in.

in and out are properties

Defaulting to STDIN and STDOUT. That is what makes a terminal testable: the suite drives a whole game in process against in memory handles, with no pipe, no fork, and no subprocess writing into the TAP stream.

start returns, and never exits

A module that calls exit cannot be tested and cannot be embedded. The exit status belongs to bin/reversi, which is the only part that knows it is a program.

The board

    a   b   c   d   e   f   g   h
  ┌───┬───┬───┬───┬───┬───┬───┬───┐
8 │   │   │   │   │   │   │   │   │ 8
  ├───┼───┼───┼───┼───┼───┼───┼───┤
...
5 │   │   │   │ ● │ ○ │ * │   │   │ 5
  ├───┼───┼───┼───┼───┼───┼───┼───┤
4 │   │   │ * │ ○ │ ● │   │   │   │ 4
...
  └───┴───┴───┴───┴───┴───┴───┴───┘
    a   b   c   d   e   f   g   h

A ruled grid with the discs sitting in it, because a reversi board is a grid and eight rows of dots are a list. ascii draws the same board as X and O on +-|, for a terminal that cannot manage the rest.

Two glyphs, not one glyph in two colours

Two discs told apart only by an escape code are indistinguishable in half the terminals in the world and unreadable to a good fraction of the people using the other half. A filled disc and a hollow one are as far apart on a screen as they are on a table, and they are still that far apart with no colour at all. So the glyphs carry all of the information and colour carries none: strip the escapes and the board reads exactly the same.

Colour is off unless the output is a terminal, and off regardless if NO_COLOR is set to anything non-empty. An explicit ansi option overrides the terminal check but not NO_COLOR, because somebody who set that meant it.

METHODS

new

in, out, level, variant, colour, seed, quiet, ansi, ascii.

ansi

Whether colour will be used.

in, out, game

The handles, and the game once start has built one. All three are read and write: a test hands this a tied handle and reads the game back out of it.

The ansi option is held privately, because "ansi" is a question about the handle and the environment as well as the option, and there should be one public answer to it rather than two.

level, variant, colour, seed, quiet, ascii

The options, as properties. colour is the side the person is playing.

glyph

The disc a colour is drawn as, which ascii decides.

board_text

The board as lines of text: a ruled grid, files across the top and the bottom, ranks down both sides, and the legal moves for a colour marked with *. Marking them is not decoration: working out which squares outflank something is the engine's job, and a person doing it by hand every turn will get it wrong.

start

Plays a game and returns its Game::Reversi::Result.

AUTHOR

LNATION <email@lnation.org>

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION <email@lnation.org>.

This is free software, licensed under the Artistic License 2.0.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 381:

Non-ASCII character seen before =encoding in '┌───┬───┬───┬───┬───┬───┬───┬───┐'. Assuming UTF-8