NAME
Game::Backgammon::Terminal - the game at a prompt
SYNOPSIS
use Game::Backgammon::Terminal;
Game::Backgammon::Terminal->new(mode => 'bot', level => 3)->run;
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.
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 takes back the move before it and the options are worked out again. 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.
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.
raw, keysource, pending
raw is whether the terminal is in cbreak mode. keysource is a code reference called with a wait flag 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.
colour, interactive
Both default from in: colour when it is a terminal and NO_COLOR is unset, and the screen is only cleared between turns for a person, so a captured transcript stays readable.
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.
step
Play one turn. Returns true while the game is still on.
run
Play until the game ends or the player stops. Returns the Game::Backgammon::Result, or undef if it was stopped.
say_to(@what)
Write a line to out.