shithub: battleship

Download patch

ref: 8760478c61d8b96d9aab1511b8759daeb84378c5
parent: 05825e751d69cde554c21ddfd00e646049425e31
author: rodri <rgl@antares-labs.eu>
date: Wed Sep 27 11:59:53 EDT 2023

implemented spectator mode.

--- a/bts.c
+++ b/bts.c
@@ -26,6 +26,11 @@
 	CMwatching,
 	CMwin,
 	CMlose,
+	CMplayeroutlay,
+	CMplayerhit,
+	CMplayermiss,
+	CMplayerplays,
+	CMplayerwon,
 };
 Cmdtab svcmd[] = {
 	CMid,		"id",		1,
@@ -41,9 +46,14 @@
 	CMmatchesb,	"matches",	1,
 	CMmatch,	"m",		4,
 	CMmatchese,	"end",		1,
-	CMwatching,	"watching",	6,
+	CMwatching,	"watching",	4,
 	CMwin,		"win",		1,
 	CMlose,		"lose",		1,
+	CMplayeroutlay,	"outlayed",	3,
+	CMplayerhit,	"hit",		3,
+	CMplayermiss,	"miss",		3,
+	CMplayerplays,	"plays",	2,
+	CMplayerwon,	"won",		2,
 };
 
 int debug;
@@ -310,11 +320,12 @@
 	static Image *c;
 	Point p;
 	char *s, aux[32];
+	int i;
 
 	s = "";
 	switch(game.state){
 	case Watching:
-		snprint(aux, sizeof aux, "watching %s vs. %s", match.pl[0], match.pl[1]);
+		snprint(aux, sizeof aux, "watching %s vs. %s", match.pl[0].uid, match.pl[1].uid);
 		s = aux;
 		break;
 	case Ready: s = "looking for players"; break;
@@ -333,9 +344,9 @@
 	vstring(dst, p, display->white, ZP, font, s);
 
 	p = Pt(alienboard.bbox.max.x+2, alienboard.bbox.min.y);
-	vstring(dst, p, display->white, ZP, font, game.state == Watching? match.pl[1]: oid);
+	vstring(dst, p, display->white, ZP, font, game.state == Watching? match.pl[1].uid: oid);
 	p = subpt(localboard.bbox.min, Pt(font->width+2,0));
-	vstring(dst, p, display->white, ZP, font, game.state == Watching? match.pl[0]: uid);
+	vstring(dst, p, display->white, ZP, font, game.state == Watching? match.pl[0].uid: uid);
 
 	if(game.state == Outlaying){
 		if(c == nil)
@@ -349,6 +360,15 @@
 			p = Pt(SCRW/2 - stringwidth(font, s)/2, SCRH-Boardmargin);
 			string(dst, p, c, ZP, font, s);
 		}
+	}else if(game.state == Watching){
+		if(c == nil)
+			c = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DYellow);
+		for(i = 0; i < nelem(match.pl); i++)
+			if(match.pl[i].state == Playing){
+				snprint(aux, sizeof aux, "it's %s's turn", match.pl[i].uid);
+				p = Pt(SCRW/2 - stringwidth(font, aux)/2, SCRH-Boardmargin);
+				string(dst, p, c, ZP, font, aux);
+			}
 	}
 }
 
@@ -756,6 +776,24 @@
 }
 
 void
+announcewinner(char *winner)
+{
+	static Image *c;
+	static char s[16];
+
+	if(winner == nil)
+		return;
+
+	/* TODO build a global color palette. this static color referencing is BS. */
+	if(c == nil)
+		c = eallocimage(display, Rect(0,0,1,1), screen->chan, 1, DGreen);
+
+	snprint(s, sizeof s, "%s WON", winner);
+	conclusion.c = c;
+	conclusion.s = s;
+}
+
+void
 processcmd(char *cmd)
 {
 	Cmdbuf *cb;
@@ -762,7 +800,7 @@
 	Cmdtab *ct;
 	Point2 cell;
 	uchar buf[BY2MAP];
-	int i;
+	int i, idx;
 
 	if(debug)
 		fprint(2, "rcvd '%s'\n", cmd);
@@ -795,14 +833,12 @@
 			matches->filling = 0;
 		else if(ct->index == CMwatching){
 			match.id = strtoul(cb->f[1], nil, 10);
-			match.pl[0] = estrdup(cb->f[2]);
-			match.pl[1] = estrdup(cb->f[3]);
+			snprint(match.pl[0].uid, sizeof match.pl[0].uid, "%s", cb->f[2]);
+			snprint(match.pl[1].uid, sizeof match.pl[1].uid, "%s", cb->f[3]);
+			match.pl[0].state = Outlaying;
+			match.pl[1].state = Outlaying;
 			match.bl[0] = &localboard;
 			match.bl[1] = &alienboard;
-			dec64(buf, sizeof buf, cb->f[4], strlen(cb->f[4]));
-			bitunpackmap(match.bl[0], buf, sizeof buf);
-			dec64(buf, sizeof buf, cb->f[5], strlen(cb->f[5]));
-			bitunpackmap(match.bl[1], buf, sizeof buf);
 			game.state = Watching;
 		}
 		break;
@@ -814,10 +850,28 @@
 			snprint(oid, sizeof oid, "%s", cb->f[1]);
 		break;
 	case Watching:
-		/* <idx?> <uid> (hit|missed) <coord> */
-		/*
-		 * TODO can't use the id as the key because they can collide.
-		 */
+		if(ct->index == CMplayeroutlay){
+			idx = strtoul(cb->f[1], nil, 10);
+			if(dec64(buf, sizeof buf, cb->f[2], strlen(cb->f[2])) < 0)
+				sysfatal("dec64 failed");
+			bitunpackmap(match.bl[idx], buf, sizeof buf);
+			match.pl[idx].state = Waiting;
+		}else if(ct->index == CMplayerhit){
+			idx = strtoul(cb->f[1], nil, 10);
+			cell = coords2cell(cb->f[2]);
+			settile(match.bl[idx^1], cell, Thit);
+		}else if(ct->index == CMplayermiss){
+			idx = strtoul(cb->f[1], nil, 10);
+			cell = coords2cell(cb->f[2]);
+			settile(match.bl[idx^1], cell, Tmiss);
+		}else if(ct->index == CMplayerplays){
+			idx = strtoul(cb->f[1], nil, 10);
+			match.pl[idx].state = Playing;
+			match.pl[idx^1].state = Waiting;
+		}else if(ct->index == CMplayerwon){
+			idx = strtoul(cb->f[1], nil, 10);
+			announcewinner(match.pl[idx].uid);
+		}
 		break;
 	case Outlaying:
 		if(ct->index == CMwait){
--- a/btsd.c
+++ b/btsd.c
@@ -162,6 +162,30 @@
 }
 
 void
+freeseats(Stands *s)
+{
+	int i;
+
+	for(i = 0; i < s->nused; i++){
+		s->seats[i]->state = Waiting0;
+		s->seats[i]->battle = nil;
+	}
+	free(s->seats);
+}
+
+void
+broadcast(Stands *s, char *fmt, ...)
+{
+	va_list arg;
+	int i;
+
+	va_start(arg, fmt);
+	for(i = 0; i < s->nused; i++)
+		chanvprint(s->seats[i]->io.out, fmt, arg);
+	va_end(arg);
+}
+
+void
 netrecvthread(void *arg)
 {
 	Chanpipe *cp;
@@ -316,8 +340,8 @@
 	Cmdbuf *cb;
 	Cmdtab *ct;
 	Player *p, *op;
-	Stands stands;
-	uchar buf1[BY2MAP], buf2[BY2MAP];
+	Stands stands; /* TODO make this a member of Match */
+	uchar buf[BY2MAP];
 	uint n0;
 
 	Point2 cell;
@@ -367,6 +391,8 @@
 							settiles(p, cell, orient, shiplen(i), Tship);
 						}
 						p->state = Waiting;
+						bitpackmap(buf, sizeof buf, p);
+						broadcast(&stands, "outlayed %d %.*[\n", p == m->pl[0]? 0: 1, sizeof buf, buf);
 						if(op->state == Waiting){
 							if(debug){
 								fprint(2, "%s's map:\n", p->name);
@@ -380,6 +406,7 @@
 							chanprint(m->pl[n0%2]->io.out, "play\n");
 							m->pl[n0%2]->state = Playing;
 							chanprint(m->pl[(n0+1)%2]->io.out, "wait\n");
+							broadcast(&stands, "plays %d\n", n0%2);
 						}
 					}
 				break;
@@ -391,6 +418,7 @@
 						settile(op, cell, Thit);
 						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)){
 							chanprint(p->io.out, "win\n");
 							chanprint(op->io.out, "lose\n");
@@ -398,6 +426,7 @@
 							p->battle = nil;
 							op->state = Waiting0;
 							op->battle = nil;
+							broadcast(&stands, "won %d\n", p == m->pl[0]? 0: 1);
 							freemsg(msg);
 							goto Finish;
 						}
@@ -406,11 +435,13 @@
 						settile(op, cell, Tmiss);
 						chanprint(p->io.out, "miss\n");
 						chanprint(op->io.out, "miss %s\n", cell2coords(cell));
+						broadcast(&stands, "miss %d %s\n", p == m->pl[0]? 0: 1, cell2coords(cell));
 Swapturn:
 						chanprint(p->io.out, "wait\n");
 						chanprint(op->io.out, "play\n");
 						p->state = Waiting;
 						op->state = Playing;
+						broadcast(&stands, "plays %d\n", op == m->pl[0]? 0: 1);
 						break;
 					}
 					if(debug)
@@ -433,8 +464,9 @@
 				}else{
 					op = p == m->pl[0]? m->pl[1]: m->pl[0];
 					chanprint(op->io.out, "win\n");
-					op->battle = nil;
 					op->state = Waiting0;
+					op->battle = nil;
+					broadcast(&stands, "won %d\n", op == m->pl[0]? 0: 1);
 					freeplayer(p);
 					freemsg(msg);
 					goto Finish;
@@ -441,17 +473,19 @@
 				}
 			}else if(strcmp(msg->body, "take seat") == 0){
 				takeseat(&stands, p);
-				p->battle = m;
 				p->state = Watching;
-				bitpackmap(buf1, sizeof buf1, m->pl[0]);
-				bitpackmap(buf2, sizeof buf2, m->pl[1]);
-				chanprint(p->io.out, "watching %d %s %s %.*[ %.*[\n",
-					m->id, m->pl[0]->name, m->pl[1]->name,
-					sizeof buf1, buf1, sizeof buf2, buf2);
+				p->battle = m;
+				chanprint(p->io.out, "watching %d %s %s\n",
+					m->id, m->pl[0]->name, m->pl[1]->name);
+				for(i = 0; i < nelem(m->pl); i++)
+					if(m->pl[i]->state != Outlaying){
+						bitpackmap(buf, sizeof buf, m->pl[i]);
+						chanprint(p->io.out, "outlayed %d %.*[\n", i, sizeof buf, buf);
+					}
 			}else if(strcmp(msg->body, "leave seat") == 0){
 				leaveseat(&stands, p);
-				p->battle = nil;
 				p->state = Waiting0;
+				p->battle = nil;
 			}
 
 			freemsg(msg);
@@ -461,7 +495,7 @@
 Finish:
 	if(debug)
 		fprint(2, "[%d] battleproc ending\n", getpid());
-	free(stands.seats);
+	freeseats(&stands);
 	rmmatch(m);
 	freematch(m);
 	threadexits(nil);
--- a/dat.h
+++ b/dat.h
@@ -34,7 +34,7 @@
 	SCRH = Boardmargin+MAPH*TH+TH+MAPH*TH+Boardmargin,
 
 	KB = 1024,
-	BY2MAP = TBITS*MAPW*MAPH/8+1,
+	BY2MAP = (TBITS*MAPW*MAPH+7)/8,
 };
 
 typedef struct Ship Ship;
@@ -114,8 +114,12 @@
 struct MatchInfo
 {
 	int id;
-	char *pl[2];
+	struct {
+		char uid[8+1];
+		int state;
+	} pl[2];
 	Board *bl[2];
+	char conclusion[16];
 };
 
 typedef struct Mentry Mentry;
--- a/fns.h
+++ b/fns.h
@@ -25,6 +25,7 @@
 int min(int, int);
 int bitpackmap(uchar*, ulong, Map*);
 int bitunpackmap(Map*, uchar*, ulong);
+int chanvprint(Channel*, char*, va_list);
 
 /*
  * menulist
--- a/util.c
+++ b/util.c
@@ -197,3 +197,17 @@
 		}
 	return n+1;
 }
+
+int
+chanvprint(Channel *c, char *fmt, va_list arg)
+{
+	char *p;
+	int n;
+
+	p = vsmprint(fmt, arg);
+	if(p == nil)
+		sysfatal("vsmprint failed: %r");
+	n = sendp(c, p);
+	yield();	/* let recipient handle message immediately */
+	return n;
+}