NAME
Game::Backgammon::Terminal - the game at a prompt
SYNOPSIS
use Game::Backgammon::Terminal;
Game::Backgammon::Terminal->new(mode => 'bot', level => 3)->start;
DESCRIPTION
All of this distribution's input and output lives here and in bin/backgammon. Nothing below this file reads or writes anything, which is what makes the engine reusable; t/09-no-io.t proves it by playing a whole game with STDOUT tied to something that dies on write.
in and out are attributes rather than bare handles, so a test can play a game through this class without a terminal. A UI that only a human can drive is a UI that rots without anybody noticing.
The board
+13-14-15-16-17-18-+---+19-20-21-22-23-24-+---+
| O X | | X O | | Black 167 pips
| O X | | X O | |
| O X | | X | |
| O | | X | |
| O | | X | |
| |BAR| |OFF|
| X | | O | |
| X | | O | |
| X O | | O | |
| X O | | O X | |
| X O | | O X | | White 167 pips
+12-11-10--9--8--7-+---+-6--5--4--3--2--1-+---+
A real board: the twelve points of each half between their borders, the bar down the middle and the tray on the right. Checkers hang down from the top border and stand up from the bottom one; a point taller than five shows its count in the last place. Round checkers unless "ascii" is set, and colour only when the session is interactive and NO_COLOR is unset or empty.
It is drawn from the numbering of whoever is on roll, so the board in front of a player is the one their own moves are written in: their home board is the bottom right quarter and they run anticlockwise into it. That is why the numbers change sides between turns, and "view" pins them to one side for anybody who would rather they did not. Your own checkers on the bar are drawn in the top of the middle column, where you re-enter, and your tray at the bottom right beside your home board.
The pip counts are beside the side they belong to: the one number in the game nobody can read off the board itself.
A painted board
With colour on, the points are painted light and dark the way the triangles of a real board alternate, by the parity of the point number, and the checkers are drawn on them. Nothing but the colour changes: the cells stay three columns wide, the bar stays the middle column and the tray the one on the right, so the picture is the same picture and the plain board is exactly the one above. The bar and the tray are not points and are not painted.
"highlight" is painted the same way, which is what lets a move be shown on the board rather than only written in a list: where the checker leaves, where it lands, whether that lands on a blot, and the points already used by the moves chosen earlier in the turn.
Building a turn one checker at a time
A backgammon turn is two moves, or four on doubles, and the number of legal turns is the number of ways those can be combined. A roll of double one from the bar can be legal three hundred ways. Numbering three hundred turns and asking somebody to read them is not a list anybody can use, which is what "offer_lines" has to do.
So on a terminal the turn is built a move at a time. At each step the moves offered are the next move of every legal turn that starts with what has been chosen so far, deduplicated. Three hundred turns becomes eight choices, then three, then three, then three.
Taking the options from the legal turns, rather than from the dice and the board, is what makes this safe. The rules oblige a player to play both dice if any sequence plays both, and the larger die if only one can be played, so a move that looks legal on its own can be one that leaves the rest of the turn illegal. Because every option here is the next move of a turn the rules already offered, no sequence of choices can reach a dead end, every legal turn stays reachable, and what is finally played is one of the objects legal_turns returned rather than a turn assembled here and hoped for.
Backspace or escape takes back the move before it and the options are worked out again. Home and end go to the first and the last move offered. Where the moves chosen so far are already a legal turn, which is how a roll that can only be half played ends, f finishes it.
Taking a turn back
u takes back your last turn and the bot's reply to it, back to the board and the dice you had then, and r plays it again; with no picker, the typed words undo and redo do the same. The dice that follow are the dice that followed before, because they come from the game's seed. Against the bot a step is one of your turns with everything the bot played after it; in hotseat every turn is a step; in watch there is nothing to take back. A turn that is forced to pass is not a step of its own. u is a whole turn: the moves chosen so far in the turn being built are dropped, and the note under the list says so, where backspace and escape take back one of them. When there is nothing to take back or play again a note says so and nothing changes.
This needs Term::ReadKey to put the terminal into cbreak mode. Without it "picking" is turned off in the constructor and a turn is picked by number as before, so the game plays with or without it and it is a recommendation rather than a prerequisite.
ATTRIBUTES
game, in, out, mode, level, seat
mode is bot, hotseat or watch.
view
white, black, or empty for whoever is on roll.
ascii
O and X instead of the round checkers.
picking
Whether a turn is built with the keys rather than picked by number. Defaults to "interactive", and is turned off in the constructor when the terminal cannot be read a key at a time, so asking for it where it cannot work is not an error.
highlight
A hash reference, point number to the name of a colour: from, to, hit and played. A painted board draws those points in that colour instead of the light or dark they would have had. The keys are in the numbering of the side the board is drawn from, which is not the mover's numbering when the board is pinned with "view", so the mover's points are converted before they go in here.
keysource, pending
keysource is a code reference called with a wait and returning one character, which replaces reading the terminal and is how the suite builds a turn with the keys with no terminal to build it on. pending holds characters read but not used, so that an escape which turns out not to begin a sequence gives back the keystroke behind it. Both are given to "keyboard" when it is built.
colour, interactive
Both default from in: colour when it is a terminal and NO_COLOR is unset or empty, and the screen is only cleared between turns for a person, so a captured transcript stays readable. Both are given to "screen" when it is built.
keyboard
The Game::Terminal::Kit::Keys the keys and the typed lines are read through, built from "in", keysource and pending.
screen
The Game::Terminal::Kit::Screen everything is written through, built from "out", "interactive", "colour" and "ascii".
log
The Game::Terminal::Kit::Log holding what the picker has to say under its list: the keys when they are asked for, and a key that did nothing.
picker
The Game::Terminal::Kit::Picker a turn is built with. It is given the next moves afresh at every step of the turn.
history
The Game::Terminal::Kit::History the turns are kept in, as "to_text" in Game::Backgammon, built in new unless one is given.
METHODS
render($view)
The board as one string.
board_lines($view)
The board as an arrayref of lines, without their newlines.
offer_lines($turns)
The legal turns, numbered, laid out across the width of the board. Doubles from the bar can offer thirty of them, and a list that long pushes the board it belongs to off the top of the screen.
clear
Clears the screen, but only for a person.
raw
Whether the terminal is being read a key at a time.
step
Play one turn. Returns true while the game is still on.
person_turn
Called by "step" when the turn passes to a person at the keys, and once when the game ends unless nobody is: keeps the game in "history" as a turn.
take_back
The u key and the undo word: your last turn and the replies to it, taken back. Returns 1, or 0 when there is nothing to take back.
play_again
The r key and the redo word: the turn "take_back" took back, played again. Returns 1, or 0 when there is nothing to play again.
swap($game)
Puts another game in place of this one, clearing "highlight" and what the picker had to say.
start
Play until the game ends or the player stops. Starts a new "history" for the game. Returns the Game::Backgammon::Result, or undef if it was stopped.
options
my $fields = Game::Backgammon::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.
mode-
One of
bot,hotseat,watch: you against the program, two people at one keyboard, or two programs. level-
A whole number from 1 to 3: how hard the program plays.
seat-
One of
white,black: your side.
run
exit Game::Backgammon::Terminal->run(@ARGV);
The whole command line program as a class method: parses the options bin/backgammon documents, builds the terminal, plays, and returns the exit status rather than calling exit: 0 when the game ended or was stopped, 2 for a bad option or a refused value, with the reason and the usage on STDERR. --help prints the usage on STDOUT and returns 0. 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 under NO_COLOR as well as off a terminal.
say_to(@what)
Write a line to out.