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.

Every legal move of the side to move: "board_moves", then "drops".

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.

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.

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)