[comp.sources.games.bugs] Official patch #3 for othello; *PLEASE* apply it.

richb@sunaus.oz (Rich Burridge) (05/04/90)

[I've sent a copy of this to the moderator of comp.sources.games for
 archiving].

As many of you will have realised I screwed up with patches 1&2.

It seems the changes to the file makemove.c never got into patch #1.
This patch really is a release of the current version of makemove.c,
with patchlevel.h and CHANGES adjusted accordingly.

Rich Burridge - Aus.Sun.COM

------CUT HERE------CUT HERE------
#! /bin/sh
# this is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh to create the files:
#	patch.3
#	makemove.c
# This archive created: Thu May  3 10:37:34 EST 1990
#
#
export PATH; PATH=/bin:$PATH
#
if [ -f patch.3 ]
then
echo shar: will not over-write existing file patch.3
else
echo shar: extracting 'patch.3',      830 characters
cat > patch.3 <<'Funky_Stuff'

------- patchlevel.h -------
*** /tmp/da06251	Thu May  3 10:36:29 1990
--- patchlevel.h	Thu May  3 10:32:18 1990
***************
*** 19,22 ****
   *  to me, then an attempt will be made to fix them.
   */
  
! #define  PATCHLEVEL  2
--- 19,22 ----
   *  to me, then an attempt will be made to fix them.
   */
  
! #define  PATCHLEVEL  3

------- CHANGES -------
*** /tmp/da06254	Thu May  3 10:36:30 1990
--- CHANGES	Thu May  3 10:34:16 1990
***************
*** 86,88 ****
--- 86,93 ----
           It would be nice if (at least on the tty version) the text
           "black" was replaced with "X" or "black (X)" and likewise for
           white.
+ 
+ v1.3 - patchlevel 3. 3rd May 1990.
+ 
+        * Release of the current version of makemove.c, because the changes
+          to this file at patch level #1 never made it out.
Funky_Stuff
len=`wc -c < patch.3`
if [ $len !=      830 ] ; then
echo error: patch.3 was $len bytes long, should have been      830
fi
fi # end of overwriting check
if [ -f makemove.c ]
then
echo shar: will not over-write existing file makemove.c
else
echo shar: extracting 'makemove.c',    12624 characters
cat > makemove.c <<'Funky_Stuff'
/*LINTLIBRARY*/

/*  %Z%%M% %I% %E%
 *
 *  Best computer strategy routines used by the othello program.
 *
 *  Copyright: Chris Miller, 1979, 1981, 1984.
 *
 *  Included with this othello game by
 *          Rich Burridge, Sun Microsystems, Australia.
 *
 *  Permission is given to distribute these sources, as long as the
 *  introductory messages are not removed, and no monies are exchanged.
 *
 *  No responsibility is taken for any errors on inaccuracies inherent
 *  either to the comments or the code of this program, but if reported
 *  to me, then an attempt will be made to fix them.
 */

#include "othello.h"
#include "extern.h"


makemove(board, player, depth)
BOARD *board ;
int depth, player ;
{
  int d,value ;
  int mv = NOMOVE ;
 
  if (!depth)
    {
      if ((mv = nextmove(board, NOMOVE, (BOARD *) 0, player)) == NOMOVE)
  return(-1) ;
      else return(mv) ;
    }

  if ((mv = nextmove(board, NOMOVE, (BOARD *) 0, player)) == NOMOVE)
    {
      if (remark) message(REMARK_MES, "I'm not dead, just resting") ;
      return(-1) ;
    }
  ab_count = se_count = 0 ;
  if (nextmove(board, mv, (BOARD *) 0, player) == NOMOVE)
    {
      value = static_eval(board) ;
      if (remark) make_remark(player,value) ;
      return(mv) ;
    }

/* Exhaustive for last 3*depth ply (at most 10!). */

  if ((board->moves_left <= 10) && (board->moves_left <= 3*depth))
    d = board->moves_left ;
  else d = depth ;
 
  set_aspiration(board) ;
  value = alfabeta(board, d, player*INFINITY, player, &mv) ;
  if (remark) make_remark(player,value) ;
  return(mv) ;
}


nextmove(pos, mv, nextpos, player)
BOARD *pos,*nextpos ;
int mv, player ;
{

/*  On first entry for a given position, move is set to NOMOVE,
 *  i.e. -1.  Moves are generated in a rough plausibility order
 *  given by the list structure move_table, whose head is
 *  move_table[-1], and whose tail-pointer is set to NOMOVE.
 *  The order is purely heuristic, and corresponds roughly to
 *  the values associated by static_eval with different squares.
 */

  static int m_table[65] =
    {
       0,  7,  6,  5,  4, 24, 16,  8, 56, 15,
      14, 13, 12, 25, 17, 49, 48, 23, 22, 21,
      20, 26, 42, 41, 40, 31, 30, 29, 28, 35,
      34, 33, 32, 39, 38, 37, 36, 10, 43, 51,
      59, 47, 46, 45, 44, 27, 19, 50, 58, 55,
      54, 53, 52,  1, 11, -1, 57, 63, 62, 61,
      60, 18,  3,  9,  2
    } ;

  static int *move_table = &(m_table[1]) ;

  while ((mv = move_table[mv]) != NOMOVE)
    if (legal(mv,player,pos))
      {
  if (nextpos) domove(pos,mv,nextpos,player) ;

/* Next position generated only if pointer is non-zero */
  return(mv) ;
      }
    return(NOMOVE) ;  /* No more legal moves */
}


domove(pos, mv, nextpos, player)
BOARD *pos, *nextpos ;
int mv, player ;
{
  register int i ;
  if (pos != nextpos) FOR_BOARD(i) nextpos->square[i] = pos->square[i] ;

  s_move = mv ;
  s_row = mv >> 3 ;
  s_col = mv & 7 ;
  s_player = player ;
  s_opponent = -player ;
  s_flip = TRUE ;
  s_pos = nextpos ;

  nextpos->square[s_move] = player ;

  (void) sandwich(-9) ;
  (void) sandwich(-8) ;
  (void) sandwich(-7) ;
  (void) sandwich(-1) ;
  (void) sandwich(1) ;
  (void) sandwich(7) ;
  (void) sandwich(8) ;
  (void) sandwich(9) ;

  nextpos->moves_left = (pos->moves_left) - 1 ;
}


legal(mv, player, pos)
BOARD *pos ;
int mv, player ;
{
  if (pos->square[mv]) return(FALSE) ;     /* Already occupied */

  s_move = mv ;
  s_row = mv >> 3 ;
  s_col = mv & 7 ;
  s_player = player ;
  s_opponent = -player ;
  s_flip = FALSE ;
  s_pos = pos ;

  return(sandwich(-9) || sandwich(-8) || sandwich(-7) || sandwich(-1) ||
   sandwich(1)  || sandwich(7)  || sandwich(8)  || sandwich(9)) ;
}


/*  Test whether the square move sandwiches a line
 *  of enemy pieces in the direction [row_inc, col_inc];
 *  If (s_flip) then update position by capturing such pieces
 */

sandwich(increment)
register int increment ;
{
  register int square,offset ;
  int row_offset,col_offset,piece,piece_count ;

  if (s_pos->square[s_move+increment] != s_opponent) return(FALSE) ;
 
/*  Quick test to catch most failures -
 *  note that the tested square may not even
 *  be on the board, but the condition is a
 *  sufficient one for failure.
 */
 
  row_offset = (increment < -1 ? s_row  :      /* inc -1: -9, -8, -7 */
    increment >  1 ? 7-s_row : 8) ;            /* inc  1:  7,  8,  9 */
  col_offset = (increment & 4 ? s_col   :      /* inc -1: -9, -1,  7 */
    increment & 1 ? 7-s_col : 8) ;             /* inc  1: -7,  1,  9 */
 
  offset = (row_offset > col_offset ? col_offset : row_offset) ;

/* offset = shortest distance to an edge in the direction of search */

  if (2 > offset) return(FALSE) ;
 
  piece_count = 1 ;
  square = s_move+increment ;
 
  while (--offset)
    {
      if (!(piece = s_pos->square[square += increment]))
  return(FALSE) ;  /* If empty square, give up */
 
      if (piece == s_player) break ;
      else piece_count++ ;      /* Count opponent's pieces encountered */
    }

  if (!offset) return(FALSE) ;

  if (s_flip)
    while (piece_count--)
      s_pos->square[square -= increment] = s_player ;

  return (TRUE) ;
}


/*  Alpha-beta pruning algorithm.
 *
 *  If sign = 1, then try to MAXIMISE score, else MINIMISE.
 *  Parent node wants to MINIMISE/MAXIMISE, so as soon as
 *  we exceed parent_opt, give up and return INFINITY.
 *  Return best move in parent_best.
 *
 *  Externals used:
 *      static_eval(position)
 *      ISMOVE(position,player)   [does player have a legal move?]
 *      game_ended(pos)
 *      typedef BOARD
 *      aspiration(player,depth,position)
 *  [return the aspiration limit for next level of search]
 *      nextmove(position,mv,nextposition,player)
 *  [give the next legal move for player in position;
 *   put the resulting position in nextposition]
 */

alfabeta(pos, depth, parent_opt, sign, parent_best)
BOARD *pos ;
int depth, parent_opt, sign, *parent_best ;
{
  BOARD nextpos ;
  int value,this_opt,this_best = NOMOVE,mv = NOMOVE,asp = 0 ;
 
  if ((depth==0) || game_ended(pos))
    {
      value = static_eval(pos) ;
      *parent_best = NOMOVE ;
      if ((sign*value) > (sign*parent_opt)) return(sign*INFINITY) ;
      else return (value) ;
    }

  ab_count++ ;     /* Record boards dynamically evaluated */
  this_opt = (sign == 1 ? -INFINITY : INFINITY) ;

  if (!ISMOVE(pos,sign))    /* No legal move */
    {
      value = alfabeta(pos,depth,this_opt,-sign,&this_best) ;
      goto valfound ;
    }

  asp = sign * aspiration(sign,depth) ;

  while ((mv = nextmove(pos,mv,&nextpos,sign)) != NOMOVE)
    {
      value = alfabeta(&nextpos,depth-1,this_opt,-sign,&this_best) ;

valfound:

      if ((sign*value) >= (sign*parent_opt)) return(sign*INFINITY) ;

      if ((sign*value) > asp)
        {
          *parent_best = mv ;
          return(value) ;
        }

      if ((sign*value) > (sign*this_opt))
        {
          this_opt = value ;
          *parent_best = mv ;
        }

/*  If two moves have same evaluation, choose
 *  uniformly randomly between them (of course,
 *  where several moves have same value, this
 *  is biased towards the later ones
 */

      if ((value == this_opt) && (rand() & 020)) *parent_best = mv ;
    }
  return(this_opt) ;
}


/*  Aspirations are as follows:
 *  Aspiration-level       Aspiration
 *      1         The worse of 0, static value
 *      2         The static value
 *      3         10% better than the static value
 *      4         25% better than the static value
 *      5         Any winning move
 *      6         The move winning by the greatest margin
 *
 *  It is assumed that the opponent has
 *  the same level of aspiration as the program.
 */

set_aspiration(position)
BOARD *position ;
{
  if (aspire == MAXASPIRE)
    {
      expected = 0 ;
      margin = INFINITY ;
      return ;
    }
 
  if (aspire == (MAXASPIRE-1))
    {
      expected = 0 ;
      margin = INFINITY-64 ;
      return ;
    }
 
  expected = static_eval(position) ;
 
  switch (aspire)
    {
      case 1 : expected /= 2 ;
               margin = -abs(expected) ;
               return ;
      case 2 : margin = 0 ;
               return ;
      case 3 : margin = abs(expected/10) ;
               return ;
      case 4 : margin = abs(expected/4) ;
               return ;
    }
}


aspiration(player, depth)
int player,depth ;
{
  if (aspire == MAXASPIRE) return(player*INFINITY) ;
  if (depth < 3 && aspire > 1) return(player*(INFINITY-64)) ;
  return(expected+player*margin) ;
}


#define  VCORN   100     /* Corner */
#define  VORTH   -30     /* Orthogonally next to corner */
#define  VDIAG   -50     /* Diagonally     "   "    "   */
#define  VEDGE   20      /* On edge */
#define  VNEXT   -7      /* Next to edge */
#define  VNORM   1       /* Elsewhere */

/*  Square values (only valid when a "vulnerable" piece occupies the
 *  square in question)
 */

static_eval(pos)
BOARD *pos ;
{
  static int model[64] = {
     VCORN, VORTH, VEDGE, VEDGE, VEDGE, VEDGE, VORTH, VCORN,
     VORTH, VDIAG, VNEXT, VNEXT, VNEXT, VNEXT, VDIAG, VORTH,
     VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE,
     VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE,
     VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE,
     VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE,
     VORTH, VDIAG, VNEXT, VNEXT, VNEXT, VNEXT, VDIAG, VORTH,
     VCORN, VORTH, VEDGE, VEDGE, VEDGE, VEDGE, VORTH, VCORN
   } ;
 
  register int i ;
  register int value = 0 ;
  int scores[64] ;

  se_count++ ;         /* Record number of static evaluations */
  if (game_ended(pos))
    {
      FOR_BOARD(i) value += pos->square[i] ;
      return (value > 0 ? INFINITY+value-64  :
        value < 0 ? -INFINITY+value+64 : 0) ;
    }

  FOR_BOARD(i) scores[i] = 0 ;

  find_invulnerable(pos,scores) ;

/*  Scores now contains VINVUL (or -VINVUL)
 *  for each invulnerable piece, and 0 elsewhere;
 *  now fill in other evaluations (special cases:
 *  next to corner [bad!], on edge[good!], next to
 *  edge[poor], anywhere else[boring])
 */

  FOR_BOARD(i) value += (scores[i] ? scores[i] : model[i]*pos->square[i]) ;
  return (value) ;
}


game_ended(pos)
BOARD *pos ;
{
  if (!(pos->moves_left)) return(TRUE) ;
  return((!ISMOVE(pos,WHITE)) && !(ISMOVE(pos,BLACK))) ;
}


/*  This function finds invulnerable pieces, and scores them
 *  appropriately; it does not find ALL invulnerable pieces -
 *  in fact, only concave blocks including a corner - but
 *  nevertheless should provide a good approximation.
 */

find_invulnerable(board, scores)
BOARD *board ;
int scores[64] ;
{
  int hwm,corner,value,i,j ;

  if ((corner = board->square[0]) != 0)
    {
      value = corner*VINVUL ;
      hwm = 7 ;

      for (i = 0; i < 56; i += 8)
        {
          if (board->square[i] != corner) break ;
          scores[i] = value ;
          for (j = 1; j < hwm; j++)
            {
              if (board->square[i+j] != corner)
                {
                  hwm = j ;
                  break ;
                }
              scores[i+j] = value ;
            }
        }
      scores[0] = corner*VCORN ;
    }

  if ((corner = board->square[7]) != 0)
    {
      value = corner*VINVUL ;
      hwm = 0 ;

      for (i = 0; i < 56; i+= 8)
        {
          if (board->square[i+7] != corner) break ;
          scores[i+7] = value ;
          for (j = 6; j > hwm; j--)
            {
              if (board->square[i+j] != corner)
                {
                  hwm = j ;
                  break ;
                }
              scores[i+j] = value ;
            }
        }
      scores[7] = corner*VCORN;
    }

  if ((corner = board->square[56]) != 0)
    {
      value = corner*VINVUL ;
      hwm = 7 ;

      for (i = 56; i > 0; i -= 8)
        {
          if (board->square[i] != corner) break ;
          scores[i] = value ;
          for (j = 1; j < hwm; j++)
            {
              if (board->square[i+j] != corner)
                {
                  hwm = j ;
                  break ;
                }
              scores[i+j] = value ;
            }
        }
      scores[56] = corner*VCORN ;
    }

  if ((corner=board->square[63]) != 0)
    {
      value = corner*VINVUL ;
      hwm = 0 ;

      for (i = 56; i > 0; i -= 8)
        {
          if (board->square[i+7] != corner) break ;
          scores[i+7] = value ;
          for (j = 6; j > hwm; j--)
            {
              if (board->square[i+j] != corner)
                {
                  hwm = j ;
                  break ;
                }
              scores[i+j] = value ;
            }
        }
      scores[63] = corner*VCORN ;
    }
}
Funky_Stuff
len=`wc -c < makemove.c`
if [ $len !=    12624 ] ; then
echo error: makemove.c was $len bytes long, should have been    12624
fi
fi # end of overwriting check