Next: Class MOVE Up: Sather 1.0 Tutorial Previous: Type $CHESS_DISPLAYand Related

Type $PLAYERand Related Classes

$PLAYER

Similar to the situation between the abstract type $CHESS_DISPLAY and the classes ASCII_DISPLAY and X_DISPLAY, the players are organized with subtyping and include as well. The abstract type $PLAYER specifies the common interface.


type $PLAYER is
  getmove(b:BOARD):MOVE;
  ask_pawn_xchg:CHAR;
end;

Class PLAYER

This is a class of type $PLAYER, which will not be used to instantiate. There will be no objects of type PLAYER. The main purpose of this class is to declare attributes and routines that are common to other classes of type $PLAYER, which include the implementation of this class.

The routine getmove does not provide a basic implementation. However, for consistency with the interface required by $PLAYER, a dummy implementation must be given. The routine ask_pawn_xchg provides a default implementation.


class PLAYER < $PLAYER is

  attr iswhite:BOOL;

  create(iswhite:BOOL):SAME is
    ret : SAME := new;
    ret.iswhite := iswhite;
    return ret;
  end;

  getmove(b:BOARD):MOVE is
    raise "PLAYER:invalid call to getmove\n";
  end;

  ask_pawn_xchg:CHAR is
    return 'Q';
  end;

end; -- of class PLAYER

This is a good place to look at the list of available class elements. We have already encountered routine definitions and include statements. Iter definitions are similar to routine definitions. All class elements can be declared private. Private elements can only be accessed from within the implementation of the class. Per default, class elements are public. It is worthwhile to take a closer look at the other class elements:

const
Constant attributes are accessible by all objects in a class and may not be assigned to. Constant attributes are initialized. They are accessible even if no object of the class is created.
shared
Shared attributes are variables that are directly accessible to all objects of a given type. They are accessible even if no object of the class is created. When only a single shared attribute is defined by a clause, it may be provided with an initializing expression which must be a constant expression. If no initialization is given, shared variables are initialized to the default.
attr
Attributes are connected with objects. Each object of a class has an individual set of attribute variables which reflect the state of the object. Attributes are only accessible when an object has been created.

Class HUMAN_PLAYER

A human player will enter his move via the interface. This is coded in the routine getmove that replaces the inherited dummy implementation.

If a human player has the chance to exchange one of his pawns with a queen or a knight, the human player will enter his decision via the interface in routine ask_pawn_xchg.


class HUMAN < $PLAYER is
  include PLAYER;

  getmove(b:BOARD):MOVE is
    return MAIN::display.getmove(iswhite);
  end;
  
  ask_pawn_xchg:CHAR is
    MAIN::display.update(MAIN::board.str);
    return MAIN::display.ask_pawn_xchg;
  end;

end; -- of class HUMAN

Class MINMAX

The automatic player is represented by the class MINMAX. The class is called MINMAX, since the strategy for determining a move is based on a minmax search.

We define a couple of constants first. The boolean constants max and min are later on used to determine whether the minmax search is at a max- or at a min-level. The constant max_depth gives the maximal depth of the search tree. If max_depth is 3, then (1) all potential next moves, (2) all reactions of the opponent player and (3) all potential future reactions to these are considered.

The best moves of phase (1) are gathered in a dynamically sized list of type FLIST, as defined in the library file Library/flist.sa. FLIST will store all moves that will eventually result in the same board evaluation on level (3).

The random number generator declared in line 35 is used to select an arbitrary move from the list. MS_RANDOM_GEN is a class that is defined in the Sather Libraries. You find it in the file Library/rnd.sa The random number object is created and initialized in the create routine in line 40.


class MINMAX < $PLAYER is
  include PLAYER;

  const max : BOOL := true;
  const min : BOOL := ~max;
  const max_depth : INT  := 3;

  attr bestmoves : FLIST{MOVE};

  shared rnd : MS_RANDOM_GEN;
  
  create(iswhite:BOOL):SAME is
    ret ::= new;
    ret.iswhite := iswhite;
    ret.bestmoves := #FLIST{MOVE};
    rnd := #MS_RANDOM_GEN;
    rnd.init(4711);
    
    return ret;
  end;

The getmove routine at first tells the viewing user that it is ``thinking" (line 46). Then it uses the routine minmax, which is described below, to find the best move. There might be more than one move that is considered to be ``best". The list bestmoves stores all of these. If there are no available moves, i.e., if the list of bestmoves is empty, then the player is mate - the game is over. This is checked in line 54.

Otherwise the random number generator returns a value in . This is multiplied by the size of the list of available best moves. Before multiplication, size, which is an integer value, is cast to be of type FLTD. The product is rounded to the floor and then cast into an integer value by the routine int. The result is then used to index into the list of possible best moves.

Before returning the move to the caller, it is displayed in line 61.

  
  getmove(board:BOARD):MOVE is
    ret : MOVE;

    MAIN::display.thinking(board.white_to_play);
    
    if board.white_to_play then
      -- minmax returns a value, that is nor needed. However, Sather does
      -- require to use the return value somehow.
      dummy ::= minmax(board,max,max_depth);
    else
      dummy ::= minmax(board,min,max_depth);
    end;

    if bestmoves.size = 0 then
      return #MOVE("quit",board.white_to_play);
    else

      ret := bestmoves[(bestmoves.size.fltd * rnd.get).floor.int];

      bestmoves.clear;
      text : STR;
      text := ret.from.str + "-" + ret.to.str;
      MAIN::display.showmove(text);
      return ret;
    end;
  end; -- of getmove

The private routine minmax returns a floating point value, FLT. FLT is specified in the library class FLT. See file Library/flt.sa for details.

The body of minmax has a good example of nested iter calls: The first loop (lines 74-103) considers all pieces on the board of my color. The inner loop (lines 75-102) then for each of these pieces considers target positions of potential moves. (It is explained later on, what an ordinary move is. Just ignore this flag for the time being.)

The move created in line 77 is guaranteed to be correct, i.e., the piece is of the correct color and the target position is correct with respect to the basic movement rules of chess. The only condition that is not guaranteed to hold is whether the own king is exposed to be in check after the piece is moved. This is checked in apply_move_with_own_check_test. See line 79.

After a move has been applied successfully, we either consider the possible reactions recursively (line 83), or evaluate the value of the board in line 81.

The depth-first search requires backtracking. This is done in line 100 by calling board.unapply_move.

  
  private minmax(board:BOARD,minmax:BOOL,depth:INT):FLT is
  
    move : MOVE;
    val,bv : FLT;
    pos : POS;

    if minmax = max then
      val := -1000.0;
    else
      val := 1000.0;
    end;

    loop piece::=board.my_piece!;
      loop
        pos :=piece.move!(board,PIECE::ordinary);
        
        move := #MOVE(piece,pos);
        move.piece := piece;

        if board.apply_move_with_own_check_test(move) then
        
          if depth = 1 then
            bv := board.board_value;
          else
            bv := minmax(board,~minmax,depth - 1);
          end;
          
          
          -- If this move really is better than previous ones,
          -- the list of best moves found so far is erased.
            
          if depth = max_depth and (   (minmax = max and bv > val)
                                              or (minmax = min and bv < val))
          then
            bestmoves.clear;
          end;
          
          -- If this move is not worse than previous ones, the move
          -- is added to the list of best moves found so far.
          
          if depth = max_depth and (   (minmax = max and bv >= val)
                                              or (minmax = min and bv <= val))
          then
            val := bv;
            bestmoves := bestmoves.push(move);
          end;
          
          board.unapply_move;
        end;
      end;
    end;
    return val;
  end; -- of minmax
  
end; -- of class MINMAX

The following remark will be completely understandable only after the type $PIECE and the concrete subtypes have been presented in section 9. For reasons of completeness note that line 76 is a dispatched iter call. Depending on the concrete type of the piece:$PIECE a different iter is called.

In Sather 1.0.2 dispatched iters are not implemented. The typecase statement can be used to implement the intended behavior:


        typecase piece
        when PAWN    then pos:=piece.move!(board,PIECE::ordinary);
        when ROOK    then pos:=piece.move!(board,PIECE::ordinary);
        when KNIGHT then pos:=piece.move!(board,PIECE::ordinary);
        when BISHOP  then pos:=piece.move!(board,PIECE::ordinary);
        when KING      then pos:=piece.move!(board,PIECE::ordinary);
        when QUEEN  then pos:=piece.move!(board,PIECE::ordinary);
        else
        end;



Next: Class MOVE Up: Sather 1.0 Tutorial Previous: Type $CHESS_DISPLAYand Related


gomes@ICSI.Berkeley.EDU
Tue May 30 21:15:13 PDT 1995