shithub: battleship

Download patch

ref: fbea30a51227ccf96382519966f09791b02d0f8e
parent: d9752f9750402f01386dceb3606b6cba7a675155
author: rodri <rgl@antares-labs.eu>
date: Tue Aug 29 06:19:42 EDT 2023

major development stride:

- implemented per-line msg framing
- wrote most of the game logic
- fixed an issue where the ship would go off-board when rotated

--- a/bts.c
+++ b/bts.c
@@ -22,6 +22,7 @@
 Board localboard;
 Ship armada[NSHIPS];
 Ship *curship;
+Point2 lastshot;
 
 struct {
 	int state;
@@ -96,16 +97,6 @@
 }
 
 void
-settile(Board *b, Point2 cell, int type)
-{
-	Point p;
-
-	p.x = cell.x;
-	p.y = cell.y;
-	b->map[p.x][p.y] = type;
-}
-
-void
 drawtile(Image *dst, Board *b, Point2 cell, int type)
 {
 	Point p;
@@ -287,14 +278,7 @@
 
 	for(i = 0; i < nelem(armada); i++){
 		s = &armada[i];
-		switch(i){
-		case Scarrier: s->ncells = 5; break;
-		case Sbattleship: s->ncells = 4; break;
-		case Scruiser: /* fallthrough */
-		case Ssubmarine: s->ncells = 3; break;
-		case Sdestroyer: s->ncells = 2; break;
-		default: sysfatal("initarmada: unknown ship: %d", i);
-		}
+		s->ncells = shiplen(i);
 		s->orient = OV;
 		s->hit = emalloc(s->ncells*sizeof(int));
 		memset(s->hit, 0, s->ncells*sizeof(int));
@@ -354,11 +338,15 @@
 	cell = toboard(b, mc->xy);
 	switch(game.state){
 	case Outlaying:
-		if(curship != nil && ++curship-armada >= nelem(armada))
-			curship = nil;
+		if(b == &localboard)
+			if(curship != nil && ++curship-armada >= nelem(armada))
+				curship = nil;
 		break;
 	case Playing:
-		chanprint(egress, "shoot %s", cell2coords(cell));
+		if(b == &alienboard){
+			chanprint(egress, "shoot %s\n", cell2coords(cell));
+			lastshot = cell;
+		}
 		break;
 	}
 	send(drawchan, nil);
@@ -382,8 +370,24 @@
 	mc->xy = addpt(mc->xy, screen->r.min);
 	switch(menuhit(2, mc, &menu, _screen)){
 	case ROTATE:
-		if(curship != nil)
+		if(curship != nil){
 			curship->orient = curship->orient == OH? OV: OH;
+			curship->bbox = mkshipbbox(curship->p, curship->orient, curship->ncells);
+
+			/* steer it, captain! don't let it go off-board! */
+			if(!rectinrect(curship->bbox, localboard.bbox))
+				switch(curship->orient){
+				case OH:
+					curship->bbox.min.x -= curship->bbox.max.x-localboard.bbox.max.x;
+					curship->bbox.max.x = localboard.bbox.max.x;
+					break;
+				case OV:
+					curship->bbox.min.y -= curship->bbox.max.y-localboard.bbox.max.y;
+					curship->bbox.max.y = localboard.bbox.max.y;
+					break;
+				}
+				curship->p = toboard(&localboard, curship->bbox.min);
+		}
 		break;
 	}
 	send(drawchan, nil);
@@ -428,7 +432,7 @@
 			n += snprint(buf+n, sizeof buf - n, "%s%c",
 				cell2coords(armada[i].p), armada[i].orient == OH? 'h': 'v');
 		}
-		chanprint(egress, "layout %s", buf);
+		chanprint(egress, "layout %s\n", buf);
 		break;
 	}
 	send(drawchan, nil);
@@ -513,55 +517,81 @@
 }
 
 void
+processcmd(char *cmd)
+{
+	char *coords[2];
+
+	if(debug)
+		fprint(2, "rcvd '%s'\n", cmd);
+
+	if(strcmp(cmd, "win") == 0){
+//		celebrate();
+		game.state = Waiting0;
+	}else if(strcmp(cmd, "lose") == 0){
+//		keelhaul();
+		game.state = Waiting0;
+	}
+
+	switch(game.state){
+	case Waiting0:
+		if(strcmp(cmd, "layout") == 0){
+			game.state = Outlaying;
+			curship = &armada[0];
+		}
+		break;
+	case Outlaying:
+		if(strcmp(cmd, "wait") == 0)
+			game.state = Waiting;
+		else if(strcmp(cmd, "play") == 0)
+			game.state = Playing;
+		break;
+	case Playing:
+		if(strcmp(cmd, "wait") == 0)
+			game.state = Waiting;
+		else if(strcmp(cmd, "hit") == 0)
+			settile(&alienboard, lastshot, Thit);
+		else if(strcmp(cmd, "miss") == 0)
+			settile(&alienboard, lastshot, Tmiss);
+		break;
+	case Waiting:
+		if(strcmp(cmd, "play") == 0)
+			game.state = Playing;
+		else if(strncmp(cmd, "hit", 3) == 0){
+			if(gettokens(cmd+4, coords, nelem(coords), "-") == nelem(coords))
+				settile(&localboard, Pt2(strtoul(coords[0], nil, 10), strtoul(coords[1], nil, 10), 1), Thit);
+		}else if(strncmp(cmd, "miss", 4) == 0){
+			if(gettokens(cmd+5, coords, nelem(coords), "-") == nelem(coords))
+				settile(&localboard, Pt2(strtoul(coords[0], nil, 10), strtoul(coords[1], nil, 10), 1), Tmiss);
+		}
+		break;
+	}
+	send(drawchan, nil);
+}
+
+void
 netrecvthread(void *arg)
 {
 	Ioproc *io;
-	char buf[256], *coords[2];
-	int n, fd;
+	char buf[256], *s, *e;
+	int n, tot, fd;
 
 	fd = *(int*)arg;
 	io = ioproc();
 
-	while((n = ioread(io, fd, buf, sizeof(buf)-1)) > 0){
-		buf[n] = 0;
-
-		if(debug)
-			fprint(2, "rcvd '%s'\n", buf);
-
-		if(strcmp(buf, "win") == 0)
-			game.state = Waiting0;
-		else if(strcmp(buf, "lose") == 0)
-			game.state = Waiting0;
-
-		switch(game.state){
-		case Waiting0:
-			if(strcmp(buf, "layout") == 0){
-				game.state = Outlaying;
-				curship = &armada[0];
-			}
-			break;
-		case Outlaying:
-			if(strcmp(buf, "wait") == 0)
-				game.state = Waiting;
-			else if(strcmp(buf, "play") == 0)
-				game.state = Playing;
-			break;
-		case Playing:
-			if(strcmp(buf, "wait") == 0)
-				game.state = Waiting;
-			break;
-		case Waiting:
-			if(strcmp(buf, "play") == 0)
-				game.state = Playing;
-			else if(strncmp(buf, "hit", 3) == 0){
-				if(gettokens(buf+4, coords, nelem(coords), "-") == nelem(coords))
-					settile(&localboard, Pt2(strtoul(coords[0], nil, 10), strtoul(coords[1], nil, 10), 1), Thit);
-			}else if(strncmp(buf, "miss", 4) == 0){
-				if(gettokens(buf+5, coords, nelem(coords), "-") == nelem(coords))
-					settile(&localboard, Pt2(strtoul(coords[0], nil, 10), strtoul(coords[1], nil, 10), 1), Tmiss);
-			}
-			break;
+	tot = 0;
+	while((n = ioread(io, fd, buf+tot, sizeof(buf)-1-tot)) > 0){
+		tot += n;
+		buf[tot] = 0;
+		s = buf;
+		while((e = strchr(s, '\n')) != nil){
+			*e++ = 0;
+			processcmd(s);
+			tot -= e-s;
+			memmove(buf, e, tot);
+			s = e;
 		}
+		if(tot >= sizeof(buf)-1)
+			tot = 0;
 	}
 	closeioproc(io);
 	threadexitsall("connection lost");
@@ -643,8 +673,8 @@
 	game.state = Waiting0;
 
 	drawchan = chancreate(sizeof(void*), 0);
-	ingress = chancreate(sizeof(char*), 16);
-	egress = chancreate(sizeof(char*), 16);
+	ingress = chancreate(sizeof(char*), 1);
+	egress = chancreate(sizeof(char*), 1);
 	threadcreate(bobross, nil, mainstacksize);
 	threadcreate(inputthread, &in, mainstacksize);
 	threadcreate(netrecvthread, &fd, mainstacksize);
--- a/btsd.c
+++ b/btsd.c
@@ -55,15 +55,26 @@
 {
 	Chanpipe *cp;
 	Ioproc *io;
-	char buf[256];
-	int n;
+	char buf[256], *s, *e;
+	int n, tot;
 
 	cp = arg;
 	io = ioproc();
 
-	while((n = ioread(io, cp->fd, buf, sizeof(buf)-1)) > 0){
-		buf[n] = 0;
-		chanprint(cp->c, "%s", buf);
+	tot = 0;
+	while((n = ioread(io, cp->fd, buf+tot, sizeof(buf)-1-tot)) > 0){
+		tot += n;
+		buf[tot] = 0;
+		s = buf;
+		while((e = strchr(s, '\n')) != nil){
+			*e++ = 0;
+			chanprint(cp->c, "%s", s);
+			tot -= e-s;
+			memmove(buf, e, tot);
+			s = e;
+		}
+		if(tot >= sizeof(buf)-1)
+			tot = 0;
 	}
 	if(debug)
 		fprint(2, "[%d] lost connection\n", getpid());
@@ -76,17 +87,23 @@
 serveproc(void *arg)
 {
 	NetConnInfo *nci[2];
-	Player **m;
+	Match *m;
+	Player *p, *op;
 	Chanpipe cp[2];
 	Alt a[3];
-	int i, n0, tid[2];
+	int i;
+	uint n0;
 	char *s;
 
+	Point2 cell;
+	char *coords[5];
+	int j, orient;
+
 	m = arg;
 	s = nil;
 
-	nci[0] = getnetconninfo(nil, m[0]->fd);
-	nci[1] = getnetconninfo(nil, m[1]->fd);
+	nci[0] = getnetconninfo(nil, m->pl[0]->fd);
+	nci[1] = getnetconninfo(nil, m->pl[1]->fd);
 	if(nci[0] == nil || nci[1] == nil)
 		sysfatal("getnetconninfo: %r");
 	threadsetname("serveproc %s ↔ %s", nci[0]->raddr, nci[1]->raddr);
@@ -94,9 +111,9 @@
 	freenetconninfo(nci[1]);
 
 	cp[0].c = chancreate(sizeof(char*), 1);
-	cp[0].fd = m[0]->fd;
+	cp[0].fd = m->pl[0]->fd;
 	cp[1].c = chancreate(sizeof(char*), 1);
-	cp[1].fd = m[1]->fd;
+	cp[1].fd = m->pl[1]->fd;
 
 	a[0].c = cp[0].c; a[0].v = &s; a[0].op = CHANRCV;
 	a[1].c = cp[1].c; a[1].v = &s; a[1].op = CHANRCV;
@@ -103,34 +120,73 @@
 	a[2].op = CHANEND;
 
 	threadsetgrp(truerand());
-	tid[0] = threadcreate(netrecvthread, &cp[0], mainstacksize);
-	tid[1] = threadcreate(netrecvthread, &cp[1], mainstacksize);
+	threadcreate(netrecvthread, &cp[0], mainstacksize);
+	threadcreate(netrecvthread, &cp[1], mainstacksize);
 
-	assert(m[0]->state == Waiting0 && m[1]->state == Waiting0);
-	write(m[0]->fd, "layout", 6);
-	write(m[1]->fd, "layout", 6);
-	m[0]->state = Outlaying;
-	m[1]->state = Outlaying;
+	write(m->pl[0]->fd, "layout\n", 7);
+	write(m->pl[1]->fd, "layout\n", 7);
+	m->pl[0]->state = Outlaying;
+	m->pl[1]->state = Outlaying;
 
 	while((i = alt(a)) >= 0){
+		p = m->pl[i];
+		op = m->pl[i^1];
+
 		if(a[i].err != nil){
 			if(debug)
 				fprint(2, "[%d] alt: %s\n", getpid(), a[i].err);
-			write(m[i^1]->fd, "win", 3);
-			m[i^1]->state = Waiting0;
-			pushplayer(m[i^1]);
-			freeplayer(m[i]);
+			write(op->fd, "win\n", 4);
+			pushplayer(op);
+			freeplayer(p);
 			break;
 		}
 		if(debug)
 			fprint(2, "[%d] said '%s'\n", i, s);
-		if(write(m[i^1]->fd, s, strlen(s)) != strlen(s)){
-			write(m[i]->fd, "win", 3);
-			m[i]->state = Waiting0;
-			pushplayer(m[i]);
-			freeplayer(m[i^1]);
-			free(s);
+
+		switch(p->state){
+		case Outlaying:
+			if(strncmp(s, "layout", 6) == 0)
+				if(gettokens(s+7, coords, nelem(coords), ",") == nelem(coords)){
+					if(debug)
+						fprint(2, "rcvd layout from %d\n", i);
+					for(j = 0; j < nelem(coords); j++){
+						cell = coords2cell(coords[j]);
+						orient = coords[j][strlen(coords[j])-2] == 'h'? OH: OV;
+						settiles(p, cell, orient, shiplen(j), Tship);
+					}
+					p->state = Waiting;
+					if(debug)
+						fprint(2, "curstates [%d] %d / [%d] %d\n", i, p->state, i^1, op->state);
+					if(op->state == Waiting){
+						n0 = truerand();
+						if(debug)
+							fprint(2, "let the game begin: %d plays, %d waits\n", n0%2, (n0+1)%2);
+						write(m->pl[n0%2]->fd, "play\n", 5);
+						m->pl[n0%2]->state = Playing;
+						write(m->pl[(n0+1)%2]->fd, "wait\n", 5);
+					}
+				}
 			break;
+		case Playing:
+			if(strncmp(s, "shoot", 5) == 0){
+				cell = coords2cell(s+6);
+				if(gettile(op, cell) == Tship){
+					settile(op, cell, Thit);
+					write(p->fd, "hit\n", 4);
+					fprint(op->fd, "hit %s\n", cell2coords(cell));
+				}else{
+					settile(op, cell, Tmiss);
+					write(p->fd, "miss\n", 5);
+					fprint(op->fd, "miss %s\n", cell2coords(cell));
+				}
+				write(p->fd, "wait\n", 5);
+				write(op->fd, "play\n", 5);
+				p->state = Waiting;
+				op->state = Playing;
+				if(debug)
+					fprint(2, "%d waits, %d plays\n", i, i^1);
+			}
+			break;
 		}
 		free(s);
 	}
@@ -175,7 +231,7 @@
 void
 matchmaker(void *)
 {
-	Player **match;
+	Match *m;
 
 	threadsetname("matchmaker");
 
@@ -185,11 +241,15 @@
 			continue;
 		}
 
-		match = emalloc(2*sizeof(Player*));
-		match[0] = popplayer();
-		match[1] = popplayer();
+		m = emalloc(sizeof *m);
+		m->pl[0] = popplayer();
+		m->pl[1] = popplayer();
+		m->pl[0]->state = Waiting0;
+		m->pl[1]->state = Waiting0;
+		memset(m->pl[0]->map, Twater, MAPW*MAPH);
+		memset(m->pl[1]->map, Twater, MAPW*MAPH);
 
-		proccreate(serveproc, match, mainstacksize);
+		proccreate(serveproc, m, mainstacksize);
 	}
 }
 
@@ -243,6 +303,7 @@
 {
 	char *addr;
 
+	GEOMfmtinstall();
 	addr = "tcp!*!3047";
 	ARGBEGIN{
 	case 'd':
--- a/dat.h
+++ b/dat.h
@@ -33,9 +33,11 @@
 
 typedef struct Input Input;
 typedef struct Ship Ship;
+typedef struct Map Map;
 typedef struct Board Board;
 typedef struct Player Player;
 typedef struct Playerq Playerq;
+typedef struct Match Match;
 typedef struct Chanpipe Chanpipe;
 
 struct Input
@@ -54,15 +56,21 @@
 	int sunk;
 };
 
+struct Map
+{
+	char map[MAPW][MAPH];
+};
+
 struct Board
 {
 	RFrame;
-	char map[17][17];
+	Map;
 	Rectangle bbox;
 };
 
 struct Player
 {
+	Map;
 	int fd;
 	int sfd;
 	int state;
@@ -74,6 +82,11 @@
 	Player **players;
 	ulong cap;
 	ulong nplayers;
+};
+
+struct Match
+{
+	Player *pl[2];
 };
 
 struct Chanpipe
--- a/fns.h
+++ b/fns.h
@@ -12,3 +12,7 @@
  */
 char *cell2coords(Point2);
 Point2 coords2cell(char*);
+int gettile(Map*, Point2);
+void settile(Map*, Point2, int);
+void settiles(Map*, Point2, int, int, int);
+int shiplen(int);
--- a/util.c
+++ b/util.c
@@ -9,6 +9,13 @@
 #include "fns.h"
 
 static char rowtab[] = "abcdefghijklmnopq";
+static int shiplentab[] = {
+ [Scarrier]	5,
+ [Sbattleship]	4,
+ [Scruiser]	3,
+ [Ssubmarine]	3,
+ [Sdestroyer]	2,
+};
 
 
 char *
@@ -16,8 +23,8 @@
 {
 	static char s[3+1];
 
-	assert(cell.x < 17 && cell.x >= 0
-		&& cell.y < 17 && cell.y >= 0);
+	assert(cell.x >= 0 && cell.x < MAPW
+		&& cell.y >= 0 && cell.y < MAPH);
 
 	snprint(s, sizeof s, "%c%d", rowtab[(int)cell.y], (int)cell.x);
 	return s;
@@ -36,7 +43,43 @@
 	cell.y = p-rowtab;
 	cell.x = strtol(s+1, nil, 10);
 
-	assert(cell.x < 17 && cell.x >= 0);
+	assert(cell.x >= 0 && cell.x < MAPW);
 
 	return cell;
+}
+
+int
+gettile(Map *m, Point2 cell)
+{
+	return m->map[(int)cell.x][(int)cell.y];
+}
+
+void
+settile(Map *m, Point2 cell, int type)
+{
+	m->map[(int)cell.x][(int)cell.y] = type;
+}
+
+void
+settiles(Map *m, Point2 cell, int o, int ncells, int type)
+{
+	Point2 sv;
+
+	switch(o){
+	case OH: sv = Vec2(1,0); break;
+	case OV: sv = Vec2(0,1); break;
+	default: sysfatal("settiles: wrong ship orientation");
+	}
+
+	while(ncells-- > 0){
+		settile(m, cell, type);
+		cell = addpt2(cell, sv);
+	}
+}
+
+int
+shiplen(int stype)
+{
+	assert(stype >= 0 && stype < NSHIPS);
+	return shiplentab[stype];
 }