ref: 006968656f38e2d238f1579b9d7ae6e9682dff4b
parent: 48531e4fd34f0f14b9fec0e844603d15cd3e184b
author: mkf <mkf@cloud9p.org>
date: Tue Jun 4 15:19:48 EDT 2024
srv5: import rewrote most of srv4 internals. server now expects clients to announce themselves in the form of: "%s %d %d %d\n", nick, game, side, opts where nick is player's nick, alphanumerical and shorter than 8 characters. special value 0 can be used to tell server to pick a random name on it's own. game is the game played (currently it's just glendy (with value of 0), but parrallel glendy could be used in future). side is player's side (0: trapper, 1: glenda, 2: let server pick) opts are for future options, 0 for none. lots of bugs added as features. lots of features added as bugs.
--- /dev/null
+++ b/srv5.c
@@ -1,0 +1,717 @@
+/*
+ * glendy (unix) server, 5th revision
+ * leaks memory, often, quick and well.
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <ctype.h>
+
+#include "unix.h"
+#include "engine.h"
+#include "util.h"
+#include "srv.h"
+
+int listenfd;
+int port = 1768;
+int debug = 1;
+
+char syncmsg[8];
+int sockfd[2];
+int gcount = 0;
+int ccount = 0;
+int id = 0;
+
+pthread_mutex_t game_lock;
+
+List *games;
+List *threads;
+Quene clients;
+
+static void
+error(const char *msg)
+{
+ perror(msg);
+ pthread_exit(NULL);
+}
+
+static void
+cleanup(int gid)
+{
+ Game *g;
+
+ dprint("cleanup(%d)\n", gid);
+
+ g = (Game*)lookup(games, gid);
+ close(g->sockfd[0]);
+ close(g->sockfd[1]);
+}
+
+static void
+printclients(char *fmt, ...)
+{
+ int i;
+ /* it seems arg gets changed during the first call to vprint, thus we need two */
+ va_list arg, arg2;
+
+ va_start(arg, fmt);
+ va_start(arg2, fmt);
+
+ vfprint(sockfd[0], fmt, arg);
+ vfprint(sockfd[1], fmt, arg2);
+ va_end(arg);
+ va_end(arg2);
+}
+
+int
+setuplistener(int portno)
+{
+ int sockfd, option = 1;
+ struct sockaddr_in serv_addr;
+
+ sockfd = socket(AF_INET, SOCK_STREAM, 0);
+ if (sockfd < 0)
+ error("ERROR opening listener socket.");
+
+ memset(&serv_addr, 0, sizeof(serv_addr));
+ serv_addr.sin_family = AF_INET;
+ serv_addr.sin_addr.s_addr = INADDR_ANY;
+ serv_addr.sin_port = htons(portno);
+ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
+
+ if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
+ error("ERROR binding listener socket.");
+
+ dprint("listener on\n");
+
+ return sockfd;
+}
+
+void
+drawlevel(void)
+{
+ /* prints first row, this assumes SzX = SzY, is there a better way? */
+ print("T%2d|", turn);
+ for(int i = 0 ; i < SzX ; i++)
+ print("%2d |", i);
+
+ dprint(" G%2d\n", id);
+
+ for(int x = 0; x < SzX; x++)
+ {
+ for(int i = 0 ; i < SzY+1 ; i++)
+ print("----");
+ print(x % 2 ? "\\" : "/");
+ print("\n");
+
+ /* show column number and have a zig-zag effect */
+ print("%2d %s|", x, x % 2 ? " " : "");
+
+ for(int y = 0; y < SzY; y++)
+ {
+ /* it's [y][x], not [x][y] */
+ switch(grid[y][x])
+ {
+ case Wall:
+ print(" * |");
+ break;
+ case Glenda:
+ print(" g |");
+ break;
+ default:
+ print(" |");
+ break;
+ }
+ }
+ print("\n");
+ }
+}
+
+void
+sendlevel(void)
+{
+ if(state == Start)
+ {
+ printclients("INIT\n");
+
+ for(int x = 0; x < SzX; x++)
+ {
+ for(int y = 0; y < SzY; y++)
+ {
+ switch(grid[x][y])
+ {
+ case Wall:
+ printclients("w %d %d\n", x, y);
+ break;
+ case Glenda:
+ printclients("g %d %d\n", x, y);
+ break;
+ }
+ }
+ }
+ printclients("SENT\n");
+ }
+ else if(state == Playing)
+ printclients("SYNC %d %s\n", turn, syncmsg);
+ else
+ {
+ printclients("SYNC %d %s\n", turn, syncmsg);
+ if(state == Won)
+ {
+ dprint("hah, trapper won\n");
+ fprint(sockfd[0], "WON\n");
+ fprint(sockfd[1], "LOST\n");
+ }
+ else if(state == Lost)
+ {
+ dprint("welp, trapper lost\n");
+ fprint(sockfd[0], "LOST\n");
+ fprint(sockfd[1], "WON\n");
+ }
+ cleanup(id);
+ return;
+ }
+ dprint("Game %d, Turn is %d, min = %d\n", id, turn, findmin(findglenda()));
+ fprint(playersock, "TURN\n");
+ fprint(sockfd[!(turn % 2)], "WAIT\n");
+}
+
+/* p x y */
+void
+proc_put(char *s)
+{
+ char *xpos, *ypos;
+ unsigned int x, y, r;
+
+ xpos = strtok(s, " ");
+ ypos = strtok(nil, " ");
+
+ if(xpos == nil || ypos == nil)
+ {
+ fprint(playersock, "ERR INVALIDINPUT proc_put():"
+ "not enough arguments or malformed string\n");
+ return;
+ }
+
+ if(!isnum(xpos, strlen(xpos)) || !isnum(ypos, strlen(ypos)))
+ {
+ fprint(playersock, "ERR invalidinput proc_put():"
+ "expected string in %s and %s\n", xpos, ypos);
+ return;
+ }
+
+ /* engine assumes it's XY, protocol assumes it's YX */
+ x = atoi(xpos);
+ y = atoi(ypos);
+
+ dprint("put %d %d\n", x, y);
+
+ if(x >= SzX || x < 0 || y >= SzY || y < 0)
+ {
+ fprint(playersock, "ERR invalidinput proc_put(): %d %d\n", x, y);
+ return;
+ }
+
+ r = doput(Pt(x, y));
+ if(r == Wall)
+ fprint(playersock, "WALL %d %d\n", x, y);
+
+ else if(r == Glenda)
+ fprint(playersock, "GLND %d %d\n", x, y);
+
+ else
+ {
+ sprint(syncmsg, "%u %u", x, y);
+ syncmsg[7] = '\0';
+ dprint("syncmsg = %s\n", syncmsg);
+ }
+}
+
+/* m x y */
+void
+proc_move(char *s)
+{
+ int d;
+ Point p;
+
+ p = findglenda();
+ d = strtodir(s);
+
+ if(d == Err)
+ fprint(playersock, "ERR invalidinput proc_move(): %s\n", s);
+
+ else if(domove(d) == Wall)
+ fprint(playersock, "WALL %s %d %d\n", s, p.x, p.y);
+
+ else
+ {
+ strncpy(syncmsg, s, 7);
+ /* better be safe than sorry */
+ syncmsg[7] = '\0';
+ dprint("syncmsg = %s\n", syncmsg);
+ }
+}
+
+/*
+ * handle input, which is in the form of:
+ * trapper> p xx yy
+ * puts a wall in xx yy
+ * glenda> m {NE, E, SE, SW, W, NW}
+ * moves the bunny
+ * > q
+ * quits the game
+ */
+int
+proc(int player, char *s)
+{
+ char *t;
+ int oturn, n;
+
+ /* early return paths */
+ if(*s == '\0' || *s == 'q')
+ {
+ fprint(sockfd[player], "DIE disconnected\n");
+ fprint(sockfd[!player], "DIE other client have been disconnected\n");
+
+ /* mmhm... what happens if we close a fd we are reading from? */
+ cleanup(id);
+ return Err;
+ }
+ else if(turn % 2 != player)
+ {
+ fprint(sockfd[player], "ERR WAIT\n");
+ return Ok;
+ }
+
+ oturn = turn;
+ /* s+2 skips command and first space after it */
+ switch(*s)
+ {
+ case 'p':
+ if(turn % 2 == 0)
+ proc_put(s+2);
+ else if(turn % 2 == 1)
+ fprint(playersock, "CANTP\n");
+ break;
+ case 'm':
+ if(turn % 2 == 0)
+ fprint(playersock, "CANTM\n");
+ else if(turn % 2 == 1)
+ proc_move(s+2);
+ break;
+ default:
+ fprint(playersock, "ERR proc() unkown command %c\n", *s);
+ }
+ /* only print the map if turn have changed */
+ if(turn != oturn)
+ {
+ if(debug)
+ drawlevel();
+ sendlevel();
+ }
+ return Ok;
+}
+
+static char*
+input(int gid, int player)
+{
+ char *s, c;
+ int n = 0;
+ Game *g;
+
+ /* sang bozorg */
+ s = emalloc(1024);
+ memset(s, 0, 1024);
+
+ g = (Game*)lookup(games, gid);
+ /* we could use local variables, but that not worth the trouble */
+ while((c = read(g->sockfd[player], s+n, 1) == 1) && n < 1024)
+ {
+ if(s[n] == '\n' || s[n] == '\0')
+ {
+ s[n] = '\0';
+ break;
+ }
+ n++;
+ }
+ if(!strcmp(s, ""))
+ dprint("input(%d, %d): got input: %x, %s\n", gid, player, *s, s);
+
+ return s;
+}
+
+/*
+ * most of game engine's works on global variables
+ * set the global variables with one game from the array
+ */
+static void
+loadgame(int gid)
+{
+ Game *g;
+// if(n > sizeof(games) / sizeof(Game))
+// sysfatal("loadgame(%d): invalid game", n);
+
+ dprint("loadgame(%d)\n", gid);
+ g = (Game*)lookup(games, gid);
+
+ id = g->id;
+ difficulty = g->difficulty;
+ state = g->state;
+ turn = g->turn;
+
+ strncpy(syncmsg, g->syncmsg, 8);
+
+ sockfd[0] = g->sockfd[0];
+ sockfd[1] = g->sockfd[1];
+
+ memcpy(grid, g->grid, sizeof(grid));
+}
+
+static void
+setgame(int gid)
+{
+ Game *g;
+// if(n > lllen(games))
+// sysfatal("setgame(%d): invalid game", n);
+
+ dprint("loadgame(%d)\n", gid);
+ g = (Game*)lookup(games, gid);
+
+ /* id got to change when we set game */
+ id = gid;
+ g->id = gid;
+ g->difficulty = difficulty;
+ g->state = state;
+ g->turn = turn;
+
+ g->sockfd[0] = sockfd[0];
+ g->sockfd[1] = sockfd[1];
+
+ memcpy(g->grid, grid, sizeof(grid));
+}
+
+/* player is either 0 or 1, trapper or glenda */
+static void
+clienthandler(void *data)
+{
+ int res, gid, player;
+ char *s;
+ Game *g;
+
+ gid = ((int*)data)[0];
+ player = ((int*)data)[1];
+
+ dprint("clienthandler({%d, %d})\n", gid, player);
+
+ g = (Game*)lookup(games, gid);
+
+ for(;;)
+ {
+ /* most of time is spent here */
+ s = input(gid, player);
+
+
+ if(!strcmp(s, ""))
+ {
+ cleanup(gid);
+ break;
+ }
+
+ pthread_mutex_lock(&game_lock);
+
+ loadgame(gid);
+ res = proc(player, s);
+ setgame(gid);
+
+ pthread_mutex_unlock(&game_lock);
+ free(s);
+ }
+}
+static void
+play(Client *c1, Client *c2)
+{
+ int res[2];
+ int tdata[2][2];
+ Game *g;
+
+ pthread_mutex_lock(&game_lock);
+ gcount++;
+
+ sockfd[0] = c1->fd;
+ sockfd[1] = c2->fd;
+
+ initlevel();
+ g = (Game*)emalloc(sizeof(Game));
+ llappend(games, g);
+ setgame(gcount);
+
+ if(debug)
+ drawlevel();
+
+ fprint(g->sockfd[0], "CONN %d\n", 0);
+ fprint(g->sockfd[1], "CONN %d\n", 1);
+
+ sendlevel();
+
+ tdata[0][0] = gcount;
+ tdata[0][1] = 0;
+
+ tdata[1][0] = gcount;
+ tdata[1][1] = 1;
+
+ c1->thread = (pthread_t*)emalloc(sizeof(pthread_t));
+ c2->thread = (pthread_t*)emalloc(sizeof(pthread_t));
+
+ llappend(threads, c1->thread);
+ llappend(threads, c2->thread);
+
+ pthread_mutex_unlock(&game_lock);
+
+
+
+ res[0] = pthread_create(c1->thread, NULL, (void*)clienthandler, (void*)tdata[0]);
+ res[1] = pthread_create(c2->thread, NULL, (void*)clienthandler, (void*)tdata[1]);
+
+ dprint("play() tdata[1] {%d, %d}\n", tdata[1][0], tdata[1][1]);
+ dprint("play() tdata[0] {%d, %d}\n", tdata[0][0], tdata[0][1]);
+
+ if(res[0] || res[1])
+ sysfatal("pthread_create() failed: %d\n", res[0] ? res[0] : res[1]);
+ else
+ dprint("threads for game %d are created\n", gcount);
+}
+
+char*
+parsenick(int fd, char *nick)
+{
+ char *s;
+ int len = strlen(nick);
+
+ if(len > 8)
+ {
+ fprint(fd, "DIE nick too long: %d\n", len);
+ goto die;
+ }
+
+ for(int i = 0 ; len > i ; i++)
+ {
+ if(!isalnum(nick[i]))
+ {
+ fprint(fd, "DIE nicks must be only alpha numeric strings\n");
+ goto die;
+ }
+ }
+
+ s = strdup(nick);
+ return s;
+
+ die:
+ close(fd);
+ return nil;
+}
+
+int
+parsegame(int fd, char *game)
+{
+ int g;
+
+ g = atoi(game);
+ if(g == 0)
+ return g;
+
+ die:
+ fprint(fd, "DIE invalid game: %d", g);
+ close(fd);
+ return -1;
+}
+
+
+int
+parseside(int fd, char *side)
+{
+ int s;
+
+ s = atoi(side);
+ if(s == 0 || s == 1)
+ return s;
+
+ die:
+ fprint(fd, "DIE invalid side: %d", s);
+ close(fd);
+ return -1;
+}
+
+int
+parseopts(int fd, char *opts)
+{
+ return atoi(opts);
+}
+
+
+Client*
+newclient(char *in, int fd)
+{
+ Client *c;
+ char *nick, *side, *game, *opts;
+
+ nick = strtok(in, " ");
+ game = strtok(nil, " ");
+ side = strtok(nil, " ");
+ opts = strtok(nil, " ");
+
+ dprint("newclient(%d): nick: %s, side: %s, game: %s, opts: %s\n", fd, nick, side, game, opts);
+
+ if(nick == nil || side == nil || game == nil)
+ return nil;
+
+ c = (Client*)emalloc(sizeof(Client));
+ c->fd = fd;
+ c->nick = parsenick(fd, nick);
+ c->game = parsegame(fd, game);
+ c->side = parseside(fd, side);
+ c->opts = parseopts(fd, opts);
+
+ if(c->nick == nil || c->game == -1 || c->side == -1)
+ {
+ free(c);
+ close(fd);
+ return nil;
+ }
+
+ ccount++;
+ return c;
+}
+
+void
+makematch(Client *c)
+{
+ Client *head = clients.l->data;
+
+ if(head == nil || c->side == head->side)
+ {
+ if(c->side == PRandom)
+ c->side = nrand(1) ? PTrapper : PGlenda;
+
+ qadd(&clients, c);
+ fprint(c->fd, "WAIT\n");
+ }
+ else
+ {
+ if(c->side == PRandom)
+ c->side = (head->side == PGlenda) ? PTrapper : PGlenda;
+
+ qnext(&clients);
+ play(c, head);
+ }
+}
+
+void
+registerclient(void *clientfd)
+{
+ char c, *s;
+ Client *cl;
+ int n = 0;
+ int fd = *(int*)clientfd;
+
+ dprint("registerclient(%d)\n", fd);
+
+ /* sang bozorg */
+ s = emalloc(1024);
+ memset(s, 0, 1024);
+
+
+ /* we could use local variables, but that not worth the trouble */
+ while((c = read(fd, s+n, 1) == 1) && n < 1024)
+ {
+ /* silently drop CR from CRLF */
+ if(s[n] == '\r')
+ s[n] = '\0';
+ else if(s[n] == '\n' || s[n] == '\0')
+ {
+ s[n] = '\0';
+ break;
+ }
+ n++;
+ }
+
+ if(!strcmp(s, ""))
+ {
+ dprint("registerclient(%d): got empty string\n", fd);
+ fprint(fd, "DIE empty string\n");
+ close(fd);
+ goto die;
+ }
+
+ dprint("registerclient(%d): got input: %x, %s\n", fd, *s, s);
+ cl = newclient(s, fd);
+
+ if(cl != nil)
+ makematch(cl);
+
+ die:
+ free(s);
+}
+
+/*
+static pthread_t*
+newthread(void)
+{
+ return (pthread_t*)emalloc(sizeof(pthread_t));
+}
+*/
+
+void
+srv(int listenfd)
+{
+ int *clientfd;
+ socklen_t clilen;
+ struct sockaddr_in servaddr, clientaddr;
+ pthread_t t;
+
+ for(;;)
+ {
+ listen(listenfd, 64);
+ memset(&clientaddr, 0, sizeof(clientaddr));
+
+ clilen = sizeof(clientaddr);
+ clientfd = (int*)emalloc(sizeof(int));
+ *clientfd = accept(listenfd, (struct sockaddr *) &clientaddr, &clilen);
+
+ if(*clientfd < 0)
+ error("srv(): failed to accept connection");
+
+ dprint("game %d: srv(): client %d connected, fd: %d\n", gcount, ccount, *clientfd);
+ pthread_create(&t, NULL, (void*)registerclient, (void*)clientfd);
+ }
+}
+
+int
+main(int argc, char **argv)
+{
+ /* it might not be a real human */
+ ptype[0] = Human;
+ ptype[1] = Human;
+
+ listenfd = setuplistener(port);
+ pthread_mutex_init(&game_lock, NULL);
+
+ /* OpenBSD ignores this */
+ srand(time(nil));
+
+ games = llnew();
+ clients.l = llnew();
+ threads = llnew();
+ srv(listenfd);
+
+ close(listenfd);
+ pthread_mutex_destroy(&game_lock);
+ pthread_exit(NULL);
+ return 0;
+}