NAME
Game::Checkers::Terminal - the game at a prompt
VERSION
Version 0.02
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, 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.
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, 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;
raw
Read and write boolean: whether the terminal is in cbreak mode. "enter_raw" sets it and "leave_raw" clears 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;
keysource
Read and write code reference, called with the wait flag "read_char" was given and returning one character 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".
$terminal->keysource(sub { shift @character });
pending
Read and write array reference of characters read but not used yet, which is how an escape that turns out not to begin a sequence gives back the keystroke behind it. Not for a caller to set.
$terminal->pending;
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;
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.
read_char
my $char = $terminal->read_char(1);
One character: whatever "pending" is holding, else "keysource" if it is set, else Term::ReadKey. With a false argument it does not block, and returns undef if nothing is there after a moment, which is how the end of an escape sequence is found.
read_key
One keystroke, as the character itself or, for a key that is not one character, a name: up, down, left, right, home, end, page_up, page_down, enter, tab, backspace, escape, interrupt and eof. 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.
read_sequence
Reads the rest of an escape sequence, the escape having been read already, and returns its name, or escape for what is not a sequence this knows. A character that turns out not to belong to one is put back on "pending" rather than lost.
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.
keyed_line
A typed line read a key at a time, echoed as it is typed, with backspace rubbing a character out. Used instead of readline while the terminal is in cbreak mode, because the terminal is not assembling lines then. Returns undef on an interrupt or the end of the input.
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.
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: your last move and the reply to it.
$terminal->take_back;
save, load
The save and load commands, which write and read PDN.
$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:
RT: CPAN's request tracker (report bugs here)
Search CPAN
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)