shithub: battleship

Download patch

ref: bc5ac3d46558512971ba8c0f6be02382d91d17f1
parent: fb4b4cbf8062bd0ebaedcd2b3aa6cd1112f35258
author: rodri <rgl@antares-labs.eu>
date: Fri Oct 6 12:31:57 EDT 2023

initial implementation of an AI.

added different modes for those who want
to play with others and those who prefer
to play against a bot (-a flag).

--- /dev/null
+++ b/andy.c
@@ -1,0 +1,168 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <keyboard.h>
+#include <geometry.h>
+#include "dat.h"
+#include "fns.h"
+#include "mixer.h"
+
+/* Nexus-9 technology from The Rosen Association */
+
+static char *nametab[] = {
+	"hannibal",
+	"luba",
+	"roy",
+	"irmgard",
+	"buster",
+	"rachael",
+	"phil",
+	"pris",
+	"polokov",
+	"zhora",
+	"kowalski",
+	"luv",
+	"sapper",
+	"freysa",
+	"mariette",
+};
+Point2 nwes[] = {
+	{0,-1,0},
+{-1,0,0},	{1,0,0},
+	{0,1,0},
+};
+
+static char *
+getaname(void)
+{
+	return nametab[ntruerand(nelem(nametab))];
+}
+
+static void
+doanotherpass(Andy *a)
+{
+	if(--a->passes > 0){
+		a->passdir = mulpt2(a->passdir, -1);
+		a->lastshot = a->firsthit;
+	}else
+		a->disengage(a);
+}
+
+static void
+andy_layout(Andy *a, Msg *m)
+{
+	/* TODO write a real layout algorithm */
+	m->body = estrdup("layout f9v,g6v,b12v,c15v,l14v");
+	sendp(a->ego->battle->data, m);
+}
+
+static void
+andy_shoot(Andy *a, Msg *m)
+{
+	Point2 cell;
+
+Retry:
+	switch(a->state){
+	case ASearching:
+		do
+			cell = Pt2(ntruerand(MAPW), ntruerand(MAPH), 1);
+		while(gettile(a, cell) != Twater);
+		fprint(2, "[%d] search shot\n", getpid());
+		break;
+	case ACalibrating:
+		do
+			cell = addpt2(a->firsthit, nwes[--a->ntries&3]);
+		while(gettile(a, cell) != Twater && a->ntries > 1);
+		if(a->ntries < 1 && gettile(a, cell) != Twater){
+			fprint(2, "[%d] neverland\n", getpid());
+			a->disengage(a);
+			goto Retry;
+		}
+		fprint(2, "[%d] calibrating shot\n", getpid());
+		break;
+	case ABombing:
+		cell = addpt2(a->lastshot, a->passdir);
+		if(gettile(a, cell) != Twater){
+			doanotherpass(a);
+			goto Retry;
+		}
+		fprint(2, "[%d] bombing shot\n", getpid());
+		break;
+	}
+	m->body = smprint("shoot %s", cell2coords(cell));
+	sendp(a->ego->battle->data, m);
+	a->lastshot = cell;
+	fprint(2, "[%d] shot enemy\n", getpid());
+}
+
+static void
+andy_engage(Andy *a)
+{
+	a->firsthit = a->lastshot;
+	a->state = ACalibrating;
+	a->ntries = nelem(nwes);
+	a->passes = 2;
+	fprint(2, "[%d] enemy engaged\n", getpid());
+}
+
+static void
+andy_disengage(Andy *a)
+{
+	a->state = ASearching;
+	fprint(2, "[%d] enemy disengaged\n", getpid());
+}
+
+static void
+andy_registerhit(Andy *a)
+{
+	fprint(2, "[%d] hit enemy\n", getpid());
+	settile(a, a->lastshot, Thit);
+	if(a->state == ASearching)
+		a->engage(a);
+	else if(a->state == ACalibrating){
+		a->passdir = subpt2(a->lastshot, a->firsthit);
+		a->state = ABombing;
+		fprint(2, "[%d] began bombing\n", getpid());
+	}
+}
+
+static void
+andy_registermiss(Andy *a)
+{
+	fprint(2, "[%d] missed enemy\n", getpid());
+	settile(a, a->lastshot, Tmiss);
+	if(a->state == ACalibrating && a->ntries < 1)
+		a->disengage(a);
+	else if(a->state == ABombing){
+		doanotherpass(a);
+		fprint(2, "[%d] bombing pass #%d dir %v\n", getpid(), a->passes, a->passdir);
+	}
+}
+
+Andy *
+newandy(Player *p)
+{
+	Andy *a;
+
+	a = emalloc(sizeof *a);
+	a->ego = p;
+	snprint(p->name, sizeof p->name, "%s", getaname());
+	a->state = ASearching;
+	a->layout = andy_layout;
+	a->shoot = andy_shoot;
+	a->engage = andy_engage;
+	a->disengage = andy_disengage;
+	a->registerhit = andy_registerhit;
+	a->registermiss = andy_registermiss;
+	return a;
+}
+
+void
+freeandy(Andy *a)
+{
+	free(a);
+}
--- a/bts.c
+++ b/bts.c
@@ -151,6 +151,7 @@
 
 struct {
 	int state;
+	int mode;
 } game;
 struct {
 	Image *c; /* color */
@@ -639,6 +640,7 @@
 
 		audio_play(playlist[SCANNON]);
 		cell = toboard(&alienboard, mc->xy);
+		/* TODO check if we already shot at that cell */
 		chanprint(egress, "shoot %s\n", cell2coords(cell));
 		lastshot = cell;
 		break;
@@ -797,7 +799,7 @@
 	case 'p':
 		if(game.state != Waiting0)
 			break;
-		chanprint(egress, "play\n");
+		chanprint(egress, "play %d\n", game.mode);
 		break;
 	case 'w':
 		if(game.state != Waiting0)
@@ -1040,7 +1042,7 @@
 void
 usage(void)
 {
-	fprint(2, "usage: %s [-d] addr\n", argv0);
+	fprint(2, "usage: %s [-da] addr\n", argv0);
 	threadexitsall("usage");
 }
 
@@ -1058,6 +1060,9 @@
 	ARGBEGIN{
 	case 'd':
 		debug++;
+		break;
+	case 'a':
+		game.mode = GMPvAI;
 		break;
 	default: usage();
 	}ARGEND
--- a/btsd.c
+++ b/btsd.c
@@ -19,7 +19,7 @@
 };
 Cmdtab clcmd[] = {
 	CMid,		"id",		2,
-	CMplay, 	"play",		1,
+	CMplay, 	"play",		2,
 	CMlayout, 	"layout",	2,
 	CMshoot, 	"shoot",	2,
 	CMgetmatches,	"watch",	1,
@@ -249,7 +249,7 @@
 void
 playerproc(void *arg)
 {
-	Player *my;
+	Player *p;
 	Match *m;
 	Cmdbuf *cb;
 	Cmdtab *ct;
@@ -256,20 +256,20 @@
 	char *s;
 	int mid;
 
-	my = arg;
+	p = arg;
 
-	threadsetname("player %s", my->nci->raddr);
+	threadsetname("player %s", p->nci->raddr);
 
-	threadsetgrp(my->io.fd);
-	threadcreate(netrecvthread, &my->io, mainstacksize);
-	threadcreate(netsendthread, &my->io, mainstacksize);
+	threadsetgrp(p->io.fd);
+	threadcreate(netrecvthread, &p->io, mainstacksize);
+	threadcreate(netsendthread, &p->io, mainstacksize);
 
-	chanprint(my->io.out, "id\n");
+	chanprint(p->io.out, "id\n");
 
 	enum { NETIN, CTL, NONE };
 	Alt a[] = {
-	 [NETIN]	{my->io.in, &s, CHANRCV},
-	 [CTL]		{my->ctl, &s, CHANRCV},
+	 [NETIN]	{p->io.in, &s, CHANRCV},
+	 [CTL]		{p->ctl, &s, CHANRCV},
 	 [NONE]		{nil, nil, CHANEND}
 	};
 	for(;;)
@@ -283,35 +283,36 @@
 			if(ct == nil)
 				goto Nocmd;
 
-			if(my->name[0] == 0){
+			if(p->name[0] == 0){
 				if(ct->index == CMid && strlen(cb->f[1]) > 0){
-					snprint(my->name, sizeof my->name, "%s", cb->f[1]);
-					sendmatches(my->io.out);
+					snprint(p->name, sizeof p->name, "%s", cb->f[1]);
+					sendmatches(p->io.out);
 				}else
-					chanprint(my->io.out, "id\n");
+					chanprint(p->io.out, "id\n");
 			}else
-				switch(my->state){
+				switch(p->state){
 				case Waiting0:
-					if(ct->index == CMplay)
-						sendp(playerq, my);
-					else if(ct->index == CMgetmatches){
-						sendmatches(my->io.out);
+					if(ct->index == CMplay){
+						p->gamemode = strtoul(cb->f[1], nil, 10);
+						sendp(playerq, p);
+					}else if(ct->index == CMgetmatches){
+						sendmatches(p->io.out);
 					}else if(ct->index == CMwatch){
 						mid = strtoul(cb->f[1], nil, 10);
 						m = getmatch(mid);
 						if(m == nil)
-							chanprint(my->io.out, "no such match\n");
+							chanprint(p->io.out, "no such match\n");
 						else
-							sendp(m->ctl, newmsg(my, estrdup("take seat")));
+							sendp(m->ctl, newmsg(p, estrdup("take seat")));
 					}
 					break;
 				case Watching:
 					if(ct->index == CMleave)
-						sendp(my->battle->ctl, newmsg(my, estrdup("leave seat")));
+						sendp(p->battle->ctl, newmsg(p, estrdup("leave seat")));
 					break;
 				default:
-					if(my->battle != nil)
-						sendp(my->battle->data, newmsg(my, estrdup(s)));
+					if(p->battle != nil)
+						sendp(p->battle->data, newmsg(p, estrdup(s)));
 				}
 Nocmd:
 			free(cb);
@@ -319,15 +320,15 @@
 			break;
 		case CTL:
 			if(s == nil){ /* cable cut */
-				switch(my->state){
+				switch(p->state){
 				case Waiting0:
-					freeplayer(my);
+					freeplayer(p);
 					break;
 				case Ready:
-					sendp(mmctl, newmsg(my, estrdup("player left")));
+					sendp(mmctl, newmsg(p, estrdup("player left")));
 					break;
 				default:
-					sendp(my->battle->ctl, newmsg(my, estrdup("player left")));
+					sendp(p->battle->ctl, newmsg(p, estrdup("player left")));
 				}
 				goto End;
 			}
@@ -342,8 +343,74 @@
 }
 
 void
-aiproc(void *)
+aiproc(void *arg)
 {
+	/* XXX this is a subset of those at bts.c:/^Cmdtab svcmd */
+	enum {
+		ACMlayout,
+		ACMplay,
+		ACMwehit,
+		ACMwemiss,
+		ACMwin,
+		ACMlose,
+	};
+	Cmdtab svcmd[] = {
+		ACMlayout,	"layout",	1,
+		ACMplay,	"play",		1,
+		ACMwehit,	"hit",		1,
+		ACMwemiss,	"miss",		1,
+		ACMwin,		"win",		1,
+		ACMlose,	"lose",		1,
+	};
+	Andy *ai;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+	char *s;
+
+	ai = arg;
+
+	threadsetname("andy %s", ai->ego->name);
+
+	enum { NETIN, CTL, NONE };
+	Alt a[] = {
+	 [NETIN]	{ai->ego->io.out, &s, CHANRCV},
+	 [CTL]		{ai->ego->ctl, &s, CHANRCV},
+	 [NONE]		{nil, nil, CHANEND}
+	};
+	for(;;)
+		switch(alt(a)){
+		case NETIN:
+			if(debug)
+				fprint(2, "[%d] bot rcvd '%s'\n", getpid(), s);
+
+			cb = parsecmd(s, strlen(s));
+			ct = lookupcmd(cb, svcmd, nelem(svcmd));
+			if(ct == nil)
+				goto Nocmd;
+
+//			sleep(ntruerand(5000));
+			switch(ct->index){
+			case ACMlayout: ai->layout(ai, newmsg(ai->ego, nil)); break;
+			case ACMplay: ai->shoot(ai, newmsg(ai->ego, nil)); break;
+			case ACMwehit: ai->registerhit(ai); break;
+			case ACMwemiss: ai->registermiss(ai); break;
+			case ACMwin:
+			case ACMlose: goto End;
+			}
+Nocmd:
+			free(cb);
+			free(s);
+			break;
+		case CTL:
+			free(s);
+			break;
+		}
+End:
+	freeplayer(ai->ego);
+	freeandy(ai);
+	if(debug)
+		fprint(2, "[%d] andy retired\n", getpid());
+	threadexits(nil);
 }
 
 void
@@ -365,7 +432,9 @@
 	m = arg;
 	memset(&stands, 0, sizeof stands);
 
-	threadsetname("battleproc [%d] %s ↔ %s", m->id, m->pl[0]->nci->raddr, m->pl[1]->nci->raddr);
+	threadsetname("battleproc [%d] %s ↔ %s", m->id,
+		m->pl[0]->nci != nil? m->pl[0]->nci->raddr: "andy",
+		m->pl[1]->nci != nil? m->pl[1]->nci->raddr: "andy");
 
 	chanprint(m->pl[0]->io.out, "oid %s\n", m->pl[1]->name);
 	chanprint(m->pl[1]->io.out, "oid %s\n", m->pl[0]->name);
@@ -398,7 +467,7 @@
 				if(ct->index == CMlayout)
 					if(gettokens(cb->f[1], coords, nelem(coords), ",") == nelem(coords)){
 						if(debug)
-							fprint(2, "rcvd layout from %s @ %s\n", p->name, p->nci->raddr);
+							fprint(2, "rcvd layout from %s\n", p->name);
 						for(i = 0; i < nelem(coords); i++){
 							cell = coords2cell(coords[i]);
 							orient = coords[i][strlen(coords[i])-1] == 'h'? OH: OV;
@@ -433,7 +502,7 @@
 						chanprint(p->io.out, "hit\n");
 						chanprint(op->io.out, "hit %s\n", cell2coords(cell));
 						broadcast(&stands, "hit %d %s\n", p == m->pl[0]? 0: 1, cell2coords(cell));
-						if(countshipcells(op) < (debug? 17: 1)){
+						if(countshipcells(op) < (debug? 12: 1)){
 							chanprint(p->io.out, "win\n");
 							chanprint(op->io.out, "lose\n");
 							p->state = Waiting0;
@@ -456,10 +525,10 @@
 						p->state = Waiting;
 						op->state = Playing;
 						broadcast(&stands, "plays %d\n", op == m->pl[0]? 0: 1);
+						if(debug)
+							fprint(2, "%s plays, %s waits\n", op->name, p->name);
 						break;
 					}
-					if(debug)
-						fprint(2, "%s plays, %s waits\n", op->name, p->name);
 				}
 				break;
 			}
@@ -520,7 +589,7 @@
 {
 	Msg *msg;
 	Match *m;
-	Player *pl[2];
+	Player *pl[2], *bot;
 	int i;
 
 	threadsetname("matchmaker");
@@ -543,16 +612,33 @@
 
 			chanprint(pl[i]->io.out, "queued\n");
 
-			if(++i > 1){
-				m = newmatch(pl[0], pl[1]);
+			switch(pl[i]->gamemode){
+			case GMPvP:
+				if(++i > 1){
+					m = newmatch(pl[0], pl[1]);
+					addmatch(m);
+					pl[0]->battle = m;
+					pl[1]->battle = m;
+					i = 0;
+	
+					proccreate(battleproc, m, mainstacksize);
+				}
+				a[QUE].v = &pl[i];
+				break;
+			case GMPvAI:
+				bot = newplayer(-1);
+				memset(bot->map, Twater, MAPW*MAPH);
+
+				m = newmatch(pl[i], bot);
 				addmatch(m);
-				pl[0]->battle = m;
-				pl[1]->battle = m;
+				pl[i]->battle = m;
+				bot->battle = m;
 				i = 0;
 
+				proccreate(aiproc, newandy(bot), mainstacksize);
 				proccreate(battleproc, m, mainstacksize);
+				break;
 			}
-			a[QUE].v = &pl[i];
 			break;
 		case CTL:
 			if(debug) fprint(2, "matchmaker rcvd '%s' from p(fd=%d)\n", msg->body, msg->from->io.fd);
--- a/dat.h
+++ b/dat.h
@@ -18,6 +18,9 @@
 	OH, /* horizontal */
 	OV, /* vertical */
 
+	GMPvP = 0,
+	GMPvAI,
+
 	Waiting0 = 0,
 	Watching,
 	Ready,
@@ -25,6 +28,10 @@
 	Waiting,
 	Playing,
 
+	ASearching = 0,
+	ACalibrating,
+	ABombing,
+
 	Boardmargin = 50,
 	TW = 16,
 	TH = TW,
@@ -55,6 +62,7 @@
 typedef struct Board Board;
 typedef struct Chanpipe Chanpipe;
 typedef struct Player Player;
+typedef struct Andy Andy;
 typedef struct Match Match;
 typedef struct Msg Msg;
 typedef struct Stands Stands;
@@ -94,10 +102,30 @@
 	Map;
 	char name[8+1];
 	int state;
+	int gamemode;
 	Match *battle;
 	NetConnInfo *nci;
 	Chanpipe io;
 	Channel *ctl;
+};
+
+struct Andy
+{
+	Map;		/* of the enemy */
+	Player *ego;
+	int state;
+	Point2 lastshot;
+	Point2 firsthit;
+	Point2 passdir;	/* direction of current pass */
+	int ntries;	/* attempts left to find the direction */
+	int passes;	/* remaining passes (one per direction) */
+
+	void (*layout)(Andy*, Msg*);
+	void (*shoot)(Andy*, Msg*);
+	void (*engage)(Andy*);
+	void (*disengage)(Andy*);
+	void (*registerhit)(Andy*);
+	void (*registermiss)(Andy*);
 };
 
 struct Match
--- a/fns.h
+++ b/fns.h
@@ -38,3 +38,9 @@
  */
 Cmdbuf *parsecmd(char*, int);
 Cmdtab *lookupcmd(Cmdbuf*, Cmdtab*, int);
+
+/*
+ * andy
+ */
+Andy *newandy(Player*);
+void freeandy(Andy*);
--- a/mkfile
+++ b/mkfile
@@ -10,6 +10,7 @@
 	alloc.$O\
 	parse.$O\
 	util.$O\
+	andy.$O\
 	menulist.$O\
 	mixer.$O\