mouse@mcgill-vision.UUCP (04/26/86)
Well, I was inspired by cad@cbosgd's posting of (a revised version of) Bruce Holloway's battleship game. I unpacked it and felt that it could use some improvement, but didn't feel up to understanding the code. Instead, I rewrote it. The user interface is nicer, the computer player is nasty, and it keeps a global score file. The only machine I know this runs on is ours (VAX11/750, 4.2bsd UNIX), but it should run on any 4.[23]bsd (or bsd-derived, such as Sun) machine. It uses flock(), so it won't run on some systems out there. Anyway, here it is. Here is what wc should give on the .c file: 1073 2929 20792 main.c -----------------------THIS IS NOT A SHAR FILE.----------------------- ------CUT HERE, STRIP MY .signature, AND SAVE THIS AS A .c FILE.------ /* * Battleship game, written by der Mouse (mcgill-vision!mouse). * * You have a fleet of ships to place on a grid, the computer has a similar * fleet, which it places as it pleases. You shoot alternately, the first * one to knock out all the other player's ships wins. The computer really * plays hardball in the shooting phase; to help compensate, it has * absolutely no intellegence about placing its ships. * * When you run the game, you will be faced with a rather cryptic screen * showing two boards and some letters at the top. The two boards are yours * and the computer's (yours is on the left); the strings at the top are the * ships you have available. The game is waiting for you to place your * ships on your board. To do so, move around (see below) and mark (see * below) first one end and then the other, for each ship in your fleet. * The game is smart enough to figure out which ship you are trying to place * (this stage considers all ships of equal length to be identical). There * is no way to `unplace' a ship, once you mark the second point you are * stuck with it. If you mark the first point and you don't want the ship * there, you can either mark the same point again, which clears the mark, * or you can do something else illegal (like try to place the ship in an * illegal direction). Ships can be placed in the 8 principal directions, * and there is no prohibition on diagonal ships being interlaced. * * To move, use (a) Rogue/Hack keys, (b) the 3x3 keypad centered on the 's' * key ('q', 'w', 'e', 'd', 'c', 'x', 'z', 'a'), or (on terminals with a * numeric keypad), the 3x3 keypad centered on the '5' key. To select a * point, use the escape key, the '.' key, LINEFEED, or RETURN. * * Once all your ships are placed, the computer places its ships (actually, * it places them before you get to place yours, but the difference is * irrelevant). Then you get to start shooting. Which of you shoots first * is random; thereafter you shoot alternately. When you shoot, the pixel * you shot is displayed in whatever `standout' mode your terminal provides; * if it was a miss, the '.' which was there is just highlighted; if it was * a hit, a character indicating the ship type appears (highlighted). That * is, when you hit, you know what type of ship you hit. This was done * because the algorithm I use for the computer requires this information as * well, and of course I had to keep the game fair. * * Eventually, one of you will lose. When this happens, your score is * updated and you are asked whether you want to play again. If you say no, * your score is updated in the global file (if one is configured into the * game); if you say yes, things restart from the point you were at just * after starting the game. * * --------------Installation * * Edit the configuration parameters below to suit you, then compile with * * cc -o <whatever> <this file> -lcurses -ltermcap * * (-ltermcap is known as -ltermlib on some systems, I believe). */ /* * Configuration parameters. * * BDSIZE is the size of the board. Each board is this many pixels square. * Battleship does not check that the boards fit on the screen (this could * be considered a bug). * * ship_lens[] is an array listing the ships, by size. Battleship can go * into an infinite loop if there are enough ships here that it is possible * to place some such that another cannot be placed. * * ship_chars[] is an array of characters used for displaying the ships. * Order must match ship_lens[]. These characters should all be distinct; * otherwise the computer will have more information than the player. * * score_file[] is the pathname of the global score file, which is used to * remember scores between games. If you do not want to keep a score file, * you can either set this to /dev/null or you can configure it as a * non-existant file. The game will not create the file if it does not * exist; to start with an empty list, create the score file with zero size * (eg, use cp /dev/null to it). The game must be able to write the score * file; if this is not the case it will behave as if no score file were * configured. This means that either the score file must be writable by * all users who will play battleship or that battleship must be setuid (or * setgid) to a uid (gid) which can write the score file. The setuid/setgid * solution is more secure, but if you are worried about making some code * you just pulled off USEnet setuid (even though it need not be to root), * you can just go with a mode 666 score file (though then any joker who * pleases can modify their or anyone's score, though why they'd want to I * dunno). */ #define BDSIZE 10 int ship_lens[] = { 5, 4, 3, 3, 2 }; char ship_chars[] = "ABDSP"; char score_file[] = "/u1/mouse/games/battleship/scores"; /* * End of configuration parameters */ #include <curses.h> #include <ctype.h> #include <setjmp.h> #include <signal.h> #include <sys/file.h> char *getenv(); #define ARRAYSIZE(arr) (sizeof(arr)/sizeof(arr[0])) #define NSHIPS (ARRAYSIZE(ship_lens)) typedef struct { unsigned char x; unsigned char y; } POS; typedef struct { POS scr; POS pos; char shipno; char force_lit; char showhidden; char shot_at; } PIXEL; typedef struct _board { int s_o_x; int s_o_y; POS (*getshot)(); int (*aftermath)(); int (*he_sunk)(); int (*you_sunk)(); struct _board *target; struct _board *nextturn; char hits_left[NSHIPS]; PIXEL b[BDSIZE][BDSIZE]; } BOARD; BOARD Human; #define human (&Human) BOARD Computer; #define computer (&Computer) BOARD *turn; int min_ship_len; int max_ship_len; int nwon_human; int nwon_computer; jmp_buf done_jmp; FILE *scoref; char *username; char human_name[512]; /* sigh, the problems of distributing code.... */ int mth$int_random() { static long int init = 0; if (init == 0) { time(&init); srandom((int)init); } return(random()); } int rnd(min,max) register int min; register int max; { return((mth$int_random()%(max+1-min))+min); } int max(a,b) register int a; register int b; { return((a>b)?a:b); } char char_for(p) register PIXEL *p; { if (p->shipno >= 0) { return(ship_chars[p->shipno]); } else { return('.'); } } int oob(x,y) register int x; register int y; { return((x<0)||(y<0)||(x>=BDSIZE)||(y>=BDSIZE)); } int ship_ok(board,shipno,x,y,dx,dy) BOARD *board; int shipno; int x; int y; int dx; int dy; { int len; if ((dx == 0) && (dy == 0)) { return(0); } for (len=ship_lens[shipno];len>0;len--) { if (oob(x,y) || (board->b[x][y].shipno >= 0)) { return(0); } x += dx; y += dy; } return(1); } POS getpos(board) BOARD *board; { int ox; int oy; static int x = BDSIZE / 2; static int y = BDSIZE / 2; char c; POS pos; ox = board->s_o_x; oy = board->s_o_y; while (1) { move(oy+y,ox+x+x); refresh(); c = getch(); clear_message(); switch (c) { default: putchar('\7'); fflush(stdout); break; case '.': case '\33': case '\r': case '\n': pos.x = x; pos.y = y; return(pos); break; case 'h': case 'a': case '4': x --; break; case 'j': case 'x': case '2': y ++; break; case 'k': case 'w': case '8': y --; break; case 'l': case 'd': case '6': x ++; break; case 'y': case 'q': case '7': x --; y --; break; case 'u': case 'e': case '9': x ++; y --; break; case 'b': case 'z': case '1': x --; y ++; break; case 'n': case 'c': case '3': x ++; y ++; break; } if (x < 0) { x = 0; } if (x >= BDSIZE) { x = BDSIZE - 1; } if (y < 0) { y = 0; } if (y >= BDSIZE) { y = BDSIZE - 1; } } } POS get_human_shot(); /* these are defined at the end */ int human_aftermath(); int human_he_sunk(); int human_you_sunk(); POS get_computer_shot(); int computer_aftermath(); int computer_he_sunk(); int computer_you_sunk(); int yes(msg) char *msg; { char resp; message("%s",msg); while (1) { refresh(); resp = getch(); switch (resp) { case 'y': case 'Y': case '1': case 't': case 'T': return(1); break; case 'n': case 'N': case '0': return(0); break; } message("%s (Y/N) ",msg); } } int taketurn() { POS shot; PIXEL *p; BOARD *t; shot = (*turn->getshot)(turn); t = turn->target; p = &t->b[shot.x][shot.y]; (*turn->aftermath)(p->pos.x,p->pos.y,p->shipno); if (! p->shot_at) { p->shot_at = 1; if (p->shipno >= 0) { t->hits_left[p->shipno] --; if (t->hits_left[p->shipno] <= 0) { (*t->he_sunk)(p->shipno); (*turn->you_sunk)(p->shipno); } } } update_pixel(p); turn = turn->nextturn; } int gamewon() { int i; int h; int c; h = 0; c = 0; for (i=0;i<NSHIPS;i++) { h += human->hits_left[i]; c += computer->hits_left[i]; } if ((h != 0) && (c != 0)) { return(0); } if ((h == 0) && (c == 0)) { message("INTERNAL ERROR, both human and computer done at once!"); refresh(); endwin(); abort(); } if (h == 0) { nwon_computer ++; } else { nwon_human ++; } drawscore(); showallships(); } int another_game() { return(yes("Care for another? ")); } message(fmt,a1,a2,a3,a4,a5,a6,a7,a8) char *fmt; int a1; int a2; int a3; int a4; int a5; int a6; int a7; int a8; { move(LINES-1,0); printw(fmt,a1,a2,a3,a4,a5,a6,a7,a8); clrtoeol(); } clear_message() { move(LINES-1,0); clrtoeol(); } topline(fmt,a1,a2,a3,a4,a5,a6,a7,a8) char *fmt; int a1; int a2; int a3; int a4; int a5; int a6; int a7; int a8; { move(0,0); printw(fmt,a1,a2,a3,a4,a5,a6,a7,a8); clrtoeol(); } clear_topline() { topline(""); } init_ships() { int i; min_ship_len = ship_lens[0]; max_ship_len = ship_lens[0]; for (i=1;i<NSHIPS;i++) { if (ship_lens[i] < min_ship_len) { min_ship_len = ship_lens[i]; } else if (ship_lens[i] > max_ship_len) { max_ship_len = ship_lens[i]; } } } init_boards() { int i; int j; PIXEL *p; human->s_o_x = (COLS - ((4 * BDSIZE) - 2)) / 3; human->s_o_y = (LINES - BDSIZE) / 2; human->getshot = get_human_shot; human->aftermath = human_aftermath; human->he_sunk = human_he_sunk; human->you_sunk = human_you_sunk; human->nextturn = computer; human->target = computer; computer->s_o_x = COLS - human->s_o_x - ((2 * BDSIZE) - 1); computer->s_o_y = human->s_o_y; computer->getshot = get_computer_shot; computer->aftermath = computer_aftermath; computer->he_sunk = computer_he_sunk; computer->you_sunk = computer_you_sunk; computer->nextturn = human; computer->target = human; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { p = &human->b[i][j]; p->shipno = -1; p->shot_at = 0; p->force_lit = 0; p->pos.x = i; p->pos.y = j; p->scr.x = human->s_o_x + (2 * i); p->scr.y = human->s_o_y + j; p->showhidden = 1; p = &computer->b[i][j]; p->shipno = -1; p->shot_at = 0; p->force_lit = 0; p->pos.x = i; p->pos.y = j; p->scr.x = computer->s_o_x + (2 * i); p->scr.y = computer->s_o_y + j; p->showhidden = 0; } } for (i=0;i<NSHIPS;i++) { human->hits_left[i] = ship_lens[i]; computer->hits_left[i] = ship_lens[i]; } computer_init(); } showallships() { int i; int j; PIXEL *p; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { p = &computer->b[i][j]; p->showhidden = 1; } } drawboard(computer); } complete_redraw() { int i; int j; PIXEL *p; clear(); drawboard(human); drawboard(computer); drawscore(); } drawscore() { topline("%s %d Computer %d",human_name,nwon_human,nwon_computer); } drawboard(board) BOARD *board; { int i; int j; PIXEL *p; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { update_pixel(&board->b[i][j]); } } } update_pixel(p) PIXEL *p; { if (p->shot_at || p->force_lit) { standout(); } mvaddch( (int)p->scr.y, (int)p->scr.x, (p->shot_at||p->showhidden) ? char_for(p) : '.' ); standend(); } place_a_ship_randomly(shipno,board) int shipno; BOARD *board; { int x; int y; int dx; int dy; do { x = rnd(0,BDSIZE-1); y = rnd(0,BDSIZE-1); dx = rnd(-1,1); dy = rnd(-1,1); } while (!ship_ok(board,shipno,x,y,dx,dy)); enter_ship(board,shipno,x,y,dx,dy,0); } enter_ship(board,shipno,x,y,dx,dy,update) BOARD *board; int shipno; int x; int y; int dx; int dy; int update; { int len; for (len=ship_lens[shipno];len>0;len--) { board->b[x][y].shipno = shipno; if (update) { update_pixel(&board->b[x][y]); } x += dx; y += dy; } } place_computer_ships() { int i; for (i=0;i<NSHIPS;i++) { place_a_ship_randomly(i,computer); } } place_human_ships() { char done[NSHIPS]; char scrx[NSHIPS]; int i; int x; POS end; PIXEL *p1; PIXEL *p2; int dx; int dy; int len; int shipsleft; char c; move(0,0); clrtoeol(); x = 0; for (i=0;i<NSHIPS;i++) { done[i] = 0; scrx[i] = x; x += ship_lens[i] + 1; } shipsleft = NSHIPS; while (shipsleft > 0) { for (i=0;i<NSHIPS;i++) { c = done[i] ? ' ' : ship_chars[i]; move(0,scrx[i]); for (x=0;x<ship_lens[i];x++) { addch(c); } } end = getpos(human); p1 = &human->b[end.x][end.y]; p1->force_lit = 1; update_pixel(p1); p1->force_lit = 0; end = getpos(human); p2 = &human->b[end.x][end.y]; update_pixel(p1); dx = p2->pos.x - p1->pos.x; dy = p2->pos.y - p1->pos.y; len = max(abs(dx),abs(dy)); if (len == 0) { continue; } if ( (dx && (dx != len) && (dx != -len)) || (dy && (dy != len) && (dy != -len)) ) { message("Ships must be placed orthogonally or diagonally!"); continue; } dx /= len; dy /= len; len ++; if (len > max_ship_len) { message("There are no ships that long!"); continue; } if (len < min_ship_len) { message("There are no ships that short!"); continue; } for (i=0;i<NSHIPS;i++) { if ((ship_lens[i] == len) && !done[i]) { break; } } if (i >= NSHIPS) { message("You have already placed all your ships of that length!"); continue; } if (! ship_ok(human,i,p1->pos.x,p1->pos.y,dx,dy)) { message("You can't put that ship there!"); continue; } enter_ship(human,i,p1->pos.x,p1->pos.y,dx,dy,1); done[i] = 1; shipsleft --; } move(0,0); clrtoeol(); drawscore(); } pause() { refresh(); getch(); } done() { longjmp(done_jmp,1); } main() { int havescores; initscr(); noecho(); nonl(); crmode(); havescores = 0; if (setjmp(done_jmp)) { move(LINES-1,0); refresh(); endwin(); printf("\n"); if (havescores) { savescores(); } exit(0); } signal(SIGINT,done); init_scores(); havescores = 1; do { init_ships(); init_boards(); complete_redraw(); place_computer_ships(); place_human_ships(); turn = (mth$int_random() & 1) ? human : computer; while (! gamewon()) { taketurn(); } } while (another_game()); done(); } lock_scoref() { flock(fileno(scoref),LOCK_EX); } unlock_scoref() { flock(fileno(scoref),LOCK_UN); } capitalize(str) char str[]; { char *cp; int inword = 0; for (cp=str;*cp;cp++) { if (isascii(*cp) && isalnum(*cp)) { if (!inword) { if (islower(*cp)) { *cp = toupper(*cp); } } inword = 1; } else { inword = 0; } } } init_scores() { char sbuf[512]; char user[512]; int hwon; int cwon; username = getenv("USER"); if (username == 0) { username = "anonymous"; } strcpy(human_name,username); capitalize(human_name); scoref = fopen(score_file,"r+"); if (scoref == 0) { scoref = fopen("/dev/null","r+"); if (scoref == 0) { message("Cannot open scorefile or /dev/null, sorry"); done(); } } rewind(scoref); lock_scoref(); while (fgets(sbuf,sizeof(sbuf),scoref) == sbuf) { if (sscanf(sbuf,"%s%d%d",user,&hwon,&cwon) == 3) { if (strcmp(user,username) == 0) { rewind(scoref); unlock_scoref(); nwon_human = hwon; nwon_computer = cwon; return; } } } rewind(scoref); unlock_scoref(); nwon_human = 0; nwon_computer = 0; } savescores() { char sbuf[512]; char user[512]; int hwon; int cwon; long int rbegin; rewind(scoref); lock_scoref(); while (1) { rbegin = ftell(scoref); if (fgets(sbuf,sizeof(sbuf),scoref) != sbuf) { break; } if (sscanf(sbuf,"%s%d%d",user,&hwon,&cwon) == 3) { if (strcmp(user,username) == 0) { fseek(scoref,rbegin,0); fprintf(scoref,"%s %10d %10d\n",username,nwon_human,nwon_computer); rewind(scoref); unlock_scoref(); return; } } } fseek(scoref,0L,2); fprintf(scoref,"%s %10d %10d\n",username,nwon_human,nwon_computer); rewind(scoref); unlock_scoref(); } POS get_human_shot(b) BOARD *b; { return(getpos(b->target)); } int human_aftermath(x,y,t) int x; int y; int t; { } int human_he_sunk(sno) int sno; { message("Gotcha!"); } int human_you_sunk(sno) int sno; { message("Ouch!"); } char known[BDSIZE][BDSIZE]; #define UNKNOWN (-2) #define EMPTY (-1) char shiphot[NSHIPS]; char shipdead[NSHIPS]; int count[BDSIZE][BDSIZE]; computer_init() { int i; int j; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { known[i][j] = UNKNOWN; } } for (i=0;i<NSHIPS;i++) { shipdead[i] = 0; shiphot[i] = 0; } } computer_aftermath(x,y,sno) int x; int y; int sno; { if (sno >= 0) { known[x][y] = sno; shiphot[sno] = 1; } else { known[x][y] = EMPTY; } } computer_he_sunk(sno) int sno; { } computer_you_sunk(sno) int sno; { shipdead[sno] = 1; shiphot[sno] = 0; } maybe_count(b,sno,sx,sy,dx,dy) BOARD *b; int sno; int sx; int sy; int dx; int dy; { int n; int i; int x; int y; x = sx; y = sy; n = 0; for (i=ship_lens[sno];i>0;i--,(x+=dx),(y+=dy)) { if (known[x][y] == EMPTY) { return; } if (known[x][y] == UNKNOWN) { continue; } if (known[x][y] == sno) { n ++; } else { return; } } if (shiphot[sno] && (n != ship_lens[sno]-b->hits_left[sno])) { return; } x = sx; y = sy; for (i=ship_lens[sno];i>0;i--,(x+=dx),(y+=dy)) { count[x][y] ++; } } drawcountpixel(x,y) int x; int y; { PIXEL *p; p = &human->b[x][y]; mvaddch(p->scr.y,p->scr.x-1, " -~=+*#@123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" [count[x][y]]); } POS get_computer_shot(b) BOARD *b; { int sno; int i; int j; int best; int n; POS shot; BOARD *target; char c; PIXEL *p; char interesting[NSHIPS]; for (i=0;i<NSHIPS;i++) { interesting[i] = ! shipdead[i]; } for (i=0;i<NSHIPS;i++) { if (shiphot[i]) { for (i=0;i<NSHIPS;i++) { interesting[i] &= shiphot[i]; } break; } } /* move(1,0); printw("Dead: "); for(i=0;i<NSHIPS;i++) { addch(shipdead[i]?ship_chars[i]:'-'); } printw(" Hot: "); for(i=0;i<NSHIPS;i++) { addch(shiphot[i]?ship_chars[i]:'-'); } printw(" Interesting: "); for(i=0;i<NSHIPS;i++) { addch(interesting[i]?ship_chars[i]:'-'); } clrtoeol(); for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { p = &human->b[i][j]; switch (known[i][j]) { case UNKNOWN: c = '?'; break; case EMPTY: c = ' '; break; default: c = ship_chars[known[i][j]]; break; } mvaddch(p->scr.y,p->scr.x-1,c); } } */ for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { count[i][j] = 0; } } target = b->target; for (sno=0;sno<NSHIPS;sno++) { if (interesting[sno]) { for (i=ship_lens[sno]-1;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { maybe_count(target,sno,j,i,0,-1); maybe_count(target,sno,i,j,-1,0); } for (j=ship_lens[sno]-1;j<BDSIZE;j++) { maybe_count(target,sno,j,i,-1,-1); } for (j=BDSIZE-ship_lens[sno];j>=0;j--) { maybe_count(target,sno,j,i,1,-1); } } } } for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { if (known[i][j] != UNKNOWN) { count[i][j] = 0; } } } best = 0; do { shot.x = mth$int_random() % BDSIZE; shot.y = mth$int_random() % BDSIZE; } while (known[shot.x][shot.y] != UNKNOWN); n = 0; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { if (count[i][j] > best) { best = count[i][j]; n = 1; shot.x = i; shot.y = j; } else if (count[i][j] == best) { n ++; } } } n = mth$int_random() % n; for (i=0;i<BDSIZE;i++) { for (j=0;j<BDSIZE;j++) { if (count[i][j] == best) { n --; if (n < 0) { shot.x = i; shot.y = j; return(shot); } } } } message("Error, cannot find shot for computer!"); pause(); return(shot); } ----------------------END OF BATTLESHIP .c FILE----------------------- -- der Mouse USA: {ihnp4,decvax,akgua,utzoo,etc}!utcsri!mcgill-vision!mouse philabs!micomvax!musocs!mcgill-vision!mouse Europe: mcvax!decvax!utcsri!mcgill-vision!mouse mcvax!seismo!cmcl2!philabs!micomvax!musocs!mcgill-vision!mouse ARPAnet: utcsri!mcgill-vision!mouse@uw-beaver.arpa "Come with me a few minutes, mortal, and we shall talk."