The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

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

Pieces are represented as single letters, in the standard notation:

  P - pawn
  N - knight
  B - bishop
  R - rook
  Q - queen
  K - king

We use uppercase to represent white pieces and lowercase for black pieces, as in standard FEN notation.

Whenever you need to deal with colors alone, 0 means black and 1 is white. For example the piece_color($p) function returns 0 for a black piece and 1 for a white piece.

Position

The diagram is represented in an array, according to an idea originally developed for Sargon [2]. It is imagined as a two dimensional array of 12 rows and 10 columns (although we really use a plain array and just map field indexes to row/cols as needed). Using a bigger board, we keep an "undef" value in the fields that are off-board and it becomes very easy to generate moves. Here's how it looks like:

  * * * * * * * * * *
  * * * * * * * * * *
  * r n b q k b n r *
  * p p p p p p p p *
  * - - - - - - - - *
  * - - - - - - - - *
  * - - - - - - - - *
  * - - - - - - - - *
  * P P P P P P P P *
  * R N B Q K B N R *
  * * * * * * * * * *
  * * * * * * * * * *

The stars are "off-board fields" (undef-s). The dashes are empty squares (contain 0).

Now, let's see the array above, this time with field numbers (indexes) instead of pieces.

    110   111   112   113   114   115   116   117   118   119
    100   101   102   103   104   105   106   107   108   109
        +-----------------------------------------------+
     90 |  91    92    93    94    95    96    97    98 |  99
     80 |  81    82    83    84    85    86    87    88 |  89
     70 |  71    72    73    74    75    76    77    78 |  79
     60 |  61    62    63    64    65    66    67    68 |  69
     50 |  51    52    53    54    55    56    57    58 |  59
     40 |  41    42    43    44    45    46    47    48 |  49
     30 |  31    32    33    34    35    36    37    38 |  39
     20 |  21    22    23    24    25    26    27    28 |  29
        +-----------------------------------------------+
     10    11    12    13    14    15    16    17    18    19
      0     1     2     3     4     5     6     7     8     9

The indexes that map to valid board fields are 21..28, 31..38, ... 91..98. For these fields, our array will contain 0 if the field is empty, or a piece character. For offboard fields, our array will contain undef. It's thus very easy to generate moves for pieces.

For example, let's say that we have a N (knight) on field 31 ('a2' on a chess board) and we need to generate a list of all fields where it can possibly move. All we have to do is to add some values to the starting position (31) and check that the resulting field is in board.

  31 + 19 = 50  (off board)
  31 + 21 = 52  (valid)
  31 +  8 = 39  (off board)
  31 + 12 = 43  (valid)
  31 - 19 = 12  (off board)
  31 - 21 = 10  (off board)
  31 -  8 = 23  (valid)
  31 - 12 = 19  (off board)

Using simple arithmetic operations we determined the 3 fields that a knight on a3 is allowed to move to. Similar logic can be easily applied for all piece types.

Some terms used in this doc

Following, when I refer to a field "index", I really mean an index in that array, which can be 0..119. 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 21. Field H7 has row = 7, col = 7 and index 98.

Internally this object works with field indexes. TODO: most functions accept field ID-s too (or even row/col), which probably slows things down because they have to check the form of the argument and determine a correct index. I should optimize this.

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 by an internal function -- _compute_valid_moves() -- and it's a hash as follows:

  {
    moves      => \@array_of_all_legal_moves,
    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}{B};

    @$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(35, 'P');
  $self->set_piece_at('e2', 'P');

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(35);
  $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 35

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  => 81
    from_row    => 6
    from_col    => 0
    to          => 'B8'
    to_index    => 92
    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).

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).

piece_color($piece)

You can call this both as an object method, or standalone. It returns the color of the specified piece. Example:

  Chess::Rep::piece_color('P') --> 1
  Chess::Rep::piece_color('k') --> 0
  $self->piece_color('e2') --> 1  (in standard 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_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.atariarchives.org/deli/computer_chess.php

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.