chris@hwcs.UUCP (Chris Miller) (06/04/85)
: Apologies to downstream sites who have already seen this once. export PATH || exec /bin/sh $0 $* : "This is a shar archive; use /bin/sh to extract" : "Extracted files will be owned by you, and will have" : "default permissions" PATH=/bin:/usr/bin echo If this archive is complete, \"End of archive\" will appear at the end echo Extracting Makefile sed 's/^X//' <<'End-of-file' >Makefile XLIB=/usr/games/lib/reversi XDESTDIR= XBIN=/usr/games XMAN=/usr/man/man6 XMODE=u=rwx,g=rx,o= XLIBS="-lcurses -ltermlib" X# other libraries may be needed for non-4.2 installations X Xdefault: reversi .informed X Xreversi: X cd src; \ X make LIB=$(LIB) DESTDIR=$(DESTDIR) BIN=$(BIN) LIBS=$(LIBS) X X.informed: X sh inform X echo >.informed X Xallinstall: install libinstall X Xinstall: X cd src; \ X make LIB=$(LIB) DESTDIR=$(DESTDIR) BIN=$(BIN) MODE=$(MODE) \ X LIBS=$(LIBS) install X @echo Type '"make libinstall"' to install documentation, etc. X Xlibinstall: X cd lib; make LIB=$(LIB) MAN=$(MAN) DESTDIR=$(DESTDIR) install X Xclean: X rm -f src/reversi src/reversi.o X Xshar: reversi.shar X Xreversi.shar: clean Makefile READ.ME inform src/* lib/* X shar Makefile READ.ME inform src lib >reversi.shar End-of-file echo Extracting READ.ME sed 's/^X//' <<'End-of-file' >READ.ME XThis is a revised version of "reversi", using the standard XI/O library and the curses package. This distribution may be freely Xcopied provided that no part of it be used for profit, that credit to Xthe original author be retained, and that any subsequent amendments be Xclearly labelled as such. X XIt runs on 4.2bsd on a VAX 11/750; I have no reason to believe that Xany of the code is VAX-dependent, and I do not recall having changed Xthis version significantly from a version which ran on 4.1bsd and V7. XSome small modifications to I/O and signal-handling may be needed for XSystem III/V and other versions. X XThe building of library path-names uses some of the Reiser kludges in Xthe C pre-processor to concatenate part-strings. X XTo install, extract the distribution in a clean directory, check the Xdefinitions at the head of the top-level Makefile, and type "make". X XPlease mail me to let me know that you have built this; I would like to Xknow who has it. The Makefile will attempt to let me know automatically Xbut the path used probably won't reach me from sites outside the UK. End-of-file echo Extracting inform sed 's/^X//' <<'End-of-file' >inform X: 'Set MAILPATH to a sufficient path to reach <Europe>!mcvax!ukc!hwcs!chris' X XMAILPATH=chris@hwcs.uucp Xset `who am i` X Xmail chris@hwcs.uucp <<% XReversi: $1, `date` X% End-of-file echo Making directory src. mkdir src echo Extracting src/Makefile sed 's/^X//' <<'End-of-file' >src/Makefile XLIBS=-lcurses -ltermlib XLIB=/usr/games/lib/reversi XDESTDIR= XBIN=/usr/games XMODE=u=rwx,g=rx,o= XCFLAGS=-O -DLIB=\"$(DESTDIR)$(LIB) X Xreversi: reversi.o X cc -o reversi reversi.o $(LIBS) X Xinstall: reversi X cp reversi $(DESTDIR)$(BIN) X strip $(DESTDIR)$(BIN)/reversi X chmod $(MODE) $(DESTDIR)$(BIN)/reversi End-of-file echo Extracting src/reversi.c sed 's/^X//' <<'End-of-file' >src/reversi.c X/************************************************ X * * X * REVERSI * X * * X * Copyright: Chris Miller, 1979, 1981, 1984 * X * * X ************************************************/ X X/* The program requires the curses library. */ X X/* Notes on efficiency. X X The best way to speed up the program would probably be to X improve the heuristic ordering of moves in the search. This X may require generating all legal successor positions and doing X a quick and dirty static evaluation on them; coding this would X be a lot of work. X X Most of the program's time is spent in the routines: X sandwich (c.20%) X legal 15% X domove 15% X static_eval 15% X so that any tweaking should be done in these procedures. A certain X amount has already been done which explains the rather hacked code, X especially in sandwich X*/ X X#include <curses.h> X#include <signal.h> X#include <setjmp.h> X#include <sys/types.h> X#include <sys/times.h> X Xlong time(); Xchar *sprintf(); X X#define SKIP ; X X#define FOR_BOARD(i) for (i=0; i<64; i++) X X#define NOMOVE -1 X#define ismove(pos,player) (nextmove(pos,NOMOVE,(POSITION *) NULL, player) != NOMOVE) X X#define INFINITY 10000 X X#define WHITE -1 X#define BLACK 1 X#define FREE 0 X X#define MAXDEPTH 6 X#define INIT_DEPTH 2 Xint depth = INIT_DEPTH; X X#define MAXASPIRE 6 X#define INIT_ASPIRE 3 Xint aspire = INIT_ASPIRE; Xint expected, margin; /* Bounds on aspiration for alphabeta search */ X Xchar input[20]; Xint machine_piece, human_piece; Xint trace_eval = FALSE, remark = TRUE, cretin_flag; Xint last_remark; X#define FIRST_REMARK (INFINITY+1) Xlong ab_count, se_count; /* Calls of alfabeta and static_eval */ X X#define libpath(l,c) l/c" Xchar infofil[] = libpath(LIB,reversi.doc); Xchar rem_file[] = libpath(LIB,reversi.remark); X Xtypedef struct { X char SQUARE[64]; X int MOVES_LEFT; X } POSITION; X Xjmp_buf newgame, undomove, intrundo; X XWINDOW *wboard, *wmoves, *wtrace, *wremarks, *wmessages; X Xmain () X{ X int finish_up (); X int oninterrupt (); X int onquit (); X int exit (); X long initrand; X X signal (SIGQUIT, exit); X X initscr(); X initwindows(); X crmode(); X noecho(); X move(0, 16); X addstr ("R E V E R S I\n\n"); X X while (TRUE) X { X message ("Do you want instructions? "); X *input = getch(); X if (*input == 'y' || *input == 'Y') X { info(); X break; X } X if (*input == 'n' || *input == 'N') break; X bell(); X } X X initrand = time ((long *) NULL); /* Initialise random number generator */ X srand ((int) (initrand & 0377)); X X get_remarks (); X X signal (SIGINT, oninterrupt); X signal (SIGQUIT, onquit); X X while (TRUE) X { setjmp(newgame); X setjmp(undomove); X setjmp(intrundo); X playgame(); X setjmp(intrundo); X setjmp(undomove); X message("Would you like another game? "); X *input = getch(); X if (*input == 'n' || *input == 'N') break; X if (*input != 'y' && *input != 'Y') { X message ("I assume that grunt means yes!\n"); X touchwin(wmoves); X touchwin(wtrace); X touchwin(wremarks); X } X } X X finish_up (); X} X Xfinish_up () X{ X wclear(wtrace); X wrefresh(wtrace); X move(LINES-3, 0); X refresh(); X endwin(); X puts ("Thank you for your company.\nAu revoir ..."); X exit (0); X} X Xoninterrupt () X{ char ch; X int oninterrupt(); X X signal (SIGINT, SIG_IGN); /* Ignore interrupts within interrupts */ X X wclear(wtrace); X waddstr(wtrace, " *** Interrupt ***\n"); X waddstr(wtrace, "x to exit program c to continue execution\n"); X waddstr(wtrace, "n to start a new game u to undo last move\n"); X waddstr(wtrace, "i for instructions\n"); X wrefresh(wtrace); X while (TRUE) X { X while (whitesp (ch=getch())) SKIP X switch (ch) X { X case 'x': message ("*** EXIT ***\n"); X finish_up(); X X case 'u': X signal (SIGINT, oninterrupt); X wclear(wmessages); X wclear(wtrace); X redraw(); X longjmp(intrundo, 1); X X case 'n': X signal (SIGINT, oninterrupt); X wclear(wmessages); X wclear(wtrace); X redraw(); X longjmp(newgame, 0); X X case 'c': X wclear(wmessages); X waddstr(wmessages, "*** Continue ***"); X wclear(wtrace); X redraw(); X signal(SIGINT, oninterrupt); X return; X X case 'i': info(); X wclear(wtrace); X redraw(); X signal(SIGINT, oninterrupt); X return; X } X } X} X Xonquit () X{ message ("*** QUIT ***"); X finish_up (); X} X Xplaygame () X{ X POSITION board, saveboard, oldboard; X register int i; X int colour = BLACK, white = 0, black = 0; X X wclear(stdscr); X wrefresh(stdscr); X wclear(wboard); X wclear(wtrace); X wclear(wremarks); X wclear(wmessages); X wclear(wmoves); X touchwin(wboard); X touchwin(wtrace); X touchwin(wremarks); X touchwin(wmessages); X touchwin(wmoves); X while (TRUE) X { X message ("What colour would you like? "); X *input = getch(); X if (*input == 'w' || *input == 'W') X { human_piece = WHITE; X machine_piece = BLACK; X break; X } X if (*input == 'b' || *input == 'B') X { human_piece = BLACK; X machine_piece = WHITE; X break; X } X bell(); X } X X FOR_BOARD (i) board.SQUARE[i] = FREE; X board.MOVES_LEFT = 64; X saveboard = board; X cretin_flag = FALSE; X last_remark = FIRST_REMARK; /* Force a remark first time */ X X clear(); X display (&board); X X while (!game_over(&board)) X { if (colour==human_piece) X { oldboard = saveboard; X saveboard = board; X if (setjmp(undomove)!=0) X { board = oldboard; X colour = human_piece; X display (&board); X continue; X } X if (setjmp(intrundo)!=0) X { board = saveboard; X colour = human_piece; X display (&board); X continue; X } X } X X if (!makemove(&board, &colour)) return; X /* Pass a pointer to colour so that restore X can reset it (this is ugly!) X */ X colour = -colour; X } X X if (machine_piece==colour) X { X display (&board); X } X X FOR_BOARD (i) X switch (board.SQUARE[i]) X { case WHITE: white++; break; X case BLACK: black++; X } X X setjmp(intrundo); X clear(); X refresh(); X touchwin(wboard); X display(&board); X move(13, 0); X printw ("Game over: I have %d pieces, you have %d pieces", X (machine_piece==WHITE? white: black), X (human_piece==BLACK? black: white)); X if (white==black) X { addstr ("\n\n\t*** Game drawn! ***\n"); X refresh(); X return; X } X if ((machine_piece==WHITE && white>black) || X (machine_piece==BLACK && black>white)) X { addstr("\n\n\t*** I win ***\n"); X if (cretin_flag) addstr ("\n\t*** CRETIN! ***\n"); X } X else addstr("\n\n\t*** You win ***\n"); X refresh(); X} X Xmakemove (board, colour) X POSITION *board; X int *colour; X{ X char letter; int number; X int mv = NOMOVE, value; X int d; X struct tms timesbefore, timesafter; X long realtime, cputime; X X getmove: X if (*colour == human_piece) X { if (!ismove (board, *colour)) X { X wmove(wmoves, 0, 0); X waddstr (wmoves, "** You have no legal move **"); X wclrtoeol(wmoves); X wrefresh(wmoves); X sleep (3); X return (TRUE); X } X X wmove(wmoves, 0, 0); X wprintw(wmoves, "Your move (%s): __", X human_piece==BLACK? "XX": "OO"); X wclrtoeol(wmoves); X waddstr(wmoves, "\b\b"); X wmove(wmoves, 0, 16); X wrefresh(wmoves); X getreply (wmoves, input); X decode (input, &letter, &number); X if (('h'<letter) || (1>number) || (8<number) || ('a'>letter)) X { if (!do_command(letter, number, board, colour)) X return (FALSE); X } X else if (okmove(board, letter, number, *colour)) X return(TRUE); X goto getmove; X } X X if (*colour == machine_piece) X { if ((mv = nextmove (board, NOMOVE, 0, *colour)) == NOMOVE) X { X wmove(wmoves, 1, 0); X wprintw (wmoves, "%s","** I have no legal move **"); X wclrtoeol(wmoves); X wrefresh(wmoves); X if (remark) { X wmove (wremarks, 0, 0); X waddstr (wremarks, "I'm not dead, just resting"); X wclrtoeol(wremarks); X wrefresh(wremarks); X } X display (board); X return (TRUE); X } X X ab_count = se_count = 0; X if (trace_eval) X { times(×before); X realtime = time((long *) NULL); X } X if ((!remark) && (!trace_eval) && X (nextmove(board, mv, 0, colour) == NOMOVE)) X { /* Only one legal move - make it */ X /* (provided we don't need dynamic value) */ X value = static_eval (board); X goto movefound; X } X X if ((board->MOVES_LEFT <= 10) && X (board->MOVES_LEFT <= 3*depth)) X /* Exhaustive for last 3*depth ply (at most 10!) */ X d = board->MOVES_LEFT; X else d = depth; X X set_aspiration (board); X value = alfabeta (board, d, (*colour)*INFINITY, *colour, &mv); X X movefound: X wmove(wmoves, 1, 0); X wprintw(wmoves, "My move (%s): ", X machine_piece==BLACK? "XX": "OO"); X wclrtoeol(wmoves); X wmove(wmoves, 1, 16); X wprintw(wmoves, "%c%c", (mv&7)+'a', (mv>>3)+'1'); X wrefresh(wmoves); X domove (board, mv, board, *colour); X if (remark) make_remark(value); X if (trace_eval) X { X wmove (wtrace, 0, 0); X wprintw(wtrace, "Depth: %d; Level: %d", X depth, aspire); X wclrtoeol(wtrace); X wprintw(wtrace, "\nValue: %5d", value); X wclrtoeol(wtrace); X wprintw(wtrace, X "\nBoards evaluated - static: %5D, dynamic: %5D", X se_count, ab_count); X wclrtoeol(wtrace); X X times (×after); X cputime = timesafter.tms_utime + timesafter.tms_stime X - timesbefore.tms_utime - timesbefore.tms_stime; X X wprintw(wtrace, "\nTime - elapsed: %4D, cpu: %4D.%1D", X (time(0) - realtime), X cputime/60, /* Seconds */ X (cputime/6) % 10); /* Tenths */ X wclrtoeol(wtrace); X wprintw(wtrace, "\nBoards per cpu second: %.2f", X (double) (se_count+ab_count) * 60.0 X / (double) cputime); X wclrtoeol(wtrace); X wrefresh(wtrace); X } X display (board); X return (TRUE); X } X return(FALSE); X} X Xokmove (board, letter, number, colour) X POSITION *board; X char letter; X int number; X int colour; X{ X int mv; X X if ((number<1) || (number>8) || (letter<'a') || (letter>'h')) X { X message ("Column should be in range a-h, row in range 1-8"); X bell(); X return (FALSE); X } X X mv = letter - 'a' + 8*(--number); X if (!legal(mv, colour, board)) X { X message ("Illegal move"); X bell(); X return (FALSE); X } X X domove (board, mv, board, colour); X display (board); X return (TRUE); X} X Xmake_remark (value) X int value; X{ X value *= machine_piece; X wmove(wremarks, 0, 0); X wclrtobot(wremarks); X if (value < -INFINITY+64) X { X waddstr (wremarks, "You should win"); X last_remark = FIRST_REMARK; /* Always remark next time */ X if (aspire==MAXASPIRE) X wprintw(wremarks, X " (by at most %d)", -value-INFINITY+64); X } X else if (value < -1000) X { if (last_remark != -INFINITY) X { cretin_flag = TRUE; X last_remark = -INFINITY; X waddstr(wremarks, X "\nOnly a cretin could lose from your position"); X } X } X else if (value <= 1000) printr(value); X else if (value < INFINITY-63) X { if (last_remark!=INFINITY) X { waddstr(wremarks, X "Resign, you dolt! Further resistance is futile"); X last_remark = INFINITY; X } X } X else X { waddstr (wremarks, "I shall win"); X last_remark = FIRST_REMARK; X if (aspire==MAXASPIRE) X wprintw (wremarks, X " (by at least %d)", value-INFINITY+64); X } X wrefresh(wremarks); X} X X#define MAX_REMARKS 50 XFILE *rem_fp; Xlong good_remarks[MAX_REMARKS], bad_remarks[MAX_REMARKS]; Xint max_good = 0, max_bad = 0; X Xget_remarks () X/* Set up file pointers to remarks; the format of the remarks file X is X X Remarks to the effect that machine is winning, in X increasing order of strength, one per line. X A line beginning with the character "@". X Remarks to the effect that human is winning, in increasing X order of strength, one per line. X*/ X{ long fp = 0l; X int ch; X X rem_fp = fopen (rem_file, "r"); X if (rem_fp==NULL) X { message ("Remarks not available"); X bell(); X sleep(1); X return; X } X X while ((ch=getc(rem_fp))!=EOF) X { if (ch=='@') break; X good_remarks[max_good++]=fp++; X if (max_good==MAX_REMARKS) X { message ("Too many remarks"); X bell(); X sleep(1); X return; X } X while (ch!='\n') X { ch = getc(rem_fp); X fp++; X } X } X max_good--; X X fp++; X while (ch!='\n') X { ch = getc(rem_fp); X fp++; X } X X while ((ch=getc(rem_fp))!=EOF) X { bad_remarks[max_bad++]=fp++; X if (max_bad==MAX_REMARKS) X { message("Too many remarks"); X bell(); X sleep(1); X return; X } X while (ch!='\n') X { ch=fgetc(rem_fp); X fp++; X } X } X max_bad--; X} X Xprintr (n) X int n; X/* Locate file-pointer and print appropriate line. The strange X computation is to cluster remarks towards low evaluations, X since the evaluation function changes more rapidly with small X changes in position as the evaluation gets larger X*/ X X X{ int ch; X int index; X float findex; X int sign_n = (n<0? -1: 1); X char string[100], *stringp = string; X X if (rem_fp==NULL) return; X X findex = (float) abs(n)/1000.0; X index = findex * (2-findex) * (n<0? max_bad: max_good) + 0.5; X if (index*sign_n == last_remark) X /* Don't make the same remark twice in a row */ X { X return; X } X X last_remark = index*sign_n; X X fseek (rem_fp, n<0? bad_remarks[index]: good_remarks[index], 0); X while ((ch=getc(rem_fp))!=EOF) X { if (ch=='\n') break; X *stringp++ = ch; X } X *stringp = '\0'; X waddstr(wremarks, string); X} X Xdo_command (letter, number, board, colour) X POSITION *board; X char letter; X int number; X int *colour; X{ X int mv = NOMOVE; X X switch (letter) X { case 'q': return(FALSE); X X case 'h': X case '?': print_help (); X wclear(wmessages); X redraw(); X break; X X case 'p': redraw(); X break; X X case 't': trace_eval = !trace_eval; X message("Tracing now %s", X trace_eval? "on": "off"); X if (!trace_eval) { X wclear(wtrace); X wrefresh(wtrace); X } X break; X X case 'r': if (rem_fp==NULL) X { X message("Remark file not available"); X break; X } X remark = !remark; X if (remark) { X message ("OK, I'll make remarks"); X } else { X message ("OK, I'll shut up"); X wclear(wremarks); X wrefresh(wremarks); X } X break; X X case 's': if (number==0) { X message ("Current search depth: %d", X depth); X } else if (number<0 || number>MAXDEPTH) { X message ("Search depth must be in range 1-%d", X MAXDEPTH); X bell(); X } X else { depth = number; X message ("Search depth set to %d", X number); X } X break; X X case 'l': if (number==0) X message ("Current aspiration level: %d", X aspire); X else if (number<0 || number>MAXASPIRE) { X message ("Aspiration level must be 1-%d", X MAXASPIRE); X bell(); X } X else { aspire = number; X message ("Aspiration level set to %d", X number); X } X break; X X case 'e': message("Static evaluation: %d", X static_eval (board)); X break; X X case 'v': if (number==0) number=depth; X if (number<1 || number>9) { X message("Invalid depth"); X bell(); X } X else { set_aspiration (board); X message("Dynamic evaluation: %d", X alfabeta (board, number, X human_piece*INFINITY, X human_piece, &mv)); X } X break; X X case 'm': if (number==0) number=depth; X if (number<1 || number>9) X { message ("Invalid depth"); X bell(); X break; X } X set_aspiration (board); X alfabeta (board, number, human_piece*INFINITY, X human_piece, &mv); X message("I would move at %c%c", X 'a'+(mv&7), '1'+(mv>>3)); X break; X X case '>': save(board, colour); X break; X X case '<': restore(board, colour); X break; X X case 'u': longjmp(undomove, 1); X break; X X default: message ("Type ? for help"); X bell(); X break; X } X return (TRUE); X} X Xalfabeta (pos, depth, parent_opt, sign, parent_best) 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_over (pos) X typedef POSITION X aspiration (player, depth, position) [return the aspiration limit for X 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*/ X X POSITION *pos; X int depth, parent_opt, sign, *parent_best; X X{ X POSITION nextpos; X int value, this_opt, this_best = NOMOVE, mv = NOMOVE, asp = 0; X X if ((depth==0) || game_over(pos)) X { value = static_eval (pos); X *parent_best = NOMOVE; X if ((sign*value) > (sign*parent_opt)) X 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 { 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 { value = alfabeta (&nextpos, depth-1, this_opt, -sign, &this_best); X valfound: X if ((sign*value) >= (sign*parent_opt)) X return (sign*INFINITY); X X if ((sign*value) > asp) X { *parent_best = mv; X return (value); X } X X if ((sign*value) > (sign*this_opt)) 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)) X *parent_best = mv; X } X X return (this_opt); X} X Xset_aspiration (position) X POSITION *position; 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 X{ X if (aspire==MAXASPIRE) X { expected = 0; X margin = INFINITY; X return; X } X X if (aspire==(MAXASPIRE-1)) X { expected = 0; X margin = INFINITY-64; X return; X } X X expected = static_eval (position); X X switch (aspire) X { case 1: expected /= 2; X margin = -abs(expected); X return; X case 2: margin = 0; return; X case 3: margin = abs (expected/10); return; X case 4: margin = abs (expected/4); return; X } X} X Xaspiration (player, depth) X int 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 Xstatic_eval (pos) X POSITION *pos; X X/* Square values (only valid when a "vulnerable" piece occupies the X square in question) X*/ X X#define VCORN 100 X /* Corner */ X#define VORTH -30 X /* Orthogonally next to corner */ X#define VDIAG -50 X /* Diagonally " " " */ X#define VEDGE 20 X /* On edge */ X#define VNEXT -7 X /* Next to edge */ X#define VNORM 1 X /* Elsewhere */ X 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_over(pos)) 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 /* 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) X value += (scores[i]? scores[i]: model[i]*pos->SQUARE[i]); X X return (value); X} X Xgame_over (pos) X POSITION *pos; X{ X if (!(pos->MOVES_LEFT)) return (TRUE); X X return ((!ismove(pos, WHITE)) && !(ismove(pos, BLACK))); X} X X#define VINVUL 50 X Xfind_invulnerable (board, scores) X POSITION *board; X int scores[64]; 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 X{ X int hwm, corner, value, i, j; X X if ((corner=board->SQUARE[0]) != 0) X { value = corner*VINVUL; X hwm = 7; X X for (i=0; i<56; i+= 8) X { if (board->SQUARE[i] != corner) break; X scores[i] = value; X for (j=1; j<hwm; j++) X { if (board->SQUARE[i+j] != corner) X { hwm = j; X break; X } X X scores[i+j] = value; X } X } X scores[0] = corner*VCORN; X } X X if ((corner=board->SQUARE[7]) != 0) X { value = corner*VINVUL; X hwm = 0; X X for (i=0; i<56; i+= 8) X { if (board->SQUARE[i+7] != corner) break; X scores[i+7] = value; X for (j=6; j>hwm; j--) X { if (board->SQUARE[i+j] != corner) X { hwm = j; X break; X } X X scores[i+j] = value; X } X } X scores[7] = corner*VCORN; X } X X if ((corner=board->SQUARE[56]) != 0) X { value = corner*VINVUL; X hwm = 7; X X for (i=56; i>0; i-= 8) X { if (board->SQUARE[i] != corner) break; X scores[i] = value; X for (j=1; j<hwm; j++) X { if (board->SQUARE[i+j] != corner) X { hwm = j; X break; X } X X scores[i+j] = value; X } X } X scores[56] = corner*VCORN; X } X X if ((corner=board->SQUARE[63]) != 0) X { value = corner*VINVUL; X hwm = 0; X X for (i=56; i>0; i-= 8) X { if (board->SQUARE[i+7] != corner) break; X scores[i+7] = value; X for (j=6; j>hwm; j--) X { if (board->SQUARE[i+j] != corner) X { hwm = j; X break; X } X X scores[i+j] = value; X } X } X scores[63] = corner*VCORN; X } X} X Xwhitesp (ch) Xchar ch; X{ X return (ch==' ' || ch=='\n' || ch=='\t'); X} X X Xdecode (s, ch, num) Xchar *s,*ch; Xint *num; X{ X if (*s=='\0') return; X *ch = ('A'<= *s && *s <='Z'? *s-'A'+'a': *s); X s++; X *num = 0; X sscanf(s, " %d", num); X} X X/*************************************/ X Xnextmove (pos, mv, nextpos, player) X POSITION *pos, *nextpos; X int mv, player; 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 { 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 { if (nextpos) domove (pos, mv, nextpos, player); X /* Next position generated only if pointer is X non-zero */ X return (mv); X } X return (NOMOVE); /* No more legal moves */ X} X X/**************************************/ X X/* Globals for passing arguments to "sandwich"; X this is to save time putting arguments on and off the X stack in a very heavily used piece of code X*/ X Xint s_move, s_row, s_col, s_player, s_opponent, s_flip; XPOSITION *s_pos; X Xdomove (pos, mv, nextpos, player) X POSITION *pos, *nextpos; X int 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 sandwich (/* mv, */ -9 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ -8 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ -7 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ -1 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ 1 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ 7 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ 8 /* , player, nextpos, TRUE */ ); X sandwich (/* mv, */ 9 /* , player, nextpos, TRUE */ ); X X nextpos->MOVES_LEFT = (pos->MOVES_LEFT) - 1; X} X X/*************************************/ X Xlegal (mv, player, pos) X POSITION *pos; X int mv, player; X{ X if (pos->SQUARE[mv]) return(FALSE); X /* Already occupied */ X X if (pos->MOVES_LEFT > 60) /* Initial 4 moves */ X return ((mv==27) || (mv==28) || (mv==35) || (mv==36)); X /* Central four squares */ 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 ( /* mv, */ -9 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ -8 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ -7 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ -1 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ 1 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ 7 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ 8 /* , player, pos, FALSE */ ) || X sandwich ( /* mv, */ 9 /* , player, pos, FALSE */ )); X} X X/**********************************/ X Xsandwich ( /* s_move, */ increment /* , s_player, s_pos, s_flip */ ) X register int increment; X /* int s_player, s_move, s_flip; */ X /* POSITION *s_pos; */ 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 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 row_offset = (increment<-1 ? s_row: /* inc -1: -9, -8, -7 */ X increment> 1 ? 7-s_row: /* inc 1: 7, 8, 9 */ X 8); X col_offset = (increment & 4? s_col: /* inc -1: -9, -1, 7 */ X increment & 1? 7-s_col: /* inc 1: -7, 1, 9 */ X 8); X X offset = (row_offset>col_offset? col_offset: row_offset); X /* offset = shortest distance to an edge in the X direction of search */ X if (2 > offset) return (FALSE); X X piece_count = 1; X square = s_move+increment; X X while (--offset) 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 X 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 Xprint_help () X{ X clear(); X move(3, 0); X addstr (" <column><row> to make a move\n"); X addstr (" ? or h to obtain this message\n"); X addstr (" q to terminate this game at once\n"); X addstr (" p to re-display the current position\n"); X addstr (" t to switch on or off tracing of evaluation\n"); X addstr (" r to switch remarks on or off\n"); X addstr (" s<n> to set depth of search to n\n"); X addstr (" l<n> to set the level of aspiration to n\n"); X addstr (" e to get the static evaluation of the position\n"); X addstr (" v<n> to get the dynamic evaluation (depth n)\n"); X addstr (" m<n> to get a suggested next move (depth n)\n"); X addstr (" u to undo your last move\n"); X addstr (" > to save the position in a file\n"); X addstr (" < to restore from a file\n"); X refresh(); X message("Type SPACE to continue ... "); X *input = getch(); X} X Xinfo() X{ X char cmd[256]; X X clear(); X refresh(); X if (access(infofil, 0444) == -1) { X message("Unfortunately, there is no information available\n"); X bell(); X sleep(1); X return; X } X sprintf(cmd, "/usr/ucb/more %s", infofil); X system(cmd); X message("Hit new line when ready..."); X *input = getch(); X clear(); X refresh(); X crmode(); X noecho(); X X return; X} X Xdisplay (board) X POSITION *board; X{ X register int i, j; X char *piece; X X wclear(wmessages); X wrefresh(wmessages); X wmove(wboard, 0, 0); X X waddstr(wboard, " a b c d e f g h\n"); X for (i=0; i<8; i++) X { wprintw(wboard, "%1d|", i+1); X for (j=0; j<8; j++) X { switch (board->SQUARE[8*i+j]) X { case WHITE: piece = "OO"; break; X case BLACK: piece = "XX"; break; X case FREE : piece = " "; break; X } X wprintw(wboard, "%s|", piece); X } X wclrtoeol(wboard); X waddch(wboard, '\n'); X } X wrefresh(wboard); X} X Xgetreply (w, str) X WINDOW *w; X char *str; X{ int ch; X char *s = str; X X while ((ch=getch()) == ' ' || ch == '\t') SKIP X while (ch!='\n' && ch!='\r') X { if (ch==EOF) /* End-of-file or read error */ X { if ((ch=getch())==EOF) /* Re-try; if failure X assume EOF */ X { message ("Unexpected end of input"); X bell(); X finish_up(); X } X else { X continue; X } X } X if (ch==0177) { X if (s>str) { X s--; X waddstr(w, "\b \b"); X wrefresh(w); X } X ch = getch(); X continue; X } X *s++ = ch; X waddch(w, ch); X wrefresh(w); X ch = getch(); X } X X *s = 0; X} X X#define MAGIC 0501 /* Header word */ X Xsave(board, colour) X POSITION *board; X int *colour; X{ FILE *fp; X char fname[128]; X X message("Save into: "); X getreply(wmessages, fname); X if ((fp=fopen(fname,"w"))==NULL) X { message("Can't open %s\n", fname); X bell(); X return; X } X putw(MAGIC, fp); X fwrite(board, sizeof(POSITION), 1, fp); X putw(*colour, fp); X fclose(fp); X} X Xrestore(board, colour) X POSITION *board; X int *colour; X{ FILE *fp; X char fname[128]; X X message("Restore from: "); X getreply(wmessages, fname); X if ((fp=fopen(fname,"r"))==NULL) X { message("Can't open %s\n", fname); X bell(); X return; X } X if (getw(fp)!=MAGIC) X { message("Not a saved position\n"); X bell(); X return; X } X fread(board, sizeof(POSITION), 1, fp); X *colour = getw(fp); X fclose(fp); X} X Xbell() X{ X putchar('\07'); X fflush(stdout); X} X Xmessage(f, a1, a2, a3, a4, a5) X{ X wclear(wmessages); X wprintw(wmessages, f, a1, a2, a3, a4, a5); X wrefresh(wmessages); X} X Xinitwindows() X{ X wboard = newwin(9, 30, 2, (COLS-30)/2); X wmoves = newwin(2, 30, 12, (COLS-30)/2); X wmessages = newwin(1, COLS, 0, 0); X wtrace = newwin(5, COLS-8, 18, 8); X wremarks = newwin(2, COLS, 16, 0); X} X Xredraw() X{ X clear(); X refresh(); X touchwin(wboard); X touchwin(wmessages); X touchwin(wtrace); X touchwin(wremarks); X touchwin(wmoves); X wrefresh(wboard); X wrefresh(wmessages); X wrefresh(wtrace); X wrefresh(wremarks); X wrefresh(wmoves); X} End-of-file echo Making directory lib. mkdir lib echo Extracting lib/Makefile sed 's/^X//' <<'End-of-file' >lib/Makefile XLIB=/usr/games/lib/reversi XMAN=/usr/man/man6 XDESTDIR= X Xreversi.doc: reversi.me X tbl reversi.me | nroff -me | col > reversi.doc X Xinstall: reversi.doc reversi.remark X cp reversi.doc reversi.remark $(DESTDIR)$(LIB) X cp reversi.6 $(DESTDIR)$(MAN) End-of-file echo Extracting lib/reversi.6 sed 's/^X//' <<'End-of-file' >lib/reversi.6 X.TH REVERSI 6 17/1/79 X.SH NAME Xreversi \- the game of Reversi (or Othello) X.SH SYNOPSIS X/usr/games/reversi X.SH DESCRIPTION X.I Reversi Xis a computerised version of the game sold in Xshops as Othello. It is played on a 8x8 board with Xpieces which are white on one side and black on the Xother (or X and O in our case). The games starts with Xthe players placing Xtwo pieces of each colour in the centre of the board. XSubsequent moves are made by adding a piece to the board so that Xit brackets one or more of your opponents pieces between Xitself and another of your pieces. All such bracketed Xpieces are then reversed and become your pieces. The Xobject of the game is to have more pieces than the computer Xwhen the board is full. X.PP XMoves are specified by board positions. Other commands Xare "h" for help, "p" to print the board, Xand "q" to quit; there are various parameters governing Xthe standard of play - the info provided when you run Xthe program gives full details X.SH AUTHOR XC.D.F. Miller End-of-file echo Extracting lib/reversi.doc sed 's/^X//' <<'End-of-file' >lib/reversi.doc X X X X X X X X _T_h_e _G_a_m_e _o_f _R_e_v_e_r_s_i X X X _R_e_v_e_r_s_i is an old english game, recently "invented" in XJapan under the name Othello. It is played on an 8x8 board, Xwith pieces which are black on one side and white on the Xother. Except for the initial 4 moves, which must all be Xmade in the centre 4 squares, a legal move consists of plac- Xing a piece of one's own colour on the board so as to X"sandwich" a row (orthogonal or diagonal) of pieces of the Xopposite colour between the piece just placed and another Xpiece of the same colour. All pieces so sandwiched are Xflipped over to reveal the colour on the other side. X X The object is to have more pieces than the opponent at Xthe end of the game (i.e. when the board is full or neither Xside has a legal move). If you have no legal move, you sim- Xply miss a turn. Black moves first. X X To specify a move, type its coordinates in the form X<letter><digit>. X X There are also a number of other commands. Some of Xthese cause evaluations to be printed out - in this case a Xnegative value means white is winning, a positive one means Xblack is winning. Some commands take an optional integer Xargument to specify a depth - the default is the current Xdepth of search. X X Commands: X ____________________________________________________ X ? or h Print help X q Quit the current game. X p Print the position. X t Switch tracing on or off - with tracing X on the program's estimate of the value X of the position is printed each time it X moves. X e Print the static evaluation of the posi- X tion; this is a very unsophisticated X type of evaluation. X v<n> Print the dynamic evaluation of the po- X sition, using a search depth of n (the X default is the current depth). This in- X volves the program searching possible X lines of play up to n moves ahead in X order to evaluate the position. X X X X X X X X X X X X X X X X X X X X m<n> Suggest a move; the program uses its own X search strategy with depth n (default X the current depth) to find the move X which it would play in its opponent's X position. X s<n> Set the current search depth to n moves. X If n is missing, the current depth is X printed. At present, n may not be X greater than 6; it is set initially to X 2. A beginner should set n to 1 at X first. X l<n> Set the current level of aspiration to X n; the higher the value of n, the higher X will be the program's standards for ac- X cepting a move as playable. If n is X missing, the current level is printed. X At present, n may not be greater than 6; X it is initially 3. A beginner should X play with n set to 1, and an expert X should not normally set n greater than 5 X (at level 6, the program not only tries X to win, but tries to win by the greatest X possible margin). X r Switch remarks on or off; with remarks X on, the program will make comments (usu- X ally rude) about the state of play. X u Undo the last move you made - this is, X of course, cheating unless you genuinely X mistyped it! X > Save the position on a file - you are X prompted for the filename. X < Restore a position from file. X X X All settings (tracing, search depth, level, remarks) Xare preserved from one game to the next. X X X X X X X X X X X X X X X X X X X X X X X End-of-file echo Extracting lib/reversi.me sed 's/^X//' <<'End-of-file' >lib/reversi.me X.pl 0 X.(b C X.i XThe Game of Reversi X.r X.)b X.pp X.i Reversi Xis an old english game, recently "invented" in Japan Xunder the name Othello. It is played on an 8x8 board, with Xpieces which are black on one side and white on the other. XExcept Xfor the initial 4 moves, which must all be made in the centre 4 Xsquares, a legal move consists of placing a piece of one's own Xcolour on the board so as to "sandwich" a row (orthogonal or Xdiagonal) of pieces of the opposite colour between the piece just Xplaced and another piece of the same colour. XAll pieces so Xsandwiched are flipped over to reveal the colour on the other Xside. X.pp XThe object is to have more pieces than the opponent at the end Xof the game (i.e. when the board is full or neither side has a Xlegal move). XIf you have no legal move, you simply miss a turn. XBlack moves first. X.pp XTo specify a move, type its coordinates in the form X<letter><digit>. X.pp XThere are also a number of other commands. XSome of these Xcause evaluations to be printed out - in this case a negative Xvalue means white is winning, a positive one means black is winning. XSome commands take an optional integer argument to specify Xa depth \- the default is the current depth of search. X.TS Xcenter; Xl lw(40). XCommands: X_ X? or h Print help Xq T{ XQuit the current game. XT} Xp T{ XPrint the position. XT} Xt T{ XSwitch tracing on or off - with tracing on the Xprogram's estimate of the value of the position Xis printed each time it moves. XT} Xe T{ XPrint the static evaluation of the position; Xthis is a very unsophisticated type of evaluation. XT} Xv<n> T{ XPrint the dynamic evaluation of the position, using Xa search depth of n (the default is the current depth). XThis involves the program searching possible lines of Xplay up to n moves ahead in order to evaluate the Xposition. XT} Xm<n> T{ XSuggest a move; the program uses its own search strategy Xwith depth n (default the current depth) to find the Xmove which it would play in its opponent's position. XT} Xs<n> T{ XSet the current search depth to n moves. XIf n is missing, the current depth is printed. XAt present, n may not be greater than 6; it is set initially to 2. XA beginner should set n to 1 at first. XT} Xl<n> T{ XSet the current level of aspiration to n; the higher the Xvalue of n, the higher will be the program's standards Xfor accepting a move as playable. XIf n is missing, the current level is printed. XAt present, n may not be greater than 6; it is initially 3. XA beginner should play with n set to 1, and an expert should not normally set Xn greater than 5 (at level 6, the program not only tries Xto win, but tries to win by the greatest possible margin). XT} Xr T{ XSwitch remarks on or off; with remarks on, the program Xwill make comments (usually rude) about the state of play. XT} Xu T{ XUndo the last move you made \- this is, of course, cheating Xunless you genuinely mistyped it! XT} X> T{ XSave the position on a file - you are prompted for the filename. XT} X< T{ XRestore a position from file. XT} X.TE X.pp XAll settings (tracing, search depth, level, remarks) are Xpreserved from one game to the next. End-of-file echo Extracting lib/reversi.remark sed 's/^X//' <<'End-of-file' >lib/reversi.remark XPretty level; I'll start trying soon. XNot much in it; superior skill will tell. XMy superior skill is telling. XYou're beginning to lose your grip. XYou're really not so hot after all. XWake up! You're falling by the wayside. XCome on, concentrate! XIf I weren't a computer, I'd be starting to get bored. XThey tell me noughts and crosses is easier. XI'm starting to get bored. XI could beat you with one CPU tied behind my back. XWho on earth programmed YOU? XQuiche-eater! XI seem to be hammering you a little. XAre you impersonating a random-number generator? XPerhaps you should try practising against a desk-calculator. XJust remember, when things are going badly DON'T PANIC. XPerhaps it's about time you started to panic. XYour position leaves almost as much to be desired as does your intelligence. XI'm thrashing you. XOh for some REAL opposition. XWhat a massacre! XMy brilliance is rivalled only by your dullness. XWhy don't you take up knitting instead? XWhy don't you take up computer science instead? XThat's how it goes - I win some, you lose some. XYou've had it. Why not give up now? X@ XJust about dead level. XMaybe you have just the tiniest advantage. XI shall easily sweep aside your slight advantage. XMaybe you're not as daft as you look, after all. XGood! You're beginning to get the hang of it now. XI suppose I ought to start playing properly soon. XAll right, no need to show off. XHave you by some chance played this game before? XHmmm. This game may be harder than I thought. XAre you fifth-generation? XI'm just trying to lull you into a false state of security. XI wish to register a complaint. XI didn't want to be a computer, you know. I wanted to be a LUMBERJACK. XYou're revealing the evidence of a mis-spent youth. XGive me a chance! I'm only a machine. XI suppose you think it's pretty smart, beating machines at silly games. XDo you go round beating up old ladies as well? XSmartass! You may be ahead, but it isn't over yet. XWell, it isn't QUITE over yet. XYou seem to know this game pretty well. XBully! Just wait till I get my big brother Kray onto you. XI think I'm getting a slight headache. XWhy don't you pick on someone your own size. XMegarats! You're winning heavily. End-of-file echo End of archive exit 0 -- Chris Miller, Heriot-Watt University, Edinburgh ...!ukc!{cstvax,kcl-cs}!hwcs!chris chris@hwcs.uucp