From bc5ac3d46558512971ba8c0f6be02382d91d17f1 Mon Sep 17 00:00:00 2001 From: rodri Date: Fri, 6 Oct 2023 16:31:57 +0000 Subject: 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). --- andy.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ bts.c | 9 +++- btsd.c | 166 ++++++++++++++++++++++++++++++++++++++++++++++++---------------- dat.h | 28 +++++++++++ fns.h | 6 +++ mkfile | 1 + 6 files changed, 336 insertions(+), 42 deletions(-) create mode 100644 andy.c diff --git a/andy.c b/andy.c new file mode 100644 index 0000000..36bd1f5 --- /dev/null +++ b/andy.c @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} diff --git a/bts.c b/bts.c index 767dd50..f446c2f 100644 --- a/bts.c +++ b/bts.c @@ -151,6 +151,7 @@ MatchInfo match; /* of which we are an spectator */ struct { int state; + int mode; } game; struct { Image *c; /* color */ @@ -639,6 +640,7 @@ lmb(Mousectl *mc) 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 @@ key(Rune r) 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 @@ netsendthread(void *arg) void usage(void) { - fprint(2, "usage: %s [-d] addr\n", argv0); + fprint(2, "usage: %s [-da] addr\n", argv0); threadexitsall("usage"); } @@ -1059,6 +1061,9 @@ threadmain(int argc, char *argv[]) case 'd': debug++; break; + case 'a': + game.mode = GMPvAI; + break; default: usage(); }ARGEND if(argc != 1) diff --git a/btsd.c b/btsd.c index 60b222b..b7f6016 100644 --- a/btsd.c +++ b/btsd.c @@ -19,7 +19,7 @@ enum { }; Cmdtab clcmd[] = { CMid, "id", 2, - CMplay, "play", 1, + CMplay, "play", 2, CMlayout, "layout", 2, CMshoot, "shoot", 2, CMgetmatches, "watch", 1, @@ -249,27 +249,27 @@ netsendthread(void *arg) void playerproc(void *arg) { - Player *my; + Player *p; Match *m; Cmdbuf *cb; Cmdtab *ct; 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 @@ playerproc(void *arg) 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 @@ Nocmd: 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 @@ End: } 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 @@ battleproc(void *arg) 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 @@ battleproc(void *arg) 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 @@ battleproc(void *arg) 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 @@ Swapturn: 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 @@ matchmaker(void *) { Msg *msg; Match *m; - Player *pl[2]; + Player *pl[2], *bot; int i; threadsetname("matchmaker"); @@ -543,16 +612,33 @@ matchmaker(void *) 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); diff --git a/dat.h b/dat.h index 46a3c12..07b7dd3 100644 --- a/dat.h +++ b/dat.h @@ -18,6 +18,9 @@ enum { OH, /* horizontal */ OV, /* vertical */ + GMPvP = 0, + GMPvAI, + Waiting0 = 0, Watching, Ready, @@ -25,6 +28,10 @@ enum { Waiting, Playing, + ASearching = 0, + ACalibrating, + ABombing, + Boardmargin = 50, TW = 16, TH = TW, @@ -55,6 +62,7 @@ typedef struct Map Map; 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,12 +102,32 @@ struct Player 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 { RWLock; diff --git a/fns.h b/fns.h index eb9c5ba..97667fb 100644 --- a/fns.h +++ b/fns.h @@ -38,3 +38,9 @@ void delmenulist(Menulist*); */ Cmdbuf *parsecmd(char*, int); Cmdtab *lookupcmd(Cmdbuf*, Cmdtab*, int); + +/* + * andy + */ +Andy *newandy(Player*); +void freeandy(Andy*); diff --git a/mkfile b/mkfile index 22f6400..689948b 100644 --- a/mkfile +++ b/mkfile @@ -10,6 +10,7 @@ OFILES=\ alloc.$O\ parse.$O\ util.$O\ + andy.$O\ menulist.$O\ mixer.$O\ -- cgit v1.2.3