A “Chess Game” in Ada 2012?

Developing a chess game can be a difficult task. This article shows how the implementation of a number of paradigms and services underpinned by Ada 2012 makes it possible to ease this task.

Introduction

As part of the Ada trainings provided by Systerel, a number of exercises are proposed. Everything being very technical, these exercises also have a playful side as shown in the “chess game” example proposed within the framework of our training “Ada2012 - The keys”.

Through this article we will present some elements of what the Ada 2012 language brings to the development of such a chess game.

The code presented is the framework of the engine of a basic chess game (1200 ELO) and representing 2000 lines of code in Ada 2012. The GUI is provided by a client not presented here and provided during the training.

Only basic knowledge of chess game rules are necessary to follow this article.

Although this will not be presented in this article, the implementation also uses traits specific to the Ada 2012 language such as:

  • Static predicates,
  • Quantified expressions,
  • Pre/post conditions.

Oriented Object Design

A chess game is a set of pieces of different types (pawn, bishop, etc.) but having common characteristics:

  • They have a color (White or Black),
  • They have a position on a chessboard,
  • They move on the same board respecting precise rules, some of which are common to all pieces.

We therefore consider here that:

  • A chessboard is a “container” of pieces,
  • A piece is a cursor on this container.
subtype Square_T is Positive range 1 .. 8 * 8;

-- The chessboard.
type Chessboard_T is array (Square_T) of access Piece_T'Class;
type Board_T is tagged record
   Chessboard : Chessboard_T;
end record;

type Color_T is (Black, White);

-- The pieces class
type Piece_T (Color : Color_T) --> its color
is abstract tagged record
   Square : Square_T; --> its position
   Board : access Board_T; --> its board
end record;

-- The weight of a piece is used by the evaluation function (see next)
function Weight (Piece : in Piece_T) return Integer is abstract
with Inline => True;

-- we need to know the rules for moving a piece
function Move_Valid_To (Piece : in Piece_T;
                        To : in Square_T) return Boolean is abstract
with Inline => True;

The class function Valid_To implements the rules common to all the pieces with respect to their movements:

function Valid_To (Piece : in Piece_T'Class;
                   To : in Square_T) return Boolean is
   Chessboard : constant Chessboard_T := Piece.Board.Chessboard;
   Result: Boolean := True;
begin
   if Chessboard (To) /= null and then 
      Chessboard (To).Color = Piece.Color
   then
      -- I can't go on a square containing a piece of my color.
      -- I can't "stand still."
      Result := False;
   end if;

   return Result;

end Valid_To;

Knowing that each part will have to implement its own function Move_Valid_To (the abstract above) to verify the validity of his movements.

Now we must define the pieces of our board:

type Pawn_T is new Piece_T with null record;

overriding 
function Move_Valid_To (Pawn : in Pawn_T;
                        To: in Square_T) return Boolean;

overriding
function Weight (Pawn : in Pawn_T) return Integer is 
   (if Pawn.Color = White then -1 else +1) with Inline => True;

With here for example:

overriding
function Move_Valid_To (Pawn : in Pawn_T;
                        To : in Square_T) return Boolean is

   Result : Boolean := Pawn.Valid_To (To);
begin
   if Result then
      Result := ... -- test of the validity of the movement specific to the pawn
   end if;
   return Result;
end Move_Valid_To;

The knight:

type Knight_T is new Piece_T with null record;

overriding
function Move_Valid_To (Knight : in Knight_T;
                        To: in Square_T) return Boolean
with Inline => True;

overriding
function Weight (Knight : in Knight_T) return Integer is
   (if Knight.Color = White then -500 else +500)
with Inline => True;

The queen, the king, the rook, etc…

And populate the board:

Chessboard : aliased Chessboard_T := (Chessboard => (1 | 8 => new Rook_T(Black),
                                                    ...
                                                    57 | 64 => new Rook_T(White),
                                                    ...
                                                    others => null)); --> empty cells

Our chessboard being populated, it is enough to move a piece and the appropriate method to verify the validity of the movement of this will be called by redirect.

-- a1(57) a2(49) 
if Chessboard.board (57).Move_Valid_To (49) then
...

-- a2(49) a3(41) 
if Chessboard.board (49).Move_Valid_To (41) then
...

-- a3(41) a4(33) CONSTRAINT_ERROR (access check)
if Chessboard.board (41).Move_Valid_To (33) then
...

Implementation of AI

The AI of the game is implemented through a Mini-Max algorithm consisting in computing successively all the possible moves for each of the players. An evaluation function makes it possible to associate a value to a game situation for each move played. All of the possibilities will then create a tree of possible moves:

  • The root of the tree represents the current state of the game (here Empty_Tree).
  • A node represents a legal situation in the game.
  • The children of a node are the situations that can be reached from this node by playing by the rules.
  • A branch represents a sequence of ½ legal moves in the game.

Analysis of this tree (upwards analysis from its leaves) thus makes it possible to know the initial move making it possible to have the best score:

Imagined in 1928 by Von Neumann, the basic postulate of this algorithm is that the computer’s opponent will always play the best move possible.

However, this algorithm has two drawbacks:

  • He explores all the branches of the tree without any form of distinction between these, while some will produce no move opportunity.
  • The number of elements in the tree explodes when the depth increases. If we calculated all the possible moves with a depth of 100, we would get a tree with more elements that nucleons in the universe, that is ~$10^{80}$ !

A more efficient algorithm such as Alpha-Beta (or Alpha-Beta pruning) could have been implemented but in the context of the exercise it was decided to implement the simplest algorithm.

The move tree has been implemented using the new Ada 2012 container Ada.Containers.Multiway_Trees.

Each node in the tree contains the following information:

  • The color played,
  • The half move played (From/To),
  • The board resulting from the half move played,
  • The value of this chessboard (the result of the evaluation function),
type Possible_Move_T is record
  From, To: Square_T := Square_T'First;
  Color : Color_T := White;
  Value : Integer  = 0;
  Chessboard : Ref_Board_T := null;
end record;

package Pkg_Move_Tree is new
        Ada.Containers.Multiway_Trees (Element_Type => Possible_Move_T);

Move_Tree : Tree := Pkg_Move_Tree.Empty_Tree; --> root

The evaluation function used here looks like:

function Value (Chessboard : in Chessboard_T;
                Color : in Color_T) return Integer is
...
begin
  for Piece of Board.ChessBoard loop
    if Piece /= null then
      Result: = Result + Piece.Weight;
    end if;
  end loop;
  return Result + ... . ;
end Value;

The construction of the tree and its browsing are done through the many services offered by the Ada.Containers.Multiway_Trees package.

Conclusion

The Ada 2012 language has brought new features such as:

  • Programming by contract and the notion of aspects,
  • New forms of expression and predicates,
  • Support for SMP architectures,
  • Iterators,
  • An extension of the predefined environment,
  • Various and varied improvements.

Apart from semantic corrections or “aesthetic” improvements, and like its “predecessors”, Ada 2012 put particular emphasis on new features to guarantee safety and integrity of the applications developed while embracing new technologies like SMP.

Although the implementation presented here could have been developed in another language, the fact that it was carried out in Ada 2012 contributed to having a concise, legible and reliable source code.

References

  1. https://www.ada2012.org/
  2. https://en.wikibooks.org/wiki/Ada_Programming/Ada_2012

Comments