billr@saab.CNA.TEK.COM (Bill Randle) (05/04/90)
Submitted-by: Rich Burridge <rburridge@sun.COM> Posting-number: Volume 9, Issue 45 Archive-name: othello2/Patch3 Patch-To: othello2: Volume 9, Issue 37-40 [from the author...] [[I screwed up. It seems the changes to the file makemove.c never got into patch #1. It's easier if I release a copy of the current version of makemove.c, and a small patch to adjust the patchlevel and update the CHANGES file. Sorry for the confusion. Rich Burridge - richb@Aus.Sun.COM]] #! /bin/sh # This is a shell archive. Remove anything before this line, then unpack # it by saving it into a file and typing "sh file". To overwrite existing # files, type "sh file -c". You can also feed this as standard input via # unshar, or by typing "sh <file", e.g.. If this archive is complete, you # will see the following message at the end: # "End of shell archive." # Contents: makemove.c patches03 # Wrapped by billr@saab on Thu May 3 14:43:09 1990 PATH=/bin:/usr/bin:/usr/ucb ; export PATH echo shar: Extracting \"'makemove.c'\" \(12624 characters\) sed "s/^X//" >'makemove.c' <<'END_OF_FILE' X/*LINTLIBRARY*/ X X/* %Z%%M% %I% %E% X * X * Best computer strategy routines used by the othello program. X * X * Copyright: Chris Miller, 1979, 1981, 1984. X * X * Included with this othello game by X * Rich Burridge, Sun Microsystems, Australia. X * X * Permission is given to distribute these sources, as long as the X * introductory messages are not removed, and no monies are exchanged. X * X * No responsibility is taken for any errors on inaccuracies inherent X * either to the comments or the code of this program, but if reported X * to me, then an attempt will be made to fix them. X */ X X#include "othello.h" X#include "extern.h" X X Xmakemove(board, player, depth) XBOARD *board ; Xint depth, player ; X{ X int d,value ; X int mv = NOMOVE ; X X if (!depth) X { X if ((mv = nextmove(board, NOMOVE, (BOARD *) 0, player)) == NOMOVE) X return(-1) ; X else return(mv) ; X } X X if ((mv = nextmove(board, NOMOVE, (BOARD *) 0, player)) == NOMOVE) X { X if (remark) message(REMARK_MES, "I'm not dead, just resting") ; X return(-1) ; X } X ab_count = se_count = 0 ; X if (nextmove(board, mv, (BOARD *) 0, player) == NOMOVE) X { X value = static_eval(board) ; X if (remark) make_remark(player,value) ; X return(mv) ; X } X X/* Exhaustive for last 3*depth ply (at most 10!). */ X X if ((board->moves_left <= 10) && (board->moves_left <= 3*depth)) X d = board->moves_left ; X else d = depth ; X X set_aspiration(board) ; X value = alfabeta(board, d, player*INFINITY, player, &mv) ; X if (remark) make_remark(player,value) ; X return(mv) ; X} X X Xnextmove(pos, mv, nextpos, player) XBOARD *pos,*nextpos ; Xint mv, player ; X{ X X/* On first entry for a given position, move is set to NOMOVE, X * i.e. -1. Moves are generated in a rough plausibility order X * given by the list structure move_table, whose head is X * move_table[-1], and whose tail-pointer is set to NOMOVE. X * The order is purely heuristic, and corresponds roughly to X * the values associated by static_eval with different squares. X */ X X static int m_table[65] = X { X 0, 7, 6, 5, 4, 24, 16, 8, 56, 15, X 14, 13, 12, 25, 17, 49, 48, 23, 22, 21, X 20, 26, 42, 41, 40, 31, 30, 29, 28, 35, X 34, 33, 32, 39, 38, 37, 36, 10, 43, 51, X 59, 47, 46, 45, 44, 27, 19, 50, 58, 55, X 54, 53, 52, 1, 11, -1, 57, 63, 62, 61, X 60, 18, 3, 9, 2 X } ; X X static int *move_table = &(m_table[1]) ; X X while ((mv = move_table[mv]) != NOMOVE) X if (legal(mv,player,pos)) X { X if (nextpos) domove(pos,mv,nextpos,player) ; X X/* Next position generated only if pointer is non-zero */ X return(mv) ; X } X return(NOMOVE) ; /* No more legal moves */ X} X X Xdomove(pos, mv, nextpos, player) XBOARD *pos, *nextpos ; Xint mv, player ; X{ X register int i ; X if (pos != nextpos) FOR_BOARD(i) nextpos->square[i] = pos->square[i] ; X X s_move = mv ; X s_row = mv >> 3 ; X s_col = mv & 7 ; X s_player = player ; X s_opponent = -player ; X s_flip = TRUE ; X s_pos = nextpos ; X X nextpos->square[s_move] = player ; X X (void) sandwich(-9) ; X (void) sandwich(-8) ; X (void) sandwich(-7) ; X (void) sandwich(-1) ; X (void) sandwich(1) ; X (void) sandwich(7) ; X (void) sandwich(8) ; X (void) sandwich(9) ; X X nextpos->moves_left = (pos->moves_left) - 1 ; X} X X Xlegal(mv, player, pos) XBOARD *pos ; Xint mv, player ; X{ X if (pos->square[mv]) return(FALSE) ; /* Already occupied */ X X s_move = mv ; X s_row = mv >> 3 ; X s_col = mv & 7 ; X s_player = player ; X s_opponent = -player ; X s_flip = FALSE ; X s_pos = pos ; X X return(sandwich(-9) || sandwich(-8) || sandwich(-7) || sandwich(-1) || X sandwich(1) || sandwich(7) || sandwich(8) || sandwich(9)) ; X} X X X/* Test whether the square move sandwiches a line X * of enemy pieces in the direction [row_inc, col_inc]; X * If (s_flip) then update position by capturing such pieces X */ X Xsandwich(increment) Xregister int increment ; X{ X register int square,offset ; X int row_offset,col_offset,piece,piece_count ; X X if (s_pos->square[s_move+increment] != s_opponent) return(FALSE) ; X X/* Quick test to catch most failures - X * note that the tested square may not even X * be on the board, but the condition is a X * sufficient one for failure. X */ X X row_offset = (increment < -1 ? s_row : /* inc -1: -9, -8, -7 */ X increment > 1 ? 7-s_row : 8) ; /* inc 1: 7, 8, 9 */ X col_offset = (increment & 4 ? s_col : /* inc -1: -9, -1, 7 */ X increment & 1 ? 7-s_col : 8) ; /* inc 1: -7, 1, 9 */ X X offset = (row_offset > col_offset ? col_offset : row_offset) ; X X/* offset = shortest distance to an edge in the direction of search */ X X if (2 > offset) return(FALSE) ; X X piece_count = 1 ; X square = s_move+increment ; X X while (--offset) X { X if (!(piece = s_pos->square[square += increment])) X return(FALSE) ; /* If empty square, give up */ X X if (piece == s_player) break ; X else piece_count++ ; /* Count opponent's pieces encountered */ X } X X if (!offset) return(FALSE) ; X X if (s_flip) X while (piece_count--) X s_pos->square[square -= increment] = s_player ; X X return (TRUE) ; X} X X X/* Alpha-beta pruning algorithm. X * X * If sign = 1, then try to MAXIMISE score, else MINIMISE. X * Parent node wants to MINIMISE/MAXIMISE, so as soon as X * we exceed parent_opt, give up and return INFINITY. X * Return best move in parent_best. X * X * Externals used: X * static_eval(position) X * ISMOVE(position,player) [does player have a legal move?] X * game_ended(pos) X * typedef BOARD X * aspiration(player,depth,position) X * [return the aspiration limit for next level of search] X * nextmove(position,mv,nextposition,player) X * [give the next legal move for player in position; X * put the resulting position in nextposition] X */ X Xalfabeta(pos, depth, parent_opt, sign, parent_best) XBOARD *pos ; Xint depth, parent_opt, sign, *parent_best ; X{ X BOARD nextpos ; X int value,this_opt,this_best = NOMOVE,mv = NOMOVE,asp = 0 ; X X if ((depth==0) || game_ended(pos)) X { X value = static_eval(pos) ; X *parent_best = NOMOVE ; X if ((sign*value) > (sign*parent_opt)) return(sign*INFINITY) ; X else return (value) ; X } X X ab_count++ ; /* Record boards dynamically evaluated */ X this_opt = (sign == 1 ? -INFINITY : INFINITY) ; X X if (!ISMOVE(pos,sign)) /* No legal move */ X { X value = alfabeta(pos,depth,this_opt,-sign,&this_best) ; X goto valfound ; X } X X asp = sign * aspiration(sign,depth) ; X X while ((mv = nextmove(pos,mv,&nextpos,sign)) != NOMOVE) X { X value = alfabeta(&nextpos,depth-1,this_opt,-sign,&this_best) ; X Xvalfound: X X if ((sign*value) >= (sign*parent_opt)) return(sign*INFINITY) ; X X if ((sign*value) > asp) X { X *parent_best = mv ; X return(value) ; X } X X if ((sign*value) > (sign*this_opt)) X { X this_opt = value ; X *parent_best = mv ; X } X X/* If two moves have same evaluation, choose X * uniformly randomly between them (of course, X * where several moves have same value, this X * is biased towards the later ones X */ X X if ((value == this_opt) && (rand() & 020)) *parent_best = mv ; X } X return(this_opt) ; X} X X X/* Aspirations are as follows: X * Aspiration-level Aspiration X * 1 The worse of 0, static value X * 2 The static value X * 3 10% better than the static value X * 4 25% better than the static value X * 5 Any winning move X * 6 The move winning by the greatest margin X * X * It is assumed that the opponent has X * the same level of aspiration as the program. X */ X Xset_aspiration(position) XBOARD *position ; X{ X if (aspire == MAXASPIRE) X { X expected = 0 ; X margin = INFINITY ; X return ; X } X X if (aspire == (MAXASPIRE-1)) X { X expected = 0 ; X margin = INFINITY-64 ; X return ; X } X X expected = static_eval(position) ; X X switch (aspire) X { X case 1 : expected /= 2 ; X margin = -abs(expected) ; X return ; X case 2 : margin = 0 ; X return ; X case 3 : margin = abs(expected/10) ; X return ; X case 4 : margin = abs(expected/4) ; X return ; X } X} X X Xaspiration(player, depth) Xint player,depth ; X{ X if (aspire == MAXASPIRE) return(player*INFINITY) ; X if (depth < 3 && aspire > 1) return(player*(INFINITY-64)) ; X return(expected+player*margin) ; X} X X X#define VCORN 100 /* Corner */ X#define VORTH -30 /* Orthogonally next to corner */ X#define VDIAG -50 /* Diagonally " " " */ X#define VEDGE 20 /* On edge */ X#define VNEXT -7 /* Next to edge */ X#define VNORM 1 /* Elsewhere */ X X/* Square values (only valid when a "vulnerable" piece occupies the X * square in question) X */ X Xstatic_eval(pos) XBOARD *pos ; X{ X static int model[64] = { X VCORN, VORTH, VEDGE, VEDGE, VEDGE, VEDGE, VORTH, VCORN, X VORTH, VDIAG, VNEXT, VNEXT, VNEXT, VNEXT, VDIAG, VORTH, X VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE, X VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE, X VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE, X VEDGE, VNEXT, VNORM, VNORM, VNORM, VNORM, VNEXT, VEDGE, X VORTH, VDIAG, VNEXT, VNEXT, VNEXT, VNEXT, VDIAG, VORTH, X VCORN, VORTH, VEDGE, VEDGE, VEDGE, VEDGE, VORTH, VCORN X } ; X X register int i ; X register int value = 0 ; X int scores[64] ; X X se_count++ ; /* Record number of static evaluations */ X if (game_ended(pos)) X { X FOR_BOARD(i) value += pos->square[i] ; X return (value > 0 ? INFINITY+value-64 : X value < 0 ? -INFINITY+value+64 : 0) ; X } X X FOR_BOARD(i) scores[i] = 0 ; X X find_invulnerable(pos,scores) ; X X/* Scores now contains VINVUL (or -VINVUL) X * for each invulnerable piece, and 0 elsewhere; X * now fill in other evaluations (special cases: X * next to corner [bad!], on edge[good!], next to X * edge[poor], anywhere else[boring]) X */ X X FOR_BOARD(i) value += (scores[i] ? scores[i] : model[i]*pos->square[i]) ; X return (value) ; X} X X Xgame_ended(pos) XBOARD *pos ; X{ X if (!(pos->moves_left)) return(TRUE) ; X return((!ISMOVE(pos,WHITE)) && !(ISMOVE(pos,BLACK))) ; X} X X X/* This function finds invulnerable pieces, and scores them X * appropriately; it does not find ALL invulnerable pieces - X * in fact, only concave blocks including a corner - but X * nevertheless should provide a good approximation. X */ X Xfind_invulnerable(board, scores) XBOARD *board ; Xint scores[64] ; X{ X int hwm,corner,value,i,j ; X X if ((corner = board->square[0]) != 0) X { X value = corner*VINVUL ; X hwm = 7 ; X X for (i = 0; i < 56; i += 8) X { X if (board->square[i] != corner) break ; X scores[i] = value ; X for (j = 1; j < hwm; j++) X { X if (board->square[i+j] != corner) X { X hwm = j ; X break ; X } X scores[i+j] = value ; X } X } X scores[0] = corner*VCORN ; X } X X if ((corner = board->square[7]) != 0) X { X value = corner*VINVUL ; X hwm = 0 ; X X for (i = 0; i < 56; i+= 8) X { X if (board->square[i+7] != corner) break ; X scores[i+7] = value ; X for (j = 6; j > hwm; j--) X { X if (board->square[i+j] != corner) X { X hwm = j ; X break ; X } X scores[i+j] = value ; X } X } X scores[7] = corner*VCORN; X } X X if ((corner = board->square[56]) != 0) X { X value = corner*VINVUL ; X hwm = 7 ; X X for (i = 56; i > 0; i -= 8) X { X if (board->square[i] != corner) break ; X scores[i] = value ; X for (j = 1; j < hwm; j++) X { X if (board->square[i+j] != corner) X { X hwm = j ; X break ; X } X scores[i+j] = value ; X } X } X scores[56] = corner*VCORN ; X } X X if ((corner=board->square[63]) != 0) X { X value = corner*VINVUL ; X hwm = 0 ; X X for (i = 56; i > 0; i -= 8) X { X if (board->square[i+7] != corner) break ; X scores[i+7] = value ; X for (j = 6; j > hwm; j--) X { X if (board->square[i+j] != corner) X { X hwm = j ; X break ; X } X scores[i+j] = value ; X } X } X scores[63] = corner*VCORN ; X } X} END_OF_FILE if test 12624 -ne `wc -c <'makemove.c'`; then echo shar: \"'makemove.c'\" unpacked with wrong size! fi # end of 'makemove.c' if test -f 'patches03' -a "${1}" != "-c" ; then echo shar: Will not clobber existing file \"'patches03'\" else echo shar: Extracting \"'patches03'\" \(830 characters\) sed "s/^X//" >'patches03' <<'END_OF_FILE' X X------- patchlevel.h ------- X*** /tmp/da06251 Thu May 3 10:36:29 1990 X--- patchlevel.h Thu May 3 10:32:18 1990 X*************** X*** 19,22 **** X * to me, then an attempt will be made to fix them. X */ X X! #define PATCHLEVEL 2 X--- 19,22 ---- X * to me, then an attempt will be made to fix them. X */ X X! #define PATCHLEVEL 3 X X------- CHANGES ------- X*** /tmp/da06254 Thu May 3 10:36:30 1990 X--- CHANGES Thu May 3 10:34:16 1990 X*************** X*** 86,88 **** X--- 86,93 ---- X It would be nice if (at least on the tty version) the text X "black" was replaced with "X" or "black (X)" and likewise for X white. X+ X+ v1.3 - patchlevel 3. 3rd May 1990. X+ X+ * Release of the current version of makemove.c, because the changes X+ to this file at patch level #1 never made it out. END_OF_FILE if test 830 -ne `wc -c <'patches03'`; then echo shar: \"'patches03'\" unpacked with wrong size! fi # end of 'patches03' fi echo shar: End of shell archive. exit 0