NAME

Game::Xiangqi::Error - a flagged refusal, returned and never thrown

SYNOPSIS

my $refusal = $game->play('c4e6');

if ($refusal) {
    say $refusal->code;         # 'elephant_crosses_river'
    say $refusal->message;      # 'the elephant never crosses the river'
    say 1 if $refusal->elephant_crosses_river;
}

DESCRIPTION

A refused move comes back as one of these. It is not thrown, and that is a decision rather than an oversight: a refused move is an ordinary thing for a player to do, and unwinding the stack for it would make every caller wrap every move in an eval. die is kept for programmer error: a flag that does not exist.

Sixteen flags, one of them set. Six are structural (bad_move, game_over, not_your_turn, no_piece, own_piece, not_legal), two are about the general (in_check, generals_face), and nine name one rule of the game each. The nine exist so a refusal tells a player which rule they broke rather than "no": somebody told their elephant cannot cross the river does not come back and argue.

Game::Xiangqi::Engine is the layer underneath and does not use this class: its own play returns the flag as a plain string, because the engine is a skin over the C ABI and knows nothing about the Perl layers above it.

ATTRIBUTES

bad_move

game_over

not_your_turn

no_piece

own_piece

in_check

generals_face

general_leaves_palace

advisor_leaves_palace

elephant_crosses_river

elephant_eye_blocked

horse_leg_blocked

cannon_needs_one_screen

soldier_no_retreat

soldier_no_sideways

One accessor per reason, true on the one that applies and false on the rest.

not_legal is the answer of last resort and the nine rule flags exist to keep it rare: it means the move is not in the legal list and no rule of the piece explains why. in_check and generals_face are told apart because they are different mistakes, one of them the flying general, and a player told the wrong one learns the wrong rule.

error

Always 1. It is there so that a caller holding something which may be a move or may be a refusal can ask one question of either.

message

The sentence for the flag, as a player should be shown it.

The legal alternatives where offering them helps. An arrayref even when empty, because the normal case is a caller dereferencing it without checking.

METHODS

throw

Game::Xiangqi::Error->throw('in_check');
Game::Xiangqi::Error->throw('not_legal', legal => \@iccs);

Returns the refusal. It does not die. The name is the house's.

Dies only if the flag does not exist, which is programmer error.

code

The flag that is set, as a string, or undef. This is what an adapter maps to its own vocabulary.

stringify

The message. There is deliberately no "" overload: a refusal that quietly turned into a sentence when it was used as a hash key would be a worse bug than one that looks like a reference.

flags

my @names = Game::Xiangqi::Error->flags;

A class method: the list of every refusal name, in @FLAGS order. An adapter mapping these to its own codes should assert against this list rather than against a copy of it.

message_for

Game::Xiangqi::Error->message_for('in_check');
# 'that would leave your general in check'

A class method: the sentence for one flag without building a refusal, or undef for a name that is not one. The instance's own sentence is message.

known

Game::Xiangqi::Error->known($flag);      # 1 or 0

A class method: whether that name is a refusal this distribution can return.

SEE ALSO

Game::Xiangqi, which returns these; Game::Xiangqi::Engine, which does not.