NAME

Game::Oware::Board - the twelve houses, the two stores, and the sowing

VERSION

Version 0.01

SYNOPSIS

use Game::Oware::Board;

my $board = Game::Oware::Board->opening;
my ($next, $last, $sown) = Game::Oware::Board->sow($board, 4);

DESCRIPTION

The position, and the one rule that moves seeds around it.

A board is an arrayref, not an object

Fourteen slots. Indices 0 to 5 are p1's houses, named A to F; indices 6 to 11 are p2's houses, named a to f; index 12 is p1's store and index 13 is p2's store.

The bot calls sow millions of times and a board holds no invariant worth protecting, so this is class methods over a plain array rather than a class with accessors.

A cell holds a count, and zero is a real value

Every cell is defined from the first move to the last, which is the opposite of Game::Reversi::Board, where undef means an empty square. Here an empty house is a number rather than an absence, so if ($board->[$house]) is a legitimate "has seeds in it" test with nothing for it to be confused with.

Counter-clockwise is ascending index

The houses are laid out so that the ring arithmetic is trivial:

f  e  d  c  b  a          11 10  9  8  7  6
A  B  C  D  E  F           0  1  2  3  4  5

Left to right along p1's row and right to left along p2's, which in index terms is simply increasing. F is followed by a, and f is followed by A.

The ring modulus is 12, never 14

Seeds are never sown into a store. The stores live in the same array as the houses because the position is one thing and a result is computed from all fourteen numbers, and the price of that representation is exactly this rule: a sowing loop that takes the length of the array as its modulus banks a seed for somebody every lap.

The bug is close to invisible. The counts stay plausible, the total is still 48, and the game plays on. It is also what every mancala implementation a reader has met does, because in Kalah you do sow into your own store. Oware sows into neither.

The origin house is skipped, and not only on the twelfth seed

A house holding twelve or more seeds laps the board. The house the seeds came from stays empty, so the step skips it every time it comes round, not once. An implementation written around the literal twelve handles a house of twelve and gets a house of twenty-five wrong.

The capture chain walks backwards

Sowing runs counter-clockwise, so the chain runs the other way: from the house the final seed landed in, stepping down the ring.

An implementation that continues forwards is inspecting houses the hand never reached. It captures seeds the rule does not award, and it is wrong only where both directions happen to hold twos and threes, which is to say wrong exactly in the close games. On the position the Wikipedia article works through, forwards takes five seeds where the article takes eight, and one of the two houses it takes was never sown into at all.

The chain never outruns the hand

A hand of two seeds touched two houses, so a chain of three describes a capture the sowing never reached. Without that cap, a long run of twos and threes left over from earlier play is harvested by a move that went nowhere near it.

The cap is the loop's own condition rather than a check inside it, so it cannot be stepped past. It binds only on short sows: a hand of twelve or more has lapped the board and touched every house, and six is all the opponent has.

What stops the walk

The first house that is not the opponent's, or does not hold exactly two or three. Two or more is not the rule and neither is at most three.

The ownership test is also what keeps the chain inside one row: walking back from the opponent's first house arrives at your own last house, so the walk terminates there without needing to know that a row boundary exists. (0 - 1) % 12 is 11 in Perl, so the wrap needs no special case either.

An empty house dies rather than refusing

Sowing nothing is meaningless, so a caller that asks for it has a bug. A player who picks an empty house is a refusal and is caught in the rules layer before this is reached, which is the house rule that die is for programmer error and a flagged error object is for a move.

METHODS

opening

The starting position: four seeds in each of the twelve houses, both stores empty.

clone

A shallow copy of a board, which is all a board ever needs.

total

The sum of all fourteen cells. It is 48 at every point in every game, which is the cheapest real assertion in the distribution.

other

The other seat.

store_of

The index of a seat's store. Dies on an unknown seat.

owner_of

The seat that owns a house, 0 to 11.

houses_of

The six house indices a seat owns, in ascending order.

seeds_on_side

How many seeds are sitting in a seat's own six houses. Not a score: seeds in a house belong to nobody until they are captured, and a player with forty seeds in front of them is very often losing.

assert_house

Dies unless its argument is a house index. Used by everything that takes one.

sow

my ($next, $last, $sown) = Game::Oware::Board->sow($board, $house);

Lifts every seed out of $house and drops one in each house counter-clockwise from it, skipping the origin and both stores. Returns a new board, the index the final seed landed in, and how many seeds were sown.

The board passed in is not modified.

capture_chain

my @chain = Game::Oware::Board->capture_chain($board, $last, $sown, $seat);

The houses $seat captures, in walk-back order, or an empty list. $board is the board after sowing, because every count in the rule is a post-sowing count.

It finds the chain and does not apply it. Deciding whether a chain is a grand slam needs the chain without its effects, so a function that captured as it walked would have to be undone.

SEE ALSO

Game::Oware, Game::Oware::Notation, Game::Oware::Move

AUTHOR

LNATION <email@lnation.org>

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.