mlm@cad.cs.cmu.edu.ARPA (Michael Mauldin) (10/14/85)
#!/bin/sh # # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # @ Here is your new automatic Robots player, Rob-O-Matic! @ # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ # # To unpack this program, run this message through 'unshar', or # remove the mail headers and pipe the body through the "sh" command. # Then "make" should build the program for you. # # A tradition of fine game playing programs, # from the People who brought you Rog-O-Matic. # # Michael L. Mauldin (Fuzzy) Department of Computer Science # Mauldin@CAD.CS.CMU.EDU Carnegie-Mellon University # (412) 578-3065 Pittsburgh, PA 15213 # echo 'Start of Rob-O-Matic Distribution:' echo 'x - robomatic.6' sed 's/^X//' > robomatic.6 << '/' X.TH ROBOMATIC 6 10/14/85 X.UC 4 X.SH NAME Xrobomatic \- Automatically Playing a Game of Logic X.SH SYNOPSIS X.I robomatic X[ X.I -d X] X.SH DESCRIPTION X.PP X.I Rob-O-Matic XPlays the "Robots" game written by XAllan Black of Strathclyde University, Glasgow. X.SH OPTIONS X.TP X.B -d XTurns on debugging. X.SH FILES X.PP XLooks for the Robots executable in the current directory, then in two Xplaces specified at compile-time (one of which is /usr/games). X.SH SEE ALSO X.PP Xrobots(6) X.SH BUGS X.PP XDoesn't play an optimum game. X.SH HISTORY X.TP X14-Oct-85 Michael Mauldin (mlm) at Carnegie-Mellon University XCreated. / echo 'x - robomatic.c' sed 's/^X//' > robomatic.c << '/' X/* robomatic.c: Rob-O-Matic I (CMU) Mon Oct 14 11:51:01 EDT 1985 - mlm */ X/***************************************************************** X * X * Rob-O-Matic: Play the robots game X * Copyright (C) 1985 by Mauldin X * The right is granted to any person, university, or company X * to copy, modify, or distribute (for free) this file, providing X * that this notice is not removed. X * X * HISTORY X * 11-Oct-85 Michael Mauldin (mlm) at Carnegie-Mellon University X * Created. X * X *****************************************************************/ X X# include <curses.h> X X# define NEWROBOT "/usr/mlm/bin/robots" X# define ROBOT "/usr/games/robots" X X# define READ 0 X# define WRITE 1 X# define abs(A) ((A)>0?(A):-(A)) X# define max(A,B) ((A)>(B)?(A):(B)) X# define sgn(A) ((A)==0?0:((A)>0?1:-1)) X X/* Define the Rob-O-Matic pseudo-terminal */ X X# define ROBOTTERM "rb|rterm:am:bs:ce=^E:cl=^L:cm=^F%+ %+ :co#80:li#24:pt:ta=^I:up=^A:do=^B:nd=^C:db:xn:" X# define ctrl(C) ((C)&037) X# define BL ctrl('G') X# define BS ctrl('H') X# define CE ctrl('E') X# define CL ctrl('L') X# define CM ctrl('F') X# define CR ctrl('M') X# define DO ctrl('B') X# define LF ctrl('J') X# define ND ctrl('C') X# define TA ctrl('I') X# define UP ctrl('A') X Xint child; Xint frobot, trobot; Xint debug=0; XFILE *trace=NULL; X/**************************************************************** X * Main routine X ****************************************************************/ X Xmain (argc, argv) Xint argc; Xchar *argv[]; X X{ int ptc[2], ctp[2]; X char *rfile=NULL; X X /* Get the options from the command line */ X while (--argc > 0 && (*++argv)[0] == '-') X { while (*++(*argv)) X { switch (**argv) X { case 'd': debug++; break; X default: printf ("Usage: robomatic [-d]\n"); exit (1); X } X } X } X X /* Open tracing file (if needed) */ X if (debug) X { if ((trace = fopen ("trace.log", "w")) == NULL) X { perror ("trace.log"); X exit (1); X } X } X X /* Find an executable of the robots game */ X if (access ("robots", 1) == 0) rfile = "robots"; X else if (access (NEWROBOT, 1) == 0) rfile = NEWROBOT; X else if (access (ROBOT, 1) == 0) rfile = ROBOT; X else X { perror ("robots"); X exit (1); X } X X /* Get two pipes to attach to the Robots process */ X if ((pipe (ptc) < 0) || (pipe (ctp) < 0)) X { fprintf (stderr, "Cannot get pipes!\n"); X exit (1); X } X X trobot = ptc[WRITE]; X frobot = ctp[READ]; X X /* Now fork a child process, update the TERMCAP, and exec the Robots */ X if ((child = fork ()) == 0) X { close (0); X dup (ptc[READ]); X close (1); X dup (ctp[WRITE]); X X putenv ("TERMCAP", ROBOTTERM); X execl (rfile, rfile, 0); X _exit (1); X } X X /* Call Robomatic as the Parent Process */ X else X { robomatic (); } X} X X/**************************************************************** X * Robomatic: Play Robots. Read the scrren, choose a move, and send it X ****************************************************************/ X X# define ROWS 24 X# define COLS 80 X Xchar screen[ROWS][COLS]; Xint playing=1; Xint row=0, col=0, atrow= -1, atcol= -1; X Xrobomatic () X{ int cmd; X X /* Initialize the Curses package */ X initscr (); crmode (); noecho (); clear (); refresh (); X X /* Clearn the screen array */ X blankscreen (); X X /* Read the first screen of output */ X send (';'); /* Cause robots to send a bell when ready to read cmd */ X getrobot (); /* Read screen updates until a bell */ X X /* Main loop, send a command and then read the screen */ X while (playing) X { X /* If the user types anything, execute his commands */ X while (charsavail (stdin)) X { switch (getchar ()) X { case 'd': debug++; X break; X case 'r': clear (); refresh (); drawscreen (); refresh (); X break; X default: break; X } X } X X /* Choose a command and send it */ X cmd = strategy (); X if (cmd == 0) { debug++; dwait ("command is zero"); } X dwait ("Sending command '%c'.", cmd); X send (cmd); X X /* Send the semicolon and read screen updates until a bell */ X send (';'); X getrobot (); X } X X /* Clear the scoreboard on some Robots */ X send ('\n'); X X /* Now wait for the user to type a character before finishing */ X refresh (); X getchar (); X X /* Print termination messages */ X move (ROWS-1, 0); clrtoeol (); refresh (); X endwin (); nocrmode (); noraw (); echo (); X X deadrobot (); X X exit (0); X} X X/**************************************************************** X * blankscreen: Fill the screen array with blanks X ****************************************************************/ X Xblankscreen () X{ register int i, j; X X for (i=0; i<ROWS; i++) X for (j=0; j<COLS; j++) X screen[i][j] = ' '; X} X X/**************************************************************** X * getrobot: Read the Robot screen, stop when you read a bell X ****************************************************************/ X Xgetrobot () X{ int r, c, ch, done=0; X register int i, j; X char *d, *h, buf[BUFSIZ]; X X d = "robot food"; /* FSM to check for death */ X h = "scrap heaps"; /* FSM to check for new level */ X X while (!done) X { X /* Read a character from the Robots process */ X if (read (frobot, buf, 1) < 1) X { ch = EOF; if (trace) fclose (trace); } X else X { ch = buf[0]; if (trace) { fputc (ch, trace); fflush (trace); } } X X /* Check for the words "robot food" to see if we died */ X if (ch == *d) { if (0 == *++d) { done++; playing=0; } } X else d = "robot food"; X X /* Check for the words "scrap heaps" to see if we survived a level */ X if (ch == *h) { if (0 == *++h) { send (';'); } } X else h = "scrap heaps"; X X /* Now figure out what the character means */ X switch (ch) X { case BL: /* Bell */ X done++; X break; X X case BS: /* BackSpace */ X col--; X break; X X case CE: /* Clear to end of line */ X for (i = col; i < COLS; i++) X screen[row][i] = ' '; X X move (row, col); X clrtoeol (); X break; X X case CL: /* Clear Screen */ X clear (); X blankscreen (); X row = col = 0; X break; X X case CM: /* Cursor motion command */ X if (read (frobot, buf, 2) < 2) X { deadrobot (); } X else X { row = buf[0] - ' '; X col = buf[1] - ' '; X if (trace) { fprintf (trace, "%c%c", buf[0], buf[1]); X fflush (trace); } X } X break; X X case CR: /* Carriage Return */ X col = 0; X break; X X case DO: /* Cursor down */ X row++; X break; X X case LF: /* Line Feed */ X row++; X col = 0; X break; X X case ND: /* Non-destructive space (cursor right) */ X col++; X break; X X case TA: /* Tab */ X col = 8 * (1 + col / 8); X break; X X case EOF: /* End of file */ X playing = 0; X return; X break; X X case UP: /* Cursor up */ X row--; X break; X X default: /* Other */ X X /* Control character */ X if (ch < ' ') X { fprintf (stderr, "Unknown character '\\%o'\n", ch); X kill (child, 9); X exit (1); X } X X /* Printing character */ X if (ch == 'I') X { atrow = row; atcol = col; } X mvaddch (row, col, ch); X screen[row][col++] = ch; X break; X } X } X X drawscreen (); X move (row, col); X refresh (); X} X X/**************************************************************** X * deadrobot: We died, read our score and print it out X ****************************************************************/ X Xdeadrobot () X{ int level=0, score=0; X char junk[BUFSIZ]; X X sscanf (screen[ROWS-1], "%s level: %d score: %d", junk, &level, &score); X printf ("Died on level %d with a score of %d.\n", level, score); X} X X/**************************************************************** X * send: Send a command to the Robots process X ****************************************************************/ X Xsend (ch) Xint ch; X{ char buf[BUFSIZ]; X X *buf = ch; X write (trobot, buf, 1); X} X X/**************************************************************** X * drawscreen: Draw the screen array on the user's terminal X ****************************************************************/ X Xdrawscreen () X{ register int r, c; X X for (r=0; r<ROWS; r++) X for (c=0; c<COLS; c++) X mvaddch (r, c, screen[r][c]); X} X X/**************************************************************** X * strategy: Five basic rules X * X * 1. Find the nearest enemy robot. X * 2. Can you hide behind a scrap heap and cause him to crash? X * 3. Can you find another robot close to the first and move in. X * such a way so as to cause them to collide? X * 4. Can you make any safe move? Choose randomly. X * 5. Teleport. X ****************************************************************/ X X/* Arrays for finding directions and keys to go that way */ Xint deltar[] = {-1, -1, -1, 0, 0, 0, 1, 1, 1}; Xint deltac[] = {-1, 0, 1, -1, 0, 1, -1, 0, 1}; Xchar *keydir = "ykuh.lbjn"; X Xstrategy () X{ register int k, r, c, ch, cmd; X int tr=0, tc=0, hr=0, hc=0; X X /* Find closest robot and find a heap to block him with */ X if (closest (&tr, &tc, '=') ) X { if (findheap (tr, tc, &hr, &hc) || findcollide (tr, tc, &hr, &hc)) X { if (cmd = makemove (hr, hc)) return (cmd); } X } X X /* Make a random move */ X cmd = 't'; X for (k=0; k<9; k++) X { r = atrow + deltar[k]; X c = atcol + deltac[k]; X ch = keydir[k]; X if (safe (r, c)) X { if (cmd=='t') cmd = ch; X else if (time(0) & 1) cmd = ch; X } X } X X return (cmd); X} X X/**************************************************************** X * safe: Is it safe to move to a given square X ****************************************************************/ X Xsafe (r, c) Xregister int r, c; X{ X return ((screen[r][c] == ' ' || screen[r][c] == 'I') && X screen[r-1][c-1] != '=' && screen[r-1][ c] != '=' && X screen[r-1][c+1] != '=' && screen[ r][c-1] != '=' && X screen[ r][c+1] != '=' && screen[r+1][c-1] != '=' && X screen[r+1][ c] != '=' && screen[r+1][c+1] != '='); X} X X/**************************************************************** X * find the closest object of a given type X ****************************************************************/ X Xclosest (rp, cp, type) Xint *rp, *cp, type; X{ register int dist=999, newdist, tr=0, tc=0, r, c; X X /* Find closest robot */ X for (r=1; r<ROWS-1; r++) X { for (c=1; c<COLS; c++) X { if (screen[r][c] == type) X { newdist = distance (atrow, atcol, r, c); X if (newdist < dist) X { tr=r; tc=c; dist=newdist; } X } X } X } X X *rp=tr; *cp=tc; X X dwait ("Closest %s is at (%d,%d).", X type == '=' ? "robot" : "scrap heap", tr, tc); X X return (dist < 999); X} X X/**************************************************************** X * distance: Calculate the distance between two points X ****************************************************************/ X Xdistance (r1, c1, r2, c2) Xint r1, c1, r2, c2; X{ register int dr, dc; X X if ((dr = r2-r1) < 0) dr = -dr; /* Absolute row distance */ X if ((dc = c2-c1) < 0) dc = -dc; /* Absolute col distance */ X return (max(dr,dc)); /* Max norm */ X} X X/**************************************************************** X * findheap: Find a heap to block a given robot X ****************************************************************/ X Xfindheap (tr, tc, pr, pc) Xint tr, tc, *pr, *pc; X{ int r, c; X X /* Check closest heap for blocking first */ X if (closest (&r, &c, '@') && willblock (r, c, tr, tc, pr, pc)) X { dwait ("Heading for nearby safe square at (%d,%d).", *pr, *pc); X return (1); X } X X /* Look through all heaps on the screen to find a block */ X for (r=1; r<ROWS-1; r++) X { for (c=1; c<COLS; c++) X { if (screen[r][c] == '@' && willblock (r, c, tr, tc, pr, pc)) X { dwait ("Heading for safe square at (%d,%d).", *pr, *pc); X return (1); X } X } X } X X /* Lose */ X return (0); X} X X/**************************************************************** X * willblock: returns true if the heap at (hr,hc) will block the enemy robot X * located at (tr,tc). The square to move to is returned in (pr, pc) X ****************************************************************/ X Xwillblock (hr, hc, tr, tc, pr, pc) Xint hr, hc, tr, tc, *pr, *pc; X{ register int dr, dc, sr,sc; X X /* First find the "shadow" behind the block from the enemy robot */ X dr = hr-tr; dc = hc-tc; X X if (abs (dr) == abs (dc)) X { sr = hr+sgn (dr); sc = hc+sgn (dc); } X else if (abs (dr) > abs (dc)) X { sr = hr+sgn (dr); sc = hc; } X else X { sr = hr; sc = hc+sgn (dc); } X X /* If we are closer to the shadow square than the robot is, win */ X if (distance (sr, sc, tr, tc) > distance (hr, hc, atrow, atcol)) X { *pr = sr; *pc = sc; return (1); } X X /* Lose */ X return (0); X} X X/**************************************************************** X * findcollide: Find a robot to collide with a given robot. The target X * square will be the nearest safe square to the collision point. X ****************************************************************/ X Xfindcollide (tr, tc, pr, pc) Xint tr, tc, *pr, *pc; X{ int r=0, c=0, d, cr1, cc1, cr2, cc2; X X /* Search from target robot for another on the same row or column */ X for (d=1; d<COLS; d++) X { if (tc+d < COLS && screen[tr][tc+d] == '=') X { r = tr; c = tc+d; break; } X X if (tc-d > 0 && screen[tr][tc-d] == '=') X { r = tr; c = tc-d; break; } X X if (tr+d < LINES-1 && screen[tr+d][tc] == '=') X { r = tr+d; c = tc; break; } X X if (tr-d > 0 && screen[tr-d][tc] == '=') X { r = tr-d; c = tc; break; } X } X X if (r || c) X { dwait ("possible collision between (%d,%d) and (%d,%d)", tr, tc, r, c); } X X /* Check for two on same row */ X if (r == tr && r != atrow) X { /* Check collision up */ X cr1 = tr - (d+1)/2 - 1; X cr2 = tr + (d+1)/2 + 1; X cc1 = cc2 = (tc + c) / 2; X if (distance (atrow, atcol, cr1, cc1) < distance (atrow, atcol, cr2, cc2)) X { *pr = cr1; *pc = cc1; } X else X { *pr = cr2; *pc = cc2; } X X if (*pr < 1 || *pr >= ROWS || *pc < 1 || *pc > COLS) return (0); X X dwait ("predicting safe spot near collision (%d,%d)", *pr, *pc); X return (1); X } X X /* Check for two on same column */ X if (c == tc && c != atcol) X { /* Check collision left */ X cr1 = cr2 = (tr + r) / 2; X cc1 = tc - (d+1)/2 - 1; X cc2 = tc + (d+1)/2 + 1; X if (distance (atrow, atcol, cr1, cc1) < distance (atrow, atcol, cr2, cc2)) X { *pr = cr1; *pc = cc1; } X else X { *pr = cr2; *pc = cc2; } X X if (*pr < 1 || *pr >= ROWS || *pc < 1 || *pc > COLS) return (0); X X dwait ("predicting safe spot near collision (%d,%d)", *pr, *pc); X return (1); X } X X /* Fail */ X return (0); X} X X/**************************************************************** X * makemove: Return the direction to move toward a given square. This X * routine checks to make sure the move is a safe one. Returns 0 if no X * move is safe, and the character if one is found. X ****************************************************************/ X Xmakemove (r, c) X{ int dr, dc; X X dr = sgn (r-atrow); dc = sgn (c-atcol); X X if (dr && dc) X { if (safe (atrow+dr, atcol+dc)) return (keydir[3*dr + dc + 4]); X if (safe (atrow, atcol+dc)) return (keydir[ 0 + dc + 4]); X if (safe (atrow+dr, atcol )) return (keydir[3*dr + 0 + 4]); X } X else if (dr == 0) X { if (safe (atrow, atcol+dc)) return (keydir[ 0 + dc + 4]); X if (safe (atrow+1, atcol+dc)) return (keydir[ 3 + dc + 4]); X if (safe (atrow-1, atcol )) return (keydir[-3 + 0 + 4]); X } X else if (dc == 0) X { if (safe (atrow+dr, atcol )) return (keydir[3*dr + 0 + 4]); X if (safe (atrow, atcol+1)) return (keydir[3*dr + 1 + 4]); X if (safe (atrow+dr, atcol-1)) return (keydir[3*dr - 1 + 4]); X } X X dwait ("makemove: cannot move to (%d,%d) safely.", r, c); X return (0); X} X X/***************************************************************** X * charsavail: How many characters are there at the terminal? If any X * characters are found, 'noterm' is reset, since there is obviously X * a terminal around if the user is typing at us. X *****************************************************************/ X Xcharsavail () X{ long n; X int retc; X X if (retc = ioctl (READ, FIONREAD, &n)) X { fprintf (stderr, "Ioctl returns %d, n=%ld.\n", retc, n); X n=0; X } X X return ((int) n); X} X X/**************************************************************** X * dwait: Debugging message X ****************************************************************/ X Xdwait (f, a1, a2, a3, a4) Xchar *f; Xint a1, a2, a3, a4; X{ char buf[BUFSIZ]; X register int c; X X if (debug) X { mvprintw (0, 0, "[%d,%d] ", atrow, atcol); X printw (f, a1, a2, a3, a4); X addch (' '); X move (row, col); X refresh (); X switch (getchar ()) X { case 'd': debug=0; break; X case 'r': clear (); refresh (); drawscreen (); refresh (); break; X default: break; X } X for (c=0; c<COLS; c++) X { mvaddch (0, c, screen[0][c]); } X } X} X X/* X * putenv -- put value into environment X * X * Usage: i = putenv (name,value) X * int i; X * char *name, *value; X * X * Putenv associates "value" with the environment parameter "name". X * If "value" is 0, then "name" will be deleted from the environment. X * Putenv returns 0 normally, -1 on error (not enough core for malloc). X * X * Putenv may need to add a new name into the environment, or to X * associate a value longer than the current value with a particular X * name. So, to make life simpler, putenv() copies your entire X * environment into the heap (i.e. malloc()) from the stack X * (i.e. where it resides when your process is initiated) the first X * time you call it. X * X * HISTORY X * 14-Oct-85 Michael Mauldin (mlm) at Carnegie-Mellon University X * Ripped out of CMU lib for Rob-O-Matic portability X * 20-Nov-79 Steven Shafer (sas) at Carnegie-Mellon University X * Created for VAX. Too bad Bell Labs didn't provide this. It's X * unfortunate that you have to copy the whole environment onto the X * heap, but the bookkeeping-and-not-so-much-copying approach turns X * out to be much hairier. So, I decided to do the simple thing, X * copying the entire environment onto the heap the first time you X * call putenv(), then doing realloc() uniformly later on. X * Note that "putenv(name,getenv(name))" is a no-op; that's the reason X * for the use of a 0 pointer to tell putenv() to delete an entry. X * X */ X X#define EXTRASIZE 5 /* increment to add to env. size */ X Xchar *index (), *malloc (), *realloc (); Xint strlen (); X Xstatic int envsize = -1; /* current size of environment */ Xextern char **environ; /* the global which is your env. */ X Xstatic int findenv (); /* look for a name in the env. */ Xstatic int newenv (); /* copy env. from stack to heap */ Xstatic int moreenv (); /* incr. size of env. */ X Xint putenv (name, value) Xchar *name, *value; X{ register int i, j; X register char *p; X X if (envsize < 0) X { /* first time putenv called */ X if (newenv () < 0) /* copy env. to heap */ X return (-1); X } X X i = findenv (name); /* look for name in environment */ X X if (value) X { /* put value into environment */ X if (i < 0) X { /* name must be added */ X for (i = 0; environ[i]; i++); X if (i >= (envsize - 1)) X { /* need new slot */ X if (moreenv () < 0) X return (-1); X } X p = malloc (strlen (name) + strlen (value) + 2); X if (p == 0) /* not enough core */ X return (-1); X environ[i + 1] = 0; /* new end of env. */ X } X else X { /* name already in env. */ X p = realloc (environ[i], X strlen (name) + strlen (value) + 2); X if (p == 0) X return (-1); X } X sprintf (p, "%s=%s", name, value);/* copy into env. */ X environ[i] = p; X } X else X { /* delete name from environment */ X if (i >= 0) X { /* name is currently in env. */ X free (environ[i]); X for (j = i; environ[j]; j++); X environ[i] = environ[j - 1]; X environ[j - 1] = 0; X } X } X X return (0); X} X Xstatic int findenv (name) Xchar *name; X{ register char *namechar, *envchar; X register int i, found; X X found = 0; X for (i = 0; environ[i] && !found; i++) X { envchar = environ[i]; X namechar = name; X while (*namechar && (*namechar == *envchar)) X { namechar++; X envchar++; X } X found = (*namechar == '\0' && *envchar == '='); X } X return (found ? i - 1 : -1); X} X Xstatic int newenv () X{ register char **env, *elem; X register int i, esize; X X for (i = 0; environ[i]; i++); X esize = i + EXTRASIZE + 1; X env = (char **) malloc (esize * sizeof (elem)); X if (env == 0) X return (-1); X X for (i = 0; environ[i]; i++) X { elem = malloc (strlen (environ[i]) + 1); X if (elem == 0) X return (-1); X env[i] = elem; X strcpy (elem, environ[i]); X } X X env[i] = 0; X environ = env; X envsize = esize; X return (0); X} X Xstatic int moreenv () X{ register int esize; X register char **env; X X esize = envsize + EXTRASIZE; X env = (char **) realloc (environ, esize * sizeof (*env)); X if (env == 0) X return (-1); X environ = env; X envsize = esize; X return (0); X} / echo 'x - Makefile' sed 's/^X//' > Makefile << '/' Xrobomatic: robomatic.c X cc -O -o robomatic robomatic.c -lcurses -ltermcap / echo 'Rob-O-Matic Distribution complete.' exit