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\