NAME
Game::Shogi::Engine - the shogi board: squares, pieces, hands and the key
SYNOPSIS
use Game::Shogi::Engine qw(:all);
my $board = Game::Shogi::Engine->new;
print $board->sfen, "\n";
print $board->key, "\n";
my $sq = Game::Shogi::Engine->square('7g');
print $board->at($sq), "\n"; # P
my ($copy, $code) = Game::Shogi::Engine->from_sfen($board->sfen);
print Game::Shogi::Engine->reason($code), "\n" unless $copy;
DESCRIPTION
The board of shogi and nothing that judges a move: the 81 squares and their names, the promotion zone, the pieces and what each promotes to, the forty pieces of a set, the two hands, whose turn it is, the move number, a key that names the position, and the position written as SFEN.
Squares are numbered 0 to 80 in SFEN order: rank a first, and in each rank file 9 first, so 0 is 9a, 8 is 1a and 80 is 1i. A square also has a name, its file digit and its rank letter, as USI writes it (7g).
Black moves first in an even game and is written in capitals, and White in lower case, as SFEN does. A side is 'b' or 'w'.
A piece is its SFEN token: P L N S G B R K for the base pieces, and +P, +L, +N, +S, +B and +R for the promoted ones, in lower case for White.
CONSTRUCTORS
new
Game::Shogi::Engine->new
Game::Shogi::Engine->new(empty => 1)
Game::Shogi::Engine->new(sfen => $sfen)
The start of an even game, Black to move, move 1. With empty, every square and both hands are empty. With sfen, the board that SFEN writes. Croaks when the SFEN is refused, with the reason.
from_sfen
my ($board, $code) = Game::Shogi::Engine->from_sfen($sfen);
The board an SFEN writes, and POS_OK; or undef and the code that says why the SFEN was refused (see "reason"). The move number may be left off, and is then 1.
clone
A board of its own with the same position.
THE POSITION
sfen
The position as SFEN: the board rank a first, / between the ranks; the side to move; the hands, rook to pawn and Black's first, or -; the move number. The start is
lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1
reason
Game::Shogi::Engine->reason($code)
The refusal code from "from_sfen" in words. The codes, which are exportable, are POS_OK, POS_NULL, POS_LONG, POS_FIELDS, POS_RANKS, POS_LETTER, POS_WIDTH, POS_PROMOTE, POS_SIDE, POS_HAND, POS_MOVE, POS_KINGS and POS_COUNT, and an SFEN is checked in that order.
An SFEN is refused for its shape and not for its sense: two pawns on a file, or a pawn on the last rank, load. The two exceptions are counts a set fixes: two kings of one side, and more of a piece than a set has.
key
The position's key as sixteen hex digits. It names every piece, both hands and the side to move, and not the move number. Two boards with the same key are the same position.
at
$board->at($sq)
The piece on a square as its token, or undef for an empty square or a number that is not a square.
put
$board->put($sq, '+r')
Puts a piece on a square, replacing what was there. It judges nothing: the key and the kings follow, and a square or a token that is not one is ignored. Returns the board.
lift
$board->lift($sq)
Empties a square. Returns the board.
hand
$board->hand('b', 'P')
How many of a piece a side holds in hand. The piece is a base piece, rook to pawn, in either case.
set_hand
$board->set_hand('w', 'R', 1)
Sets how many of a piece a side holds, from 0 to as many as a set has, and keeps the key. Anything else is ignored. Returns the board.
hands
Both hands as a hash reference, { b => { P => 2 }, w => {} }, each holding only the pieces it has.
side_to_move
'b' or 'w'.
set_side_to_move
Makes it a side's turn. Returns the board.
move_number
The move number, as SFEN counts it: 1 for the first move of the game, and one more for each move by either side.
set_move_number
Sets it, from 1 to MOVE_MAX. Returns the board.
king_square
$board->king_square('b')
The square of a side's king, or undef when it has none. Where "put" has given a side two, it is the one put last.
THE BOARD MOVES
A move is its USI string: the square it leaves, the square it reaches, and a + when it promotes, as 7g7f or 8h2b+. Where promotion is optional a move is listed twice, with the + first; where it is forced, only with it. A drop is the piece's letter in capitals, for either side, a * and the square, as P*5e.
attacked
$board->attacked($sq, 'w')
True when a piece of that side could move onto the square now, whatever stands there, and whatever the move would do to its own king.
in_check
$board->in_check('b')
True when a side's king is attacked; false when it is not, or it has none.
pseudo_moves
Every move of the side to move's pieces on the board, without asking whether it leaves its own king attacked, square by square in square order.
board_moves
The same, less every move that leaves the mover's own king attacked. A side with no king has them all.
do_move
$board->do_move('7g7f')
Makes a move: anything on the square it reaches goes into the mover's hand as its base piece, the piece promotes if the move says so, the turn passes and the move number counts on. Returns 1, or 0 and changes nothing when the move is not a piece of the side to move, lands on its own piece, promotes a piece that cannot, takes a king, or would fill a hand past the set. It does not ask whether the piece moves that way or whether the king is safe: take moves from "board_moves" for that.
undo_move
Takes back the last move "do_move" made on this board. Returns 1, or 0 when there is none, or when the board no longer shows it (a "put", "lift" or setter since has changed it). A "clone" starts with no moves to take back.
moves_made
How many moves "do_move" has made on this board and "undo_move" has not taken back.
THE DROPS
A piece in hand may be dropped on any empty square, unpromoted, except: a pawn or lance on the last rank or a knight on either of the last two, where it could never move; a pawn on a file where its side already has an unpromoted pawn; and a pawn dropped to give mate. Like any move, a drop may not leave its own king attacked. "do_move" makes a drop of a piece in hand onto an empty square and asks none of these; "drops" and "legal" list only the drops that pass them all.
drops
The legal drops of the side to move: each piece it holds in the order rook, bishop, gold, silver, knight, lance, pawn, and for each, every square it may go on in square order.
legal
Every legal move of the side to move: "board_moves", then "drops".
has_legal_move
True when the side to move has a legal move.
drop_fault
$board->drop_fault('P', $sq)
Why the side to move may not drop that piece there, as a code, or DROP_OK. The piece is a capital letter, rook to pawn. The codes, which are exportable, are DROP_OK, DROP_BAD, DROP_NOT_HELD, DROP_OCCUPIED, DROP_DEAD, DROP_TWO_PAWNS, DROP_IN_CHECK and DROP_PAWN_MATE, asked in that order.
drop_reason
Game::Shogi::Engine->drop_reason($code)
A drop code in words.
perft
$board->perft(4)
How many sequences of legal moves of that many plies there are from the board. It leaves the board as it was.
perft_counts
my $c = $board->perft_counts(5);
The same, with the last ply's moves counted by what they are, as a hash reference: nodes, captures, drops, promotions, checks and mates.
HOW A POSITION ENDS
A side with no legal move has lost: when it is in check that is mate, and when it is not, every move left to it would break a rule, which in shogi loses too. Repetition and the impasse need the game's history, which a board does not keep.
status
'ongoing', 'mate' or 'no_move', for the side to move.
outcome
A Game::Shogi::Result when the side to move has lost, or undef while it has a move.
resign
my $result = $board->resign('b');
A Game::Shogi::Result for that side resigning. Only the side to move may, and only while it has a move; anything else is undef. It does not change the board.
move_fault
$board->move_fault('5h4h')
Why the side to move may not make a move, as a code, or FAULT_OK exactly for the moves "legal" lists. A move with two faults gives the one asked first: whose piece it is, whether a drop is held, whether the piece reaches, whether a drop's square is empty, whether the promotion is right, whether a drop could ever move, two pawns, the king, and the pawn-drop mate. The codes, which are exportable, are FAULT_OK, FAULT_BAD, FAULT_NOT_YOURS, FAULT_NOT_HELD, FAULT_CANNOT_REACH, FAULT_OCCUPIED, FAULT_PROMOTE_NOT_ALLOWED, FAULT_PROMOTE_REQUIRED, FAULT_DEAD, FAULT_TWO_PAWNS, FAULT_IN_CHECK and FAULT_PAWN_MATE.
fault_reason
Game::Shogi::Engine->fault_reason($code)
A fault code in words, as a player would read it.
WHAT NEEDS THE HISTORY
A board keeps the positions "do_move" has reached on it, from the one it held when "do_move" was first called, and "undo_move" takes them back off. A "clone" starts with none. Changing the board any other way ("put", "lift", a setter) does not add to them, and repetition is not judged until the board is again the last position they hold.
cap
The move count at which a game stops as a draw: 500 unless new is given another (cap => 300). At the cap, a side in check plays on until it is not. Y line 128 is the Japan Shogi Association's rule.
outcome
As well as mate and no move: sennichite, a draw, when the position stands for the fourth time; perpetual_check, a loss for the side that gave check with every move since it first stood; and cap, a draw, at the cap.
THE SEARCH
evaluate
$board->evaluate
$board->evaluate(EVAL_NO_KING_SAFETY)
The position's worth to the side to move, in centipawns: the pieces on the board and in hand (a piece in hand worth a little more), its king's guards and attackers, and a bonus for a king in the enemy camp. The flags turn terms off, for measuring what each is worth.
search
my $usi = $board->search(nodes => 60_000, seed => $n);
my ($usi, $info) = $board->search(nodes => 60_000, depth => 4, flags => SEARCH_NO_MATE);
The move to play, found by an alpha-beta search of at most nodes nodes (60,000 when not given) and depth plies (no ceiling when not given), its equal-scoring moves told apart by seed. history, an array reference of the keys of the positions the game stood in before this one, defaults to "history"; a position reached again scores as a draw. undef when the side to move has no move. In list context, also nodes, depth (the deepest iteration finished), score and stopped in a hash reference. The budget is in nodes and never in time, so a search plays the same move on any machine, and it leaves the board as it was.
history
The keys of the positions "do_move" reached before this one, oldest first: what "search" treats as already stood in. Empty for a clone, and when the board has been changed another way since.
packed
The position as PACKED bytes: the squares, both hands and the side to move, not the move number. Two boards with the same bytes are the same position as repetition counts one.
repetition
A Game::Shogi::Result for the fourth time the position stands, or undef.
impasse_points
$board->impasse_points('b')
The points a side would declare with: a rook or bishop, promoted or not, 5, any other piece 1, counting only its pieces in the enemy camp (not the king) and its hand.
impasse_pieces
How many of a side's pieces, not the king, stand in the enemy camp.
declare_fault
Why the side to move may not declare the impasse, or DECLARE_OK: its king is in the enemy camp, ten of its other pieces are there, it is not in check, and it has 28 points as Black or 27 as White, asked in that order. The codes are exportable: DECLARE_OK, DECLARE_NO_KING, DECLARE_FEW_PIECES, DECLARE_IN_CHECK and DECLARE_FEW_POINTS.
can_declare
True when the side to move may declare.
declare_reason
A declaration code in words.
declare
A Game::Shogi::Result for the side to move declaring the impasse and winning, or undef when it may not, the game is already over, or the cap has been reached. It does not change the board.
THE BOARD
These are the same for every board, and may be called on the class.
squares
81.
square
Game::Shogi::Engine->square('7g')
The square a name names, or undef.
square_name
The name of a square, as USI writes it.
sq_of
Game::Shogi::Engine->sq_of($file, $rank)
The square at a file and a rank, each 1 to 9, rank 1 being rank a. undef off the board.
file_of
The file of a square, 1 to 9.
rank_of
The rank of a square, 1 for rank a to 9 for rank i.
step
Game::Shogi::Engine->step($sq, $dfile, $drank)
The square that many files and ranks away, or undef off the board. A $drank of -1 is towards rank a, which is forward for Black. Only one file across and two ranks along are served, which is as far as any single step or a knight's jump goes; anything wider is undef.
in_zone
Game::Shogi::Engine->in_zone('b', $sq)
True for the three ranks furthest from a side, where its pieces may promote.
last_distance
Game::Shogi::Engine->last_distance('w', $sq)
How many ranks lie beyond a square as a side faces: 0 on the furthest rank, 8 on its own back rank.
promoted
Game::Shogi::Engine->promoted('S') # +S
The token of what a piece promotes to, or undef for a gold, a king or a piece already promoted.
unpromoted
Game::Shogi::Engine->unpromoted('+r') # r
The base piece of a token. A base piece is its own.
set_count
Game::Shogi::Engine->set_count('P') # 18
How many of a base piece a whole set has, both sides together.
zobrist
zobrist_hand
zobrist_move
Game::Shogi::Engine->zobrist('b', '+R', $sq)
Game::Shogi::Engine->zobrist_hand('w', 'P', 3)
Game::Shogi::Engine->zobrist_move
The numbers the key is made of, as hex: one for each piece of each side on each square, one for each count of each piece in each side's hand, and one for White to move. A hand of none is all zeros.
FOR TESTS
abi_version
The version of the board's C interface.
tables_ok
True when the board was built and every count agreed with what it should be.
live
How many boards exist and have not been let go.
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 (GPL Compatible)