NAME

Chess::Rep - represent chess positions, generate list of legal moves, parse moves in various formats.

The name stands for "Chess Representation", basically meaning that this module won't actually play chess -- it just helps you represent the board and validate the moves according to the laws of chess. It also generates a set of all valid moves for the color to play.

SYNOPSIS

my $pos = Chess::Rep->new;
print $pos->get_fen;

# use any decent notation to describe moves
# the parser will read pretty much anything which isn't ambiguous

$pos->go_move('e4');
$pos->go_move('e7e5');
$pos->go_move('Bc4');
$pos->go_move('Nc8-C6');
$pos->go_move('Qf3');
$pos->go_move('d6');
$pos->go_move('F3-F7');

if ($pos->status->{check}) {
  print("CHECK\n");
}

if ($pos->status->{mate}) {
  print("MATE\n");
}

if ($pos->status->{stalemate}) {
  print("STALEMATE\n");
}

# reset position from FEN

$pos->set_from_fen('r1b1k1nr/pp1ppppp/8/2pP4/3b4/8/PPP1PqPP/RNBQKBNR w KQkq - 0 1');
my $status = $pos->status;

my $moves = $status->{moves}; # there's only one move, E1-D2
print Chess::Rep::get_field_id($moves->[0]{from}) . '-' .
      Chess::Rep::get_field_id($moves->[0]{to});

print $status->{check};   # 1
print $status->{mate};
print $status->{stalemate};

REPRESENTATION

Pieces and colors

As of version 0.4, a piece is represented as a byte, as follows:

p => 0x01  # black pawn
n => 0x02  # black knight
k => 0x04  # black king
b => 0x08  # black bishop
r => 0x10  # black rook
q => 0x20  # black queen
P => 0x81  # white pawn
N => 0x82  # white knight
K => 0x84  # white king
B => 0x88  # white bishop
R => 0x90  # white rook
Q => 0xA0  # white queen

This representation is incompatible with older versions, which were representing a piece as a char. Performance is the main reason for this change. For example, in order to test if a piece is king (regardless the color) we now do:

$p & 0x04

while in versions prior to 0.4 we needed to do:

lc $p eq 'k'

Similarly, if we wanted to check if a piece is a queen or a bishop, in previous version we had:

lc $p eq 'q' || lc $p eq 'b'

while in the new version we do:

$p & 0x28

which is considerably faster. (if you wonder why the difference between 0.03 milliseconds and 0.01 milliseconds matters all that much, try writing a chess engine).

To determine the color of a piece, AND with 0x80 (zero means a black piece, 0x80 is white piece). In previous version we needed to do uc $p eq $p, a lot slower.

Position

The diagram is represented in the "0x88 notation" (see [2]) -- an array of 128 elements, of which only 64 are used. An index in this array maps directly to a row, col in the chess board like this:

my ($row, $col) = (1, 4); # E2
my $index = $row << 4 | $col;  ( = 0x14)

Valid row and col numbers are 0..7 (so they have bit 4 unset), therefore it's easy to detect when an index is offboard by AND with 0x88. Read [2] for more detailed description of this representation.

Some terms used in this doc

Following, when I refer to a field "index", I really mean an index in the position array, which can be 0..127. Using get_index() you can compute an index from a field ID.

By field ID I mean a field in standard notation, i.e. 'e4' (case insensitive).

When I refer to row / col, I mean a number 0..7. Field A1 corresponds to row = 0 and col = 0, and has index 0x00. Field H7 has row = 7, col = 7 and index 0x77.

Internally this object works with field indexes.

OBJECT METHODS

new($fen)

Constructor. Pass a FEN string if you want to initialize to a certain position. Otherwise it will be initialized with the standard starting position.

reset()

Resets the object to standard start position.

set_from_fen($fen)

Reset this object to a position described in FEN notation.

get_fen()

Returns the current position in standard FEN notation.

status()

Returns the status of the current position. The status is automatically computed whenever the position is changed with set_from_fen() or go_move(). The return valus is a hash as follows:

{
  moves      => \@array_of_all_legal_moves,
  pieces     => \@array_of_pieces_to_move,
  hash_moves => \%hash_of_all_legal_moves,
  type_moves => \%hash_of_moves_by_type_and_target_field,
  check      => 1 if king is in check, undef otherwise,
  mate       => 1 if position is mate, undef otherwise,
  stalemate  => 1 if position is stalemate, undef otherwise
}

The last three are obvious -- simple boolean indicators that describe the position state. The first three are:

  • moves

    An array of all the legal moves. A move is represented as a hash containing:

    {
      from  => $index_of_origin_field,
      to    => $index_of_target_field,
      piece => $id_of_the_moved_piece
    }
  • hash_moves

    A hash table containing as keys all legal moves, in the form "$from_index:$to_index". For example, should E2-E4 be the single legal move, then this hash would be:

    {
      '35-55' => 1
    }
  • type_moves

    Again a hash table that maps target fields to piece types. For example, if you want to determine all white bishops that can move on field C4 (index 58), you can do the following:

    my $a = $self->status->{type_moves}{58}{0x88};

    @$a now contains the indexes of the fields that currently hold white bishops that are allowed to move on C4.

    This hash is mainly useful when we interpret standard algebraic notation.

set_piece_at($where, $piece)

Sets the piece at the given position. $where can be:

- a full index conforming to our representation
- a standard field ID (i.e. 'e2')

The following are equivalent:

$self->set_piece_at(0x14, 'P');
$self->set_piece_at('e2', 'P');

Piece can be a piece ID as per our internal representation, or a piece name such as 'P', 'B', etc.

This function does not rebuild the valid moves hashes so if you call status() you'll get wrong results. After you setup the position manually using this function (same applies for set_piece_at_index()) you need to call $self->compute_valid_moves().

set_piece_at_index($index, $p)

Sets the piece at the given index to $p. Returns the old piece. It's similar to the function above, but faster as it only works with field indexes.

get_piece_at($where, $col)

Returns the piece at the given position. $where can be:

- a full index conforming to our representation
- a 0..7 row number (in which case $col is required)
- a standard field ID (i.e. 'e2')

The following are equivalent:

$self->get_piece_at('e2');
$self->get_piece_at(0x14);
$self->get_piece_at(1, 4);

If you call this function in array context, it will return the index of the field as well; this is useful if you don't pass a computed index:

($piece, $index) = $self->get_piece_at('e2');
# now $piece is 'P' and $index is 0x14

get_piece_at_index($index)

Similar to the above function, this one is faster if you know for sure that you pass an $index to it. That is, it won't support $row, $col or field IDs, it only does field indexes.

$self->get_piece_at_index(0x14)
  == $self->get_piece_at(1, 4)
  == $self->get_piece_at('e2')
  == $self->get_piece_at(0x14)

to_move()

Returns (and optionally sets if you pass an argument) the color to move. Colors are 0 (black) or 1 (white).

go_move($move)

Updates the position with the given move. The parser is very forgiving; it understands a wide range of move formats:

e4, e2e4, exf5, e:f5, e4xf5, e4f5, Nc3, b1c3, b1-c3,
a8=Q, a7a8q#, a7-a8=q#, a8Q, etc.

After the move is executed, the position status is recomputed and you can access it calling $self->status. Also, the turn is changed internally (see to_move()).

This method returns a hash containing detailed information about this move. For example, for "axb8=Q" it will return:

{
  from        => 'A7'
  from_index  => 0x60
  from_row    => 6
  from_col    => 0
  to          => 'B8'
  to_index    => 0x71
  to_row      => 7
  to_col      => 1
  piece       => 'P'
  promote     => 'Q'
  san         => 'axb8=Q'
}

Of course, the exact same hash would be returned for "a7b8q", "A7-b8=Q", "b8Q". This method parses a move that can be given in a variety of formats, and returns a canonical representation of it (including a canonical SAN notation which should be understood by any conformant parser on the planet).

compute_valid_moves()

Rebuild the valid moves hashes that are returned by $self->status() for the current position. You need to call this function when you manually interfere with the position, such as when you use set_piece_at() or set_piece_at_index() in order to setup the position.

is_attacked($index, $color, $try_move)

Checks if the field specified by $index is under attack by a piece of the specified $color.

$try_move is optional; if passed it must be a hash of the following form:

{ from  => $from_index,
  to    => $to_index,
  piece => $piece }

In this case, the method will take the given move into account. This is useful in order to test moves in compute_valid_moves(), as we need to filter out moves that leave the king in check.

can_castle($color, $ooo)

Return true if the given $color can castle kingside (if $ooo is false) or queenside (if you pass $ooo true).

has_castled($color)

Returns true (non-zero) if the specified color has castled, or false (zero) otherwise. If the answer to this question is unknown (which can happen if we initialize the Chess::Rep object from an arbitrary position) then it returns undef.

piece_color($piece)

You can call this both as an object method, or standalone. It returns the color of the specified $piece, which must be in the established encoding. Example:

Chess::Rep::piece_color(0x81) --> 0x80 (white (pawn))
Chess::Rep::piece_color(0x04) --> 0 (black (king))
$self->piece_color('e2') --> 0x80 (white (standard start position))

If you call it as a method, the argument must be a field specifier (either full index or field ID) rather than a piece.

get_index($row, $col)

Static function. Computes the full index for the given $row and $col (which must be in 0..7).

Additionally, you can pass a field ID instead (and omit $col).

Examples:

Chess::Rep::get_index(2, 4) --> 45
Chess::Rep::get_index('e3') --> 45

get_index_from_row_col($row, $col)

This does the same as the above function, but it won't support a field ID (i.e. 'e3'). You have to pass it a row and col (which are 0..7) and it simply returns ($row << 4) | $col. It's faster than the above when you don't really need support for field IDs.

get_field_id($index)

Returns the ID of the field specified by the given index.

Chess::Rep::get_field_id(45) --> 'e3'
Chess::Rep::get_field_id('f4') --> 'f4' (quite pointless)

get_row_col($where)

Returns a list of two values -- the $row and $col of the specified field. They are in 0..7.

Chess::Rep::get_row_col('e3') --> (2, 4)
Chess::Rep::get_row_col(45) --> (2, 4)

dump_pos()

Object method. Returns a string with the current position (in a form more readable than standard FEN). It's only useful for debugging.

LINKS

[1] SAN ("Standard Algebraic Notation") is the most popular notation
    for chess moves.

    http://en.wikipedia.org/wiki/Algebraic_chess_notation

[2] Ideas for representing a chess board in memory.

    http://www.cis.uab.edu/hyatt/boardrep.html

AUTHOR

Mihai Bazon, <mihai.bazon@gmail.com> http://www.dynarchlib.com/ http://www.bazon.net/mishoo/

This module was developed for Dynarch Chess -- http://chess.dynarch.com/en.html

COPYRIGHT

Copyright (c) Mihai Bazon 2008. All rights reserved.

This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.