[net.sources.games] Revised Battleship Source

cad@cbosgd.UUCP (Chuck A DeGaul) (03/25/86)

The following is a revised version of Bruce Holloway's battleship
game.  It is based on his latest (I think) version dated 3/21/86
that incorporated all known bugs fixes.  This was the version Bruce
said he thought was now "complete" (Ha!)

This revision makes two major changes:

	1) The game can now be played several different ways using
	   command line options, and
	2) a new way to play using "salvos" has been added.

The command line now looks like this:

	battle [ -b | -s ] [ -a ] [ -m ]

The first argument selects the game type:

	-b is Bruce's "blitz" game (known as cheating)
	-s is my "salvo" game (known as challenging)
	no first option means a standard game

The next two arguments simply turn on two operational modes:

	-a has the computer ask you whether a shot hits, this is
		off by default (opposite of Bruce's version)
	-m has the computer mark its "misses" on your board, and
		is off by default (just like Bruce's version)

    Example: battle -b -m	plays a blitz game and marks misses
	     battle -a -m	plays a standard game, asks you for
					hit/miss results and marks misses
	     battle -s		plays a salvo game
	     battle ?		prints a cryptic usage message


The new game type "salvo", is a popular variation of "battleship" that
allows a player one shot for each of his/her ships still afloat.  This
puts a premium scoring hits early and knocking out some ships and also
makes much harder the situation where you face a superior force with only
your "PT-boat".  Makes for quicker play, too.

Bugs cheerfully heard about, and maybe even fixed!

			---> Chuck A DeGaul <---
				cbosgd!cad

--- Cut Here -------------- Cut Here ---------------- Cut Here ------------

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create:
#	Makefile
#	battle.c
# This archive created: Tue Mar 25 13:30:13 1986
export PATH; PATH=/bin:/usr/bin:$PATH
echo shar: "extracting 'Makefile'" '(248 characters)'
if test -f 'Makefile'
then
	echo shar: "will not over-write existing file 'Makefile'"
else
cat << \SHAR_EOF > 'Makefile'
#	Makefile for Battleship
#
#	If you're compiling this under BSD Unix
#	type:
#		make bsdbattle
#
#	Otherwise,
#	type:
#		make
#
battle: battle.c
	cc -o battle battle.c -lcurses

bsdbattle: battle.c
	cc -o battle -DBSD battle.c -lcurses -ltermcap

SHAR_EOF
fi
echo shar: "extracting 'battle.c'" '(13953 characters)'
if test -f 'battle.c'
then
	echo shar: "will not over-write existing file 'battle.c'"
else
cat << \SHAR_EOF > 'battle.c'
/* 
 * battle.c - original author: Bruce Holloway
 *		mods by: Chuck A DeGaul
 */
#include <stdio.h>
#include <curses.h>
#include <signal.h>

/* #define	BSD	1	/* Define BSD if compiling for BSD Unix */

#ifdef BSD
#define	beep()	write(1,"\007",1);
#define	cbreak	crmode
#define	saveterm savetty
#define	resetterm resetty
#define	nocbreak nocrmode
#endif

#define	OTHER	1-turn

/* following #ifdef constants are now unnecessary, use the - options */
/*#define	NOBLITZ	0 */	/* Defined if HITs don't mean you get to 
				   continue wiping out your opponent. */
/*#define	NOASK	0 */	/* Defined if the computer figures out whether
				   it hits without bothering yout about it. */
/*#define	SEEMISS	0 */	/* Defined if the player sees the computer's
				   misses. */

char numbers[] = "   0  1  2  3  4  5  6  7  8  9";

char carrier[] = "Aircraft Carrier";
char battle[] = "Battleship";
char sub[] = "Submarine";
char destroy[] = "Destroyer";
char ptboat[] = "PT Boat";

char name[40];
char dftname[] = "Stranger";

struct _ships {
    char *name;
    char symbol;
    char length;
    char start;		/* Coordinates - 0,0=0; 10,10=100. */
    char dir;		/* Direction - 0 = right; 1 = down. */
    char hits;		/* How many times has this ship been hit? (-1==sunk) */
    };

struct _ships plyship[] = {
    { carrier,'A',5,0,0,0 },
    { battle,'B',4,0,0,0 },
    { destroy,'D',3,0,0,0 },
    { sub,'S',3,0,0,0 },
    { ptboat,'P',2,0,0,0 },
};

struct _ships cpuship[] = {
    { carrier,'A',5,0,0,0 },
    { battle,'B',4,0,0,0 },
    { destroy,'D',3,0,0,0 },
    { sub,'S',3,0,0,0 },
    { ptboat,'P',2,0,0,0 },
};

char hits[2][100], board[2][100];	/* "Hits" board, and main board. */

int srchstep;
int cpuhits;
int cstart, cdir;
int plywon=0, cpuwon=0;			/* How many games has each won? */
int turn;				/* 0=player, 1=computer */
int huntoffs;				/* Offset on search strategy */

int salvo, blitz, ask, seemiss, do_options();		/* options */

main(argc, argv)
	int argc;
	char *argv[];
{
    do_options(argc, argv);

    intro();
    do {
	initgame();
	while(awinna() == -1) {
	    if (!blitz) {
		if (!salvo) {
	    	    if(turn) cputurn(); else plyturn();
		} else {
		     register int i;

		     i = scount(turn);
		     while (i--) {
			 if (turn) {
			     if (cputurn()) {
				 if (awinna() != -1) {
				     i = 0;
				 }
			     }
			 } else {
			     if(plyturn()) {
				 if (awinna() != -1) {
				     i = 0;
				 }
			     }
			 }
		     }
		 } 
	    } else {
	    	while((turn) ? cputurn() : plyturn());
	    }
	    turn = OTHER;
	}
    } while(playagain());
    uninitgame();
}

#define	PR	addstr

intro(){
    int uninitgame();
    extern char *getlogin();
    char *tmpname;

    srand(time(0L));			/* Kick the random number generator */

    signal(SIGINT,uninitgame);
    if(signal(SIGQUIT,SIG_IGN) != SIG_IGN) signal(SIGQUIT,uninitgame);

    if(tmpname = getlogin())
	strcpy(name,tmpname);
    else
	strcpy(name,dftname);
    name[0] = toupper(name[0]);

    initscr();
    saveterm();
    nonl(); cbreak(); noecho();
    clear();
    mvaddstr(4,29,"Welcome to Battleship!");
    move(8,0);
PR("                                                  \\\n");
PR("                           \\                     \\ \\\n");
PR("                          \\ \\                   \\ \\ \\_____________\n");
PR("                         \\ \\ \\_____________      \\ \\/            |\n");
PR("                          \\ \\/             \\      \\/             |\n");
PR("                           \\/               \\_____/              |__\n");
PR("           ________________/                                       |\n");
PR("           \\  S.S. Penguin                                         |\n");
PR("            \\                                                     /\n");
PR("             \\___________________________________________________/\n");
    mvaddstr(20,27,"Hit any key to continue..."); refresh();
    getch();
}

initgame(){
    int i;

    clear();
    mvaddstr(0,35,"BATTLESHIP");
    mvaddstr(4,12,"Main Board");
    mvaddstr(6,0,numbers);
    move(7,0);
    for(i=0; i<10; ++i){
	printw("%c  .  .  .  .  .  .  .  .  .  .  %c\n",i+'A',i+'A');
	}
    mvaddstr(17,0,numbers);
    mvaddstr(4,55,"Hit/Miss Board");
    mvaddstr(6,45,numbers);
    for(i=0; i<10; ++i){
	mvprintw(7+i,45,"%c  .  .  .  .  .  .  .  .  .  .  %c",i+'A',i+'A');
	}
    mvaddstr(17,45,numbers);
    for(turn=0; turn<2; ++turn)
	for(i=0; i<100; ++i){
	    hits[turn][i] = board[turn][i] = 0;
	    }
    for(turn=0; turn<2; ++turn){
	for(i=0; i<5; ++i)
	    if(!turn) plyplace(&plyship[i]);
	    else cpuplace(&cpuship[i]);
	}
    turn = rnd(2);
    cstart = cdir = -1;
    cpuhits = 0;
    srchstep = 3;
    huntoffs = rnd(srchstep);
}

rnd(n)
int n;
{
    return(((rand() & 0x7FFF) % n));
}

plyplace(ss)
struct _ships *ss;
{
    int c, d;

    do{
	prompt();
	printw("Place your %s (ex.%c%d) ? ",ss->name,rnd(10)+'A',rnd(10));
	c = getcoord();
	d = getdir();
	} while(!checkplace(ss,c,d));
    placeship(ss,c,d);
}

getdir(){
    int d;

    prompt(); addstr("What direction (0=right, 1=down) ? ");
    return(sgetc("01")-'0');
}

placeship(ss,c,d)
struct _ships *ss;
int c, d;
{
    int x, y, l, i;

    for(l=0; l<ss->length; ++l){
	i = c + l * ((d) ? 10 : 1);
	board[turn][i] = ss->symbol;
	x = (i % 10) * 3 + 3;
	y = (i / 10) + 7;
	if(!turn) mvaddch(y,x,ss->symbol);
	}
    ss->start = c;
    ss->dir = d;
    ss->hits = 0;
}

checkplace(ss,c,d)
struct _ships *ss;
int c, d;
{
    int x, y, l;

    x = c%10; y = c/10;
    if(((x+ss->length) > 10 && !d) || ((y+ss->length) > 10 && d==1)){
	if(!turn)
	    switch(rnd(3)){
		case 0:
		    error("Ship is hanging from the edge of the world");
		    break;
		case 1:
		    error("Try fitting it on the board");
		    break;
		case 2:
		    error("Figure I won't find it if you put it there?");
		    break;
		}
	return(0);
	}
    for(l=0; l<ss->length; ++l){
	x = c + l * ((d) ? 10 : 1);
	if(board[turn][x]){
	    if(!turn)
		switch(rnd(3)){
		    case 0:
			error("There's already a ship there");
			break;
		    case 1:
			error("Collision alert! Aaaaaagh!");
			break;
		    case 2:
			error("Er, Admiral, what about the other ship?");
			break;
		    }
	    return(0);
	    }
	}
    return(1);
}

error(s)
char *s;
{
    prompt(); beep();
    printw("%s -- hit any key to continue --",s);
    refresh();
    getch();
}

prompt(){
    move(22,0); clrtoeol();
}

toupper(ch)
int ch;
{
    return((ch >= 'a' && ch <= 'z') ? ch-'a'+'A' : ch);
}

getcoord(){
    int ch, x, y, oldx, oldy;

redo:
    y = sgetc("ABCDEFGHIJ");
    do{
	ch = getch();
	if(ch == 0x7F || ch == 8){
	    addstr("\b \b"); refresh();
	    goto redo;
	    }
	} while(ch < '0' || ch > '9');
    addch(x=ch); refresh();
    return((y-'A')*10+x-'0');
}

cpuplace(ss)
struct _ships *ss;
{
    int c, d;

    do{
	c = rnd(100); d = rnd(2);
	} while(!checkplace(ss,c,d));
    placeship(ss,c,d);
}

awinna(){
    int i, j;
    struct _ships *ss;

    for(i=0; i<2; ++i){
	ss = (i) ? cpuship : plyship;
	for(j=0; j<5; ++j, ++ss)
	    if(ss->length != ss->hits)
		break;
	if(j == 5) return(OTHER);
	}
    return(-1);
}

plyturn(){
    int c, res, i;
    char *m;

    prompt();
    addstr("Where do you want to shoot? ");
    c = getcoord();
    if(!(res = hits[turn][c])){
	hits[turn][c] = res = (board[OTHER][c]) ? 'H' : 'M';
	mvaddch(7+c/10,48+3*(c%10),(res=='H') ? 'H' : 'o');
	if(c = hitship(c)){
	    prompt();
	    switch(rnd(3)){
		case 0:
		    m = "You sank my %s!";
		    break;
		case 1:
		    m = "I have this sinking feeling about my %s....";
		    break;
		case 2:
		    m = "Have some mercy for my %s!";
		    break;
		}
	    move(23,0); clrtoeol(); beep();
	    printw(m,cpuship[c-1].name); refresh();
	    return(awinna() == -1);
	    }
	}
    prompt();
    move(23,0); clrtoeol();
    printw("You %s.",(res=='M')?"missed":"scored a hit"); refresh();
    return(res == 'H');
}

hitship(c)
int c;
{
    struct _ships *ss;
    int sym, i, j;

    ss = (turn) ? plyship : cpuship;
    if(!(sym = board[OTHER][c])) return(0);
    for(i=0; i<5; ++i, ++ss)
	if(ss->symbol == sym){
	    j = ss->hits; ++j; ss->hits = j;
	    if(j == ss->length) return(i+1);
	    return(0);
	    }
}

cputurn(){
    int c, res, x, y, i, d;

redo:
    if(cstart == -1){
	if(cpuhits){
	    for(i=0, c=rnd(100); i<100; ++i, c = (c+1) % 100)
		if(hits[turn][c] == 'H')
		    break;
	    if(i != 100){
		cstart = c;
		cdir = -1;
		goto fndir;
		}
	    }
	do{
	    i = 0;
	    do{
		while(hits[turn][c=rnd(100)]);
		x = c % 10; y = c / 10;
		if(++i == 1000) break;
		} while(((x+huntoffs) % srchstep) != (y % srchstep));
	    if(i == 1000) --srchstep;
	    } while(i == 1000);
	}
    else if(cdir == -1){
fndir:	for(i=0, d=rnd(4); i++ < 4; d = (d+1) % 4){
	    x = cstart%10; y = cstart/10;
	    switch(d){
		case 0: ++x; break;
		case 1: ++y; break;
		case 2: --x; break;
		case 3: --y; break;
		}
	    if(x<0 || x>9 || y<0 || y>9) continue;
	    if(hits[turn][c=y*10+x]) continue;
	    cdir = -2;
	    break;
	    }
	if(i == 4){
	    cstart = -1;
	    goto redo;
	    }
	}
    else{
	x = cstart%10; y = cstart/10;
	switch(cdir){
	    case 0: ++x; break;
	    case 1: ++y; break;
	    case 2: --x; break;
	    case 3: --y; break;
	    }
	if(x<0 || x>9 || y<0 || y>9 || hits[turn][y*10+x]){
	    cdir = (cdir+2) % 4;
	    for(;;){
		switch(cdir){
		    case 0: ++x; break;
		    case 1: ++y; break;
		    case 2: --x; break;
		    case 3: --y; break;
		    }
		if(x<0 || x>9 || y<0 || y>9){ cstart = -1; goto redo; }
		if(!hits[turn][y*10+x]) break;
		}
	    }
	c = y*10 + x;
	}

    if (!ask) {
        res = (board[OTHER][c]) ? 'H' : 'M';
        move(21,0); clrtoeol();
        printw("I shoot at %c%d. I %s!",c/10+'A',c%10,(res=='H')?"hit":"miss");
    } else {
        for(;;){
	    prompt();
	    printw("I shoot at %c%d. Do I (H)it or (M)iss? ",c/10+'A',c%10);
	    res = sgetc("HM");
	    if((res=='H' && !board[OTHER][c]) || (res=='M' && board[OTHER][c])){
	        error("You lie!");
	        continue;
	        }
	    break;
	    }
        addch(res);
    }
    hits[turn][c] = res;
    if(res == 'H') {
	++cpuhits;
	if(cstart == -1) cdir = -1;
	cstart = c;
	if(cdir == -2) cdir = d;
	mvaddch(7+(c/10),3+3*(c%10),'*');
	if (blitz && !ask) {
	    refresh();
	    sleep(1);
	}
    }
    else { 
	if (seemiss) {
	    mvaddch(7+(c/10),3+3*(c%10),' ');
	} else {
	    if(cdir == -2) cdir = -1;
	}
    }
    if(c=hitship(c)){
	cstart = -1;
	cpuhits -= plyship[c-1].length;
	x = plyship[c-1].start;
	d = plyship[c-1].dir;
	y = plyship[c-1].length;
	for(i=0; i<y; ++i){
	    hits[turn][x] = '*';
	    x += (d) ? 10 : 1;
	    }
	}
    if (salvo && !ask) {
	refresh();
	sleep(1);
    }
    if(awinna() != -1) return(0);
    return(res == 'H');
}

playagain(){
    int i, x, y, dx, dy, j;

    for(i=0; i<5; ++i){
	x = cpuship[i].start; y = x/10+7; x = (x % 10) * 3 + 48;
	dx = (cpuship[i].dir) ? 0 : 3;
	dy = (cpuship[i].dir) ? 1 : 0;
	for(j=0; j < cpuship[i].length; ++j){
	    mvaddch(y,x,cpuship[i].symbol);
	    x += dx; y += dy;
	    }
	}

    if(awinna()) ++cpuwon; else ++plywon;
    i = 18 + strlen(name);
    if(plywon >= 10) ++i;
    if(cpuwon >= 10) ++i;
    mvprintw(2,(80-i)/2,"%s: %d     Computer: %d",name,plywon,cpuwon);

    prompt();
    printw((awinna()) ? "Want to be humiliated again, %s? "
		  : "Going to give me a chance for revenge, %s? ",name);
    return(sgetc("YN") == 'Y');
}

uninitgame(){
    refresh();
    resetterm(); echo(); endwin();
    exit(0);
}

sgetc(s)
char *s;
{
    char *s1;
    int ch;

    refresh();
    for(;;){
	ch = toupper(getch());
	if(ch == 3) uninitgame();
	for(s1=s; *s1 && ch != *s1; ++s1);
	if(*s1){
	    addch(ch); refresh();
	    return(ch);
	    }
	}
}

/* 
 * I should use getopts() from libc.a, but I'm leary that other UNIX
 * systems might not have it, although I'd love to use it.
 */
int
do_options(c,op)
	int c;
	char *op[];
{
    register int i;

    if (c > 1) {
	for (i=1; i<c; i++) {
	    switch(op[i][0]) {
	    default:
	    case '?':
		fprintf(stderr, "Usage: battle [ -s | -b ] [ -a ] [ -m ]\n");
		fprintf(stderr, "\tWhere the options are:\n");
		fprintf(stderr, "\t-s : play a salvo game (mutex with -b)\n");
		fprintf(stderr, "\t-b : play a blitz game (mutex with -s)\n");
		fprintf(stderr, "\t-a : computer asks you for hit/miss\n");
		fprintf(stderr, "\t-m : computer misses are displayed\n");
		exit(1);
		break;
	    case '-':
		switch(op[i][1]) {
		case 'b':
		    blitz = 1;
		    if (salvo == 1) {
			fprintf(stderr,
			    "Bad Arg: -b and -s are mutually exclusive\n");
			exit(1);
		    }
		    break;
		case 's':
		    salvo = 1;
		    if (blitz == 1) {
			fprintf(stderr,
			    "Bad Arg: -s and -b are mutually exclusive\n");
			exit(1);
		    }
		    break;
		case 'a':
		    ask = 1;
		    break;
		case 'm':
		    seemiss = 1;
		    break;
		default:
		    fprintf(stderr,
			"Bad Arg: type \"%s ?\" for usage message\n", op[0]);
		    exit(1);
		}
	    }
	}
	fprintf(stdout, "Playing optional game (");
	if (salvo) {
	    fprintf(stdout, "salvo, noblitz, ");
	} else if (blitz) {
	    fprintf(stdout, "blitz, nosalvo, ");
	} else {
	    fprintf(stdout, "noblitz, nosalvo, ");
	}
	if (ask) {
	    fprintf(stdout, "ask, ");
	} else {
	    fprintf(stdout, "noask, ");
	}
	if (seemiss) {
	    fprintf(stdout, "seemiss)\n");
	} else {
	    fprintf(stdout, "noseemiss)\n");
	}
    } else {
	fprintf(stdout,
	    "Playing standard game (noblitz, noslavo, noask, noseemiss)\n");
    }
    sleep(2);
    return(0);
}

int
scount(who)
	int who;
{
    register int i, shots;
    register struct _ships *sp;

    if (who) {
	/* count cpu shots */
	sp = cpuship;
    } else {
	/* count player shots */
	sp = plyship;
    }
    for (i=0, shots = 0; i<5; i++, sp++) {
	/* extra test for machines with unsigned chars! */
	if (sp->hits == (char) -1 || sp->hits >= sp->length) {
	    continue;	/* dead ship */
	} else {
	    shots++;
	}
    }
    return(shots);
}
SHAR_EOF
fi
exit 0
#	End of shell archive

---- Cut Here -------------- Cut Here ------------------ Cut Here -------

				<--- Chuck A DeGaul --->
					cbsogd!cad