NAME
Game::Dominoes::Layout - the tiles on the table, and where another may go
VERSION
Version 0.01
SYNOPSIS
use Game::Dominoes::Layout;
my $layout = Game::Dominoes::Layout->new;
$layout->place($spinner, 'L'); # 5-5 opens
$layout->arms_open; # ('L', 'R'): the arms are shut
$layout->place($tile, 'L');
$layout->place($other, 'R');
$layout->arms_open; # ('L', 'R', 'U', 'D'): both sides covered
$layout->open_ends; # the faces a tile may be matched against
$layout->ends; # the same ends, as the scorer needs them
DESCRIPTION
The geometry, and nothing else. This class knows where a tile may physically go; whether a player may put it there is Game::Dominoes, and what it scores is Game::Dominoes::Scoring.
The vocabulary, which is the opposite of the intuitive one
A double is laid crosswise, its long axis across the line of play. Its two long sides face along the line, so a tile played against a side continues the main line. Its two short ends stick out, and those are the extra arms.
So the rule "the sides before the ends" means the main line must be extended in both directions before either perpendicular arm opens, which is the reverse of what the words suggest. Getting this backwards produces an engine that opens the arms immediately, and the tactical shape of All Fives collapses when it does.
The arms are named L and R for the two directions of the main line, and U and D for the two off the spinner. Those names are what the notation prints and what the event log stores.
The spinner, and why the line is one array
The spinner is the first double played, wherever it lands, and only the first. Rather than four arms growing from a root, the main line is one ordered array with the spinner at a known index. The contested rule then becomes arithmetic: a side is covered when the line runs past the spinner in that direction, so sides_covered is two comparisons and not a special case.
A spinner played as the opening tile covers neither side. A spinner played onto an existing line covers one immediately, because the tile it was laid against is already against one of its sides. Both fall out of the index.
A later double is not a spinner. It is laid crosswise like any double and the line runs straight through it, and it never blocks.
PROPERTIES
line
$layout->line;
The main line, left to right, as { tile, left, right } entries where left and right are the faces pointing that way.
spinner_index
$layout->spinner_index;
Where the spinner sits in line, or undef before a double is played.
u, d
$layout->u;
The arms off the spinner's short ends, as { tile, inner, outer } entries growing outward.
FUNCTIONS
is_empty, count, tiles, pips
$layout->is_empty;
$layout->count; # tiles on the table
$layout->tiles; # all of them, as an arrayref
$layout->pips; # their pips, for the 168 invariant
spinner
$layout->spinner;
The spinner tile, or undef if no double has been played.
sides_covered
$layout->sides_covered; # 0, 1 or 2
How many of the spinner's long sides carry a tile. Zero without a spinner. The perpendicular arms open at two.
arms_open
$layout->arms_open; # ('L', 'R')
Which arms will accept a tile now. Just L on an empty table, which is where the opening tile goes by convention; L and R once there is a line; all four once the spinner has both sides covered.
face_of
$layout->face_of('L'); # 6
The face that arm is showing, and so what a tile must carry to go there. Undef when the arm is not open. An empty U or D shows the spinner's own face.
open_ends
$layout->open_ends; # (6, 4)
The faces a tile could be matched against right now. This is for legality. What an end is worth is a different question with a different answer: see ends.
ends
$layout->ends; # ({ arm, tile, face, sole }, ...)
The open ends as the scorer needs them, one entry per end that exists. This reports geometry and never a total, because what an end is worth is contested and belongs in Game::Dominoes::Scoring: a double at an end counts both its halves, and a spinner stops counting once both its sides are covered.
Neither of those needs a special case here. A spinner with both sides covered is no longer at an end, so it simply does not appear in this list.
sole marks a single tile on the table, which is one end and not two, so that its two faces are not counted twice.
clone
->clone;
A copy with its own arrays, for trying a play without committing to it. The tiles inside are immutable and shared.
can_place
$layout->can_place($tile, 'L');
Whether that tile may physically go on that arm.
place
my $play = $layout->place($tile, 'L');
Puts the tile on the arm and returns the Game::Dominoes::Play. Dies on an illegal placement, which is programmer error: a player's mistake is refused further up by Game::Dominoes as a returned error object, and nothing a player can do reaches here without can_place having said yes.
SEE ALSO
Game::Dominoes::Play, what this returns; Game::Dominoes::Tile.
AUTHOR
LNATION <email@lnation.org>
BUGS
Please report any bugs or feature requests to bug-game-dominoes at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Game-Dominoes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Game::Dominoes::Layout
ACKNOWLEDGEMENTS
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 (GPL Compatible)