NAME

Game::Checkers::Terminal - the game at a prompt

VERSION

Version 0.04

SYNOPSIS

use Game::Checkers;
use Game::Checkers::Bot;
use Game::Checkers::Terminal;

Game::Checkers::Terminal->new(
	game  => Game::Checkers->new,
	bot   => Game::Checkers::Bot->new(level => 3),
	human => 'black',
)->start;

DESCRIPTION

Everything in this distribution that reads a handle or writes to one is here. Game::Checkers and the modules under it do no input and no output at all, and nothing in the engine loads this module: the checkers script does.

The handles are properties. in and out default to STDIN and STDOUT, and a test hands it two in memory filehandles and plays a whole game in process, which is the reason for the split.

start returns the Game::Checkers::Result and never calls exit, so the script owns the exit status.

The board

    a   b   c   d   e   f   g   h
  +---+---+---+---+---+---+---+---+
8 |   | b |   | b |   | b |   | b |  Black 12  (0 kings)
  +---+---+---+---+---+---+---+---+
7 | b |   | b |   | b |   | b |   |  White 12  (0 kings)
  +---+---+---+---+---+---+---+---+
6 |   | b |   | b |   | b |   | b |
  +---+---+---+---+---+---+---+---+
5 | . |   | . |   | . |   | . |   |
  ...

Pieces are the Unicode draughts symbols, or b, B, w and W with "ascii". An empty playing square is a dot: half the board is never played on and the dots are what say which half. Colour is on only when the input is a terminal and NO_COLOR is unset.

In colour the board is checkered instead, and then the grid and the dots both go: the squares are painted light and dark, they meet, and the dark ones are the played half. The cell widens from three columns to five so that a piece has a middle to sit in, and the pitch of the files widens with it, so a piece is always in the column of its own letter. Nothing but the colour changes, which is what keeps the plain board exactly the board above.

The highlight is painted the same way. "move_marks" names the squares a move starts on, lands on, passes through and takes, and "cell" paints each of those its own colour, so the move under the cursor is shown on the board rather than only written in the list.

Moves and the commands

A move is written as the squares it is on, read off the letters and numbers round the edge: a3-b4 to slide and c3xe5xg7 to jump, with the whole path for a multiple jump. Numeric notation is still accepted, because Game::Checkers::Notation reads both, but nothing here prints it: a saved game is PDN, which is numeric, and the board is not.

A bare number plays that move from the moves list. Everything else is a command: help, moves, pick, type, hint, board, fen, undo, redo, save FILE, load FILE, level N, flip, ascii, draw, resign and quit. An unknown word is answered and the prompt comes back: the loop never dies on what somebody types, which is what Game::Checkers::Error is for.

Taking a turn back

u, or the typed undo, takes back your last move and every reply the bot made to it, so the board is as it was when it was last your turn. r, or the typed redo, plays that turn again. Each goes as far back, or forward, as you like, a turn at a time. Playing a move after taking one back forgets whatever could have been played again. In hotseat every move is a turn, and with two bots playing there is nothing to take back. With nothing to take back or play again a line says so and the game is unchanged. Loading a game starts its history afresh.

Picking a move instead of typing one

On a terminal there is nothing to type. The legal moves are listed under the board, one is always under the cursor, and the up and down keys walk the list while the board shows where that move goes. Enter plays it. h asks the bot and moves the cursor to what it suggests, u takes a move back, r plays it again, f flips the board, q stops, a digit jumps to that numbered move, and ? lists the commands. : gives a line to type, for the commands that need a word or a file name, and the typed type command stops the keys and pick starts them again.

Resigning and offering a draw are deliberately not on a key. They are not undoable and the keys for them would sit under the same fingers as the arrows.

This needs Term::ReadKey to put the terminal into cbreak mode. Without it, "keys_available" is false, "picking" is turned off in the constructor, and moves are typed exactly as they always were. It is not in PREREQ_PM for that reason: the game plays without it.

The board is drawn again when something changes it, and not otherwise. A list of moves, a hint or a refusal stays on the screen with the prompt under it, rather than being cleared away, or pushed off the top, by a board that is exactly the one already drawn.

End of file on in is a clean quit, so echo | checkers ends instead of spinning.

PROPERTIES

game

Read and write Game::Checkers, a new game by default.

$terminal->game;

bot

Read and write Game::Checkers::Bot, or undef for two people at one keyboard.

$terminal->bot;

human

Read and write string: black or white for a game against the bot, both for hotseat, none to watch two bots play.

$terminal->human;

in, out

Read and write filehandles, STDIN and STDOUT by default. out is given a UTF-8 layer unless "ascii" is set.

$terminal->out;

interactive

Read and write boolean, true when in is a terminal. It decides whether the screen is cleared between moves, so a captured transcript stays readable.

$terminal->interactive;

colour

Read and write boolean. Defaults to on when the session is interactive and NO_COLOR is unset.

$terminal->colour;

ascii

Read and write boolean: letters instead of the Unicode symbols.

$terminal->ascii;

redraw

Read and write boolean: whether the board wants drawing again. "render" clears it and a move, an undo, a load or one of the switches sets it, so the loop only draws a board that has changed.

$terminal->redraw;

flip

Read and write boolean: draw the board from White's side. The square names do not change, because they name the board and not the view of it.

$terminal->flip;

highlight

Read and write hash reference, square number to the name of a colour: from, to, captured and cursor. "cell" paints a square its colour instead of the light or dark it would have had. Only a painted board reads it, so a plain one is drawn the same whatever is in here.

$terminal->highlight({ 11 => 'from', 15 => 'to' });

picking

Read and write boolean: whether a move is chosen with the keys rather than typed. Defaults to "interactive", and is turned off in the constructor if "keys_available" is false, so asking for it on something that cannot do it is not an error.

$terminal->picking;

history

The Game::Terminal::Kit::History the turns are kept in, as PDN, built in new unless one is given.

$terminal->history->can_undo;

keysource

A code reference given to new, called for each character and returning it or undef. Set it to take keys from somewhere that is not a terminal, which is how the suite plays a whole game by key with no terminal to play it on. Left unset, Term::ReadKey reads "in".

Game::Checkers::Terminal->new(keysource => sub { shift @character });

pending

An array reference given to new, of characters to be read before any others.

keyboard

The Game::Terminal::Kit::Keys the keys are read through, built from "in", "keysource" and "pending" unless one is given.

screen

The Game::Terminal::Kit::Screen everything is printed through, built from "out", "interactive", "colour" and "ascii" unless one is given.

log

The Game::Terminal::Kit::Log holding what the picker has to say under the list: a hint, the help, a key that did nothing.

frame

The lines of the screen the picker last drew, an array reference. A command typed after : is drawn under them.

FUNCTIONS

start

Runs the game to its end and returns the result. It is the one that owns the terminal mode: it enters cbreak if "picking" is on, puts the terminal back whatever happens, "turns" dying included, and catches an interrupt for just long enough to restore it before raising it again. A shell is never left in raw mode.

my $result = $terminal->start;

options

my $fields = Game::Checkers::Terminal->options;

The options a person would set before a game, in the order a launcher shows them. Each is a hash reference with a name, a type and a help, and min and max or choices where the type has them. A new list is returned each time.

level

A whole number from 1 to 5: the strength of the bot.

side

One of black, white, random: your side; black moves first.

hotseat

A switch: two people at one keyboard, no bot.

bot-vs-bot

A switch: watch two bots play.

seed

Text: the bot varies its easy levels with it.

run

exit Game::Checkers::Terminal->run(@ARGV);

The whole command line program as a class method: parses the options bin/checkers documents, builds the game and the terminal, plays, and returns the exit status rather than calling exit: 0 if you won, drew or quit, or nobody at the keyboard was playing a side, 1 if you lost, 2 for a bad option, a leftover word or a refused value, with the reason and the usage on STDERR. --help and --version print to STDOUT and return 0; --analyse and --replay return 0 once they have printed. Signal handlers are local to the call and the terminal is left in cooked mode on every way out, so a launcher such as Game::Terminal can call it in its own process and carry on afterwards. Colour is off when NO_COLOR is set, as it is off a terminal.

turns

The loop itself, one turn an iteration: draw if the board changed, stop on a result, let the bot move if it is the bot's turn, and otherwise take a move from the keys or the prompt. "start" is what callers want; this is separate so that restoring the terminal can wrap it.

$terminal->turns;

keys_available

Whether a move can be picked with the keys here: true when "keysource" is set, or when "in" is a terminal and Term::ReadKey can be loaded.

$terminal->keys_available or print "type your moves\n";

enter_raw

Puts the terminal into cbreak mode, so a key arrives as it is pressed and is not echoed. Returns the object, or undef if it cannot be done, which is the answer "start" turns "picking" off on. Safe to call twice. Signals are left alone, so an interrupt is still an interrupt.

leave_raw

Puts the terminal back and returns the object. Safe to call twice, and safe when "enter_raw" was never called.

raw

Whether the terminal is being read a key at a time. "enter_raw" makes it so and "leave_raw" ends it. Everything that reads a key tests this rather than "picking", because the typed line reader is the one to use until the mode is actually changed.

$terminal->raw;

read_key

One keystroke, as "read_key" in Game::Terminal::Kit::Keys names it: the character itself or, for a key that is not one character, a name such as up, enter or escape. A name is always longer than one character, so

my $named = length $key > 1;

is the test. Returns undef at the end of the input.

picker

my $picker = $terminal->picker($legal, $at);

The Game::Terminal::Kit::Picker for a list of moves, each row the move and what it does, with the cursor on the first or on the index given.

pick

Draws the board, the move list and the cursor, and reads keys until something comes of it. Returns the empty string when a move was chosen, which it plays itself; the word of a command for "command" to run; a typed line when : was pressed; or undef at the end of the input.

my $line = $terminal->pick;

index_of

Where a move sits in a list of moves, by its notation, or 0 if it is not in it. Used to put the cursor on the move a hint names.

$terminal->index_of($legal, $move);

show_choice

Draws one frame of "pick": the board with the chosen move marked on it, the status block, the list, anything in the notice, and the keys.

$terminal->show_choice($legal, 0, ['Try f6-e5.']);

move_marks

The hash "highlight" takes for one move: where it leaves, where it lands, the squares it passes through on a multiple jump, and the pieces it takes.

$terminal->highlight($terminal->move_marks($move));

choice_lines

The move list as lines, the chosen one marked and in reverse video, windowed to eight at a time so that a position full of jumps cannot push the board off the top of the screen. The lines above and below are counted rather than drawn.

$terminal->choice_lines($legal, 2);

describe

What a move does, beyond where it goes: what it takes and whether it crowns, or the empty string for a plain step.

$terminal->describe($move);

legend

The one line of keys printed under the list.

hint

What the bot would play, as a hash reference: line to print and move, the move itself, so a caller can put the cursor on it. move is absent when there is nothing to play.

my $hint = $terminal->hint;

set_picking

Runs the pick and type commands: turns the keys on, or off and the terminal back with them.

$terminal->set_picking('type');

render

Draws the board and the status block.

$terminal->render;

board_lines

The board as an arrayref of lines, without their newlines.

$terminal->board_lines;

status_lines

The lines under the board: the last move, whose turn it is and how many moves they have, any draw offer, and the result once there is one.

$terminal->status_lines;

aside

The two lines printed beside the top of the board, one a side, with the piece counts.

$terminal->aside;

cell

One square of the board, drawn: three characters on a plain board, and on a painted one five, coloured by whether the square is played on and by whatever "highlight" says about it.

$terminal->cell($board, 0, 1);

paint

Wraps text in an ANSI colour, or returns it untouched when colour is off.

$terminal->paint('b', '1;36');

command

Handles one line of input, whether it is a move or a command. Returns true when the game should stop.

$terminal->command('f6-e5');

play

Plays a move written out, and prints why not when it is refused.

$terminal->play('f6-e5');

play_number

Plays a move by its place in the numbered list.

$terminal->play_number(3);

bot_plays

True when the given side belongs to the bot.

$terminal->bot_plays('white');

bot_move

Lets the bot play one move and announces it.

$terminal->bot_move;

read_line

Prints a prompt and reads one line from in, returning undef at end of file. While the terminal is being read a key at a time the line is typed under the last screen the picker drew, with backspace rubbing a character out, and escape gives an empty line.

my $line = $terminal->read_line;

say

Prints one line to out.

$terminal->say('your move');

clear

Clears the screen, but only in an interactive session.

$terminal->clear;

show_moves, show_hint, show_help

The moves, hint and help commands.

$terminal->show_moves;

help_lines

The help as lines rather than printed, so that "pick" can show it without printing under a screen it is about to draw again.

$terminal->help_lines;

take_back

The undo command and the u key: your last move and the replies to it. Returns 0.

$terminal->take_back;

play_again

The redo command and the r key: the turn "take_back" took back, played again. Returns 0.

$terminal->play_again;

swap

Puts another game in place of this one: clears the "highlight" and what the picker had to say, makes the bot forget its last search, and asks for the board to be drawn again.

$terminal->swap($game);

person_turn

Called by "turns" when the turn passes to the person at the keys: records the game in "history" as a turn.

$terminal->person_turn;

notify

One line for the person: under the move list while keys are read one at a time, and printed otherwise. Returns 0.

$terminal->notify('Taken back.');

save, load

The save and load commands, which write and read PDN. A loaded game starts a new history.

$terminal->save('game.pdn');

set_level

The level command.

$terminal->set_level(4);

toggle

Flips one of the rendering switches by name.

$terminal->toggle('flip');

offer_draw

The draw command. The bot answers from the score it recorded on its last move, taking the draw when it is not better than a third of a man ahead.

$terminal->offer_draw;

resign

The resign command.

$terminal->resign;

quit

The quit command, which asks first when a game is under way.

$terminal->quit;

AUTHOR

LNATION, <email at lnation.org>

BUGS

Please report any bugs or feature requests to bug-game-checkers at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Checkers. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

SUPPORT

You can find documentation for this module with the perldoc command.

perldoc Game::Checkers

You can also look for information at:

ACKNOWLEDGEMENTS

LICENSE AND COPYRIGHT

This software is Copyright (c) 2026 by LNATION.

This is free software, licensed under:

The Artistic License 2.0 (GPL Compatible)