4c6582b4bdb47766e130e415b5706a9e9c3e0e31
[netris.git] / server.c
1 /*
2  * Netris -- A free networked version of T*tris
3  * Copyright (C) 1994,1995,1996  Mark H. Weaver <mhw@netris.org>
4  * 
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  * 
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  * 
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  *
19  * $Id: game.c,v 1.39 1999/05/16 06:56:27 mhw Exp $
20  */
21
22 #define NOEXT
23 #include "netris.h"
24
25 #include <stdlib.h>
26 #include <ctype.h>
27 #include <string.h>
28 #include <sys/types.h>
29 #include <netinet/in.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <netdb.h>
33 #include <errno.h>
34 #include <setjmp.h>
35
36 #include "util.h"
37
38 #define HEADER_SIZE sizeof(netint4[3])
39
40 static struct option options[] = {
41         { "wait",        0, 0, 'w' },
42         { "port",        1, 0, 'p' },
43         { "quadra",      1, 0, 'q' },
44         { "min-players", 1, 0, 'm' },
45         { "max-players", 1, 0, 'x' },
46         { "continuous",  1, 0, 'c' },
47         { "speed",       1, 0, 'i' },
48         { "seed",        1, 0, 's' },
49         { "verbose",     0, 0, 'v' },
50         { "info",        0, 0, 'H' },
51         { "help",        0, 0, 'h' },
52         { 0,             0, 0,  0  }
53 };
54
55 static char minplayers = 2;
56 static char maxplayers = 8;
57 static char playercount;
58 static char verbose = 0;
59
60 struct sockaddr_in addr;
61
62 MyEventType NetGenFunc(EventGenRec *gen, MyEvent *event);
63 static EventGenRec netGen[MAX_SCREENS] = {
64         { NULL, 0, FT_read, -1, NetGenFunc, EM_net, 0, "\0", 0, HEADER_SIZE }
65 };
66
67 static MyEventType AlarmGenFunc(EventGenRec *gen, MyEvent *event);
68 static EventGenRec alarmGen = {
69         &alarmGen, 0, FT_read, -1, AlarmGenFunc, EM_alarm
70 };
71
72 static MyEventType ConnGenFunc(EventGenRec *gen, MyEvent *event);
73 static EventGenRec connGen = {
74         NULL, 0, FT_read, -1, ConnGenFunc, EM_connect
75 };
76
77 static EventGenRec *nextGen = &alarmGen;
78
79 static sigjmp_buf close_env;
80
81
82 void SendPacketTo(short playa, short uid, NetPacketType type, int size, void *data)
83 { //send to someone
84         netint4 header[3];
85
86         if (netGen[playa].fd >= 0) {
87                 if (verbose)
88                         fprintf(stderr, ": send %d from %d to %d\n", type, uid, playa);
89                 header[0] = hton4(uid);
90                 header[1] = hton4(type);
91                 header[2] = hton4(size + HEADER_SIZE);
92                 if (MyWrite(netGen[playa].fd, header, HEADER_SIZE) != HEADER_SIZE)
93                         die("write (header)");
94                 if (size > 0 && data && MyWrite(netGen[playa].fd, data, size) != size)
95                         die("write");
96         }
97 }
98
99 static MyEventType AlarmGenFunc(EventGenRec *gen, MyEvent *event)
100 {
101         return E_alarm;
102 }
103
104 void SCloseNet(short playa)
105 { //kick some connection's ass!
106         MyEvent event;
107
108         if (netGen[playa].fd >= 0) {
109                 if (Players[playa].alive >= 0) {
110                         SendPacketTo(playa, 0, NP_endConn, 0, NULL);
111                         do {} while (WaitMyEvent(&event, EM_net) != E_lostConn);
112                 } //say bye to player
113                 close(netGen[playa].fd);
114                 netGen[playa].fd = -1;
115         }
116         if (netGen[playa].next)
117                 RemoveEventGen(&netGen[playa]);
118 }
119
120 void CloseNets(void)
121 { //nou oogjes dicht en snaveltjes toe
122         int i;
123
124         fprintf(stderr, "- Closing connections...\n");
125         for (i = 1; i < MAX_SCREENS; i++)
126                 SCloseNet(i); //bye everybuddy
127         fprintf(stderr, "* All Done\n\n");
128 }
129
130 MyEventType NetGenFunc(EventGenRec *gen, MyEvent *event)
131 { //receive
132         int result;
133         short uid, type, size;
134         netint4 *data = (netint4*)&(gen->buf);
135
136         result = MyRead(gen->fd, gen->buf + gen->bufSize,
137                 gen->bufGoal - gen->bufSize);
138         if (result < 0) {
139                 fprintf(stderr, "- Closed connection to player #%d\n", gen->player);
140                 return E_lostConn;
141         }
142         gen->bufSize += result;
143         if (gen->bufSize < gen->bufGoal)
144                 return E_none;
145         // *ugly* memcpy(data, gen->buf, sizeof(data));
146         uid = ntoh4(data[0]);
147         type = ntoh4(data[1]);
148         size = ntoh4(data[2]);
149         gen->bufGoal = size;
150         if (gen->bufSize < gen->bufGoal)
151                 return E_none;
152         gen->bufSize = 0;
153         gen->bufGoal = HEADER_SIZE;
154         event->u.net.sender = gen->player;
155         event->u.net.uid = uid;
156         event->u.net.type = type;
157         event->u.net.size = size - HEADER_SIZE;
158         event->u.net.data = gen->buf + HEADER_SIZE;
159         if (type == NP_endConn) {
160                 fprintf(stderr, "- Quit player #%d\n", gen->player);
161                 return E_lostConn;
162         } //client sent quit signal
163         return E_net;
164 }
165
166
167 static MyEventType ConnGenFunc(EventGenRec *gen, MyEvent *event)
168 {
169         int addrLen;
170         struct linger val2;
171         int new;
172
173         addrLen = sizeof(addr);
174         for (new = 1; new <= MAX_SCREENS; new++)
175                 if (netGen[new].fd < 0) break;
176         if (new > maxplayers) return;
177
178         if ((netGen[new].fd =
179                 accept(gen->fd, (struct sockaddr *)&addr, &addrLen)) < 0)
180                         die("accept");
181         fprintf(stderr, "+ Connection: %s\n", inet_ntoa(addr.sin_addr));
182         val2.l_onoff = 1;
183         val2.l_linger = 0;
184         setsockopt(netGen[new].fd, SOL_SOCKET, SO_LINGER,(void *)&val2,
185                 sizeof(val2));
186         AddEventGen(&netGen[new]);
187         netGen[new].player = event->u.net.uid = new;
188                         { //new connection; netGen already initialized in GenFunc
189                                 struct hostent *host;
190
191                                 sprintf(Players[new].host, "%s", inet_ntoa(addr.sin_addr));
192                                 if (addr.sin_family == AF_INET) {
193                                         host = gethostbyaddr((void *)&addr.sin_addr,
194                                                              sizeof(struct in_addr), AF_INET);
195                                         if (host) {
196                                                 strncpy(Players[new].host, host->h_name,
197                                                         sizeof(Players[new].host) - 1);
198                                                 Players[new].host[sizeof(Players[new].host) - 1] = 0;
199                                         } //set player's hostname
200                                 }
201                         } //E_connect
202         return E_connect;
203 }
204
205 void CountPlayers(void)
206 { //count number of players/teams
207         int i, j;
208         playercount = 0;
209         for (i = 1; i < MAX_SCREENS; i++) if (Players[i].alive > 0) {
210                 if (Players[i].team < 128) for (j = 1; j < i; j++)
211                         if (Players[j].alive > 0 && (Players[j].team == Players[i].team)) {
212                                 playercount--;
213                                 break;
214                         } //player of same team counted before
215                 playercount++;
216         } //player alive
217 }
218
219 int StartServer(void)
220 {
221         MyEvent event;
222         netint2 currentpiece[MAX_SCREENS];
223         int playersReady = 0;
224         int paused = 1;
225         int i;
226         char teams[10][7] = {
227                 "", "Green", "Cyan", "Blue", "Purple",
228                 "Red", "Grey", "White", "*Orange"
229         };
230
231         do {
232                 switch (WaitMyEvent(&event, EM_any)) {
233                         case E_lostConn: //client went away :(
234                                 Players[event.u.net.sender].alive = -1;
235                                 for (i = 1; i < MAX_SCREENS; i++)
236                                         if (Players[i].alive >= 0)
237                                                 SendPacketTo(i, event.u.net.sender,
238                                                         NP_part, sizeof(Players[0].alive),
239                                                         &Players[event.u.net.sender].alive);
240                                 SCloseNet(event.u.net.sender);
241                                 break; //NP_endConn
242                         case E_net:
243                                 if (verbose) fprintf(stderr, ": %d sent %d\n",
244                                         netGen[event.u.net.sender].fd, event.u.net.type);
245                                 switch(event.u.net.type) {
246                                         case NP_hello:
247 //                                              if (event.u.net.type != NP_hello) ByeClient(new);
248                                         {
249                                                 netint4 versiondata[2];
250                                                 char data[255];
251                                                 int major;
252                                                 int protocolVersion;
253
254                                                 memcpy(versiondata, event.u.net.data,
255                                                         sizeof(versiondata));
256                                                 major = ntoh4(versiondata[0]);
257                                                 protocolVersion = ntoh4(versiondata[1]);
258                                                 if (major != MAJOR_VERSION
259                                                  || protocolVersion != PROTOCOL_VERSION) {
260                                                         snprintf(data, sizeof(data),
261                                                                 "Version mismatch: received %d.%d",
262                                                                 major, protocolVersion);
263                                                         fprintf(stderr, "= Wrong version player #%d (%s)\n",
264                                                                 event.u.net.sender, data);
265                                                         SendPacketTo(event.u.net.sender, 0, NP_error,
266                                                                 strlen(data)+1, data);
267                                                         SCloseNet(event.u.net.sender);
268                                                 } //version mismatch
269                                                 fprintf(stderr, "* Accepted player #%d\n",
270                                                         event.u.net.sender);
271                                                 break;
272                                         } //NP_hello
273                                         case NP_newPlayer:
274                                         //receive player details and return other players
275                                                 memcpy(&Players[event.u.net.sender],
276                                                         event.u.net.data, event.u.net.size);
277                                                 if (Players[event.u.net.sender].team < 1
278                                                 || Players[event.u.net.sender].team > 7) {
279                                                         int team;
280
281                                                         for (team = 1; team < 7; team++) {
282                                                                 for (i = 1; i < MAX_SCREENS; i++)
283                                                                         if ((Players[i].alive > 0) && (Players[i].team == team))
284                                                                                 break; //team in use
285                                                                 if (i==MAX_SCREENS) break;
286                                                         } //find unused team
287                                                         Players[event.u.net.sender].team = team;
288                                                         SendPacketTo(event.u.net.sender, event.u.net.sender, NP_team,
289                                                                 sizeof(Players[event.u.net.sender].team),
290                                                                 &Players[event.u.net.sender].team);
291                                                 } //invalid team
292                                                 if (Game.started < 2)
293                                                         Players[event.u.net.sender].flags |= SCF_paused;
294                                                 if (!Game.continuous && Game.started >= 2) {
295                                                         char data[40];
296                                                         strcpy(data, "Can't join: Game has already started");
297                                                         fprintf(stderr, "- Can't join player #%d in "
298                                                                 "non-continuous game\n", event.u.net.sender);
299                                                         SendPacketTo(event.u.net.sender, 0, NP_error,
300                                                                 strlen(data)+1, data);
301 //                                                      SCloseNet(event.u.net.sender, 0);
302                                                         break;
303                                                 } //can't join started game
304                                                 {
305                                                         static struct {
306                                                                 int playerflags;
307                                                                 int gravity;    //1
308                                                                 int started;    //2
309                                                                 int continuous; //3
310                                                                 long seed;              //4
311                                                                 int initspeed;  //5
312                                                         } data;
313
314                                                         memcpy(&data, &Players[event.u.net.sender].flags,
315                                                                 sizeof(data.playerflags));
316                                                         memcpy(&data.gravity, &Game,
317                                                                 sizeof(data) - sizeof(data.playerflags));
318                                                         SendPacketTo(event.u.net.sender, 0, NP_gamedata,
319                                                                 sizeof(data), &data);
320                                                 } //send game options
321                                                 for (i = 1; i < MAX_SCREENS; i++)
322                                                         if (netGen[i].fd >= 0 && i != event.u.net.sender) {
323                                                                 SendPacketTo(event.u.net.sender, i,
324                                                                         NP_newPlayer, sizeof(_Player), &Players[i]);
325                                                                 SendPacketTo(event.u.net.sender, i, NP_newPiece,
326                                                                         sizeof(Players[i].curShape), &Players[i].curShape);
327                                                                 SendPacketTo(i, event.u.net.sender, NP_newPlayer,
328                                                                         sizeof(_Player), &Players[event.u.net.sender]);
329                                                         } //send (to) players
330                                                 fprintf(stderr, "> Joined player #%d: %s <%s> (%s)\n",
331                                                         event.u.net.sender,
332                                                         Players[event.u.net.sender].name,
333                                                         Players[event.u.net.sender].host,
334                                                         teams[Players[event.u.net.sender].team]);
335                                                 if (++playersReady >= minplayers) {
336                                                         if (Game.started > 1)
337                                                                 SendPacketTo(event.u.net.sender, 0,
338                                                                         NP_start, 0, NULL);
339 /*                                                      else {
340                                                                 fprintf(stderr, "* Starting game (%010d)\n",
341                                                                         Game.seed);
342                                                                 for (i = 1; i < MAX_SCREENS; i++)
343                                                                         SendPacketTo(i, 0, NP_start, 0, NULL);
344                                                                 Game.started++;
345                                                         } //first goahead (to all)*/
346                                                 } //give go ahead
347                                                 break; //NP_playerdata
348                                         case NP_newPiece:
349                                                 memcpy(&Players[event.u.net.sender].curShape,
350                                                         event.u.net.data, sizeof(Players[0].curShape));
351                                                 goto sendtoall;
352                                         case NP_argghhh:
353                                                 Players[event.u.net.sender].alive = 0;
354                                                 fprintf(stderr, "< Player #%d died\n",
355                                                         event.u.net.sender);
356                                                 //check for unpaused game
357                                         case NP_pause:
358                                         {
359                                                 Players[event.u.net.sender].flags ^= SCF_paused;
360                                                 paused = Game.started < 1;
361                                                 for (i = 1; i < MAX_SCREENS; i++)
362                                                         if (Players[i].alive > 0)
363                                                                 paused |= Players[i].flags & SCF_paused;
364                                                 fprintf(stderr, "* Player #%d (un)paused (pause=%d)\n",
365                                                         event.u.net.sender, paused);
366                                                 if (paused) paused = 1;
367                                                 goto sendtoall;
368                                         } //NP_pause
369                                         default: //relay data to all players
370                                         sendtoall:
371 //                                              if (event.u.net.type >= NP_pause)
372                                                 if (event.u.net.type >= NP_rotright
373                                                  && Game.started < 2)
374                                                         break;
375                                                 for (i = 1; i < MAX_SCREENS; i++)
376                                                         if (i != event.u.net.sender)
377                                                         if (event.u.net.type != NP_giveJunk ||
378                                                         Players[i].team != Players[event.u.net.sender].team)
379                                                                 SendPacketTo(i, event.u.net.sender,
380                                                                         event.u.net.type, event.u.net.size,
381                                                                         event.u.net.data);
382                                                 break; //>=NP_paused
383                                 }
384                                 break; //E_net
385                         case E_connect:
386                         { //new connection; netGen already initialized in GenFunc
387                                 char serverdata[255];
388
389                                 sprintf(serverdata, "Netris server %s", version_string);
390                                 SendPacketTo(event.u.net.uid, event.u.net.uid, NP_hello,
391                                         strlen(serverdata)+1, serverdata);
392                                 break;
393                         } //E_connect
394                 } //event
395                 CountPlayers();
396                 if (Game.started < 1) {
397                         if (playercount > 1) {
398                                 fprintf(stderr, "* Game (%010d) ready to start\n", Game.seed);
399                                 Game.started++;
400                         } //give goahead
401                 } //game not yet started
402                 else {
403                         if (playercount < 2) {
404                                 fprintf(stderr, "* Stopping game\n");
405                                 if (Game.seed) Game.seed++;
406                                 if (Game.started > 1) for (i = 1; i < MAX_SCREENS; i++)
407                                         if (Players[i].alive >= 0) {
408                                                 Players[i].alive = 1;
409                                                 Players[i].flags |= SCF_paused;
410                                                 SendPacketTo(i, 0, NP_stop,
411                                                         sizeof(Game.seed), &Game.seed);
412                                         } //transmit game stop and set players not ready
413                                 paused = 1;
414                                 Game.started = 0;
415                         } //too few players for game
416                         if (Game.started == 1 && !paused) {
417                                 Game.started++;
418                                 fprintf(stderr, "* Game starts\n");
419                                 for (i = 1; i < MAX_SCREENS; i++)
420                                         if (Players[i].alive > 0)
421                                                 SendPacketTo(i, 0, NP_start, 0, NULL);
422                         } //everybody ready to start
423                 } //game (ready to) start(ed)
424         } while (1);
425         fprintf(stderr, "* Exiting server\n");
426 }
427
428
429 void SHeader(void)
430 {
431         fprintf(stderr,
432                 "NETRIS Server %s\t(c) 2002 Shiar <shiar@shiar.org>\n\n",
433                 version_string);
434 }
435
436 void SUsage(void)
437 {
438         SHeader();
439         fprintf(stderr,
440                 "Usage: netris <options>\n"
441                 "\n"
442                 "  -h, --help\t\tPrint this usage information\n"
443                 "  -H, --info\t\tShow distribution and warranty information\n"
444                 "\n"
445                 "  -p, --port <port>\tSet port number (default is %d)\n"
446                 "\n"
447                 "  -s, --seed <seed>\tStart with given random seed\n"
448                 "  -i, --speed <sec>\tSet the initial step-down interval, in seconds\n"
449                 "  -m, --min-players <2>\tNumber of players required before starting the game\n"
450                 "  -x, --max-players <8>\tMaximum number of players allowed in the game\n"
451                 "  -c, --continuous\tDon'n quit the game\n"
452                 "\n", DEFAULT_PORT);
453 }
454
455 void WriteConf(void)
456 {
457         FILE *file_out;
458
459         file_out = fopen(CONFIG_FILE, "w");
460         if (file_out == NULL) {
461                 perror("Error writing config file");
462                 exit(1);
463         }
464
465         fprintf(file_out, "### NETRIS %s Config file ###\n\n", version_string);
466
467         fclose(file_out);
468         fprintf(stderr, "Wrote new game configuration to %s\n", CONFIG_FILE);
469 }
470
471 void HandleOption(char tag, char *value)
472 {
473         switch (tag) {
474                 case 'v':  //verbose
475                         verbose = 1;
476                         break;
477                 case 'p':  //port
478                         port = atoi(value);
479                         break;
480                 case 'c':  //min-players
481                         Game.continuous = atoi(value);
482                         break;
483                 case 'm':  //min-players
484                         minplayers = atoi(value);
485                         break;
486                 case 'x':  //max-players
487                         maxplayers = atoi(value);
488                         if (maxplayers >= MAX_SCREENS)
489                                 maxplayers = MAX_SCREENS;
490                         break;
491                 case 'q':  //quadra-style gravity
492                         Game.gravity ^= 1;
493                         break;
494                 case 'i':  //speed (of level 1)
495                         Game.initspeed = atof(value) * 1e6;
496                         break;
497                 case 's':  //seed
498                         Game.seed = atoi(value);
499                         break;
500                 case 'H':  //info
501                         SHeader();
502                         DistInfo(); exit(0);
503                 case 'h':  //help
504                         SUsage(); exit(0);
505                 default:
506                         break;
507         }
508 }
509
510 void ReadConf(char *filename)
511 {
512         FILE *file_in;
513         char buf[513];
514         int i;
515         char *ch;
516         char tag[81], value[81];
517
518         file_in = fopen(filename, "r");
519         if (file_in) {
520                 while (fgets(buf, 512, file_in) != NULL) {
521                         if ((ch = strchr(buf, '#')))
522                                 *ch = '\0'; // truncate string from # char
523                         for (i = strlen(buf)-1; i >= 0; i--)
524                                 if (buf[i] == ' '  || buf[i] == '\t'
525                                  || buf[i] == '\n' || buf[i] == 13)
526                                         buf[i] = '\0';
527                                 else break;
528
529                         sscanf(buf, "%80[^= \t] = %80[^\n]", tag, value);
530                         for (i = 0; options[i].name; i++){
531                                 if (!strcasecmp(options[i].name, tag)) {
532                                         HandleOption(options[i].val, value);
533                                         break;
534                                 }
535                         }
536                 }
537                 fclose(file_in);
538         } //read file
539         else {
540                 fprintf(stderr, "Unable to open config file %s.\n", filename);
541         } //defaults
542
543 }
544
545 void CatchInt(int sig)
546 {
547         siglongjmp(close_env, 1);
548 }
549
550 int main(int argc, char **argv)
551 {
552         char ch;
553
554         if (sigsetjmp(close_env, 1)) exit(0);
555         signal(SIGINT, CatchInt);
556         port = DEFAULT_PORT;
557         maxplayers = 8;
558         Game.initspeed = DEFAULT_INTERVAL;
559         Game.seed = time(0);
560         Game.gravity = 0;
561         {
562                 int i;
563
564                 for (i = 1; i < MAX_SCREENS; i++)
565                         Players[i].alive = -1;
566         }
567
568 //      if (getopt(argc, argv, "f:") == 'f')
569 //              ReadConf(optarg);
570 //      else
571         ReadConf(CONFIG_FILE);
572         while ((ch = getopt_long(
573                 argc, argv, "hHvqp:i:s:c:m:x:", options, NULL
574         )) != -1)
575                 HandleOption(ch, optarg);
576         if (optind < argc) {
577                 SUsage();
578                 exit(1);
579         }
580 //      WriteConf();
581
582         SHeader();
583
584         {
585                 int i;
586
587                 for (i = 1; i < MAX_SCREENS; i++)
588                         memcpy(&netGen[i], &netGen[0], sizeof(EventGenRec));
589         } //setup netGen var
590
591         AtExit(CloseNets);
592
593         {
594                 int val1;
595
596                 memset(&addr, 0, sizeof(addr));
597                 addr.sin_family = AF_INET;
598                 addr.sin_port = htons(port);
599                 addr.sin_addr.s_addr = htonl(INADDR_ANY);
600                 if ((connGen.fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
601                         die("socket");
602                 val1 = 1;
603                 setsockopt(connGen.fd, SOL_SOCKET, SO_REUSEADDR,
604                         (void *)&val1, sizeof(val1));
605                 if (bind(connGen.fd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
606                         die("bind");
607                 if (listen(connGen.fd, 1) < 0)
608                         die("listen");
609
610                 AddEventGen(&connGen);
611         } //setup listening sock
612
613         StartServer(); //server loop
614
615         return 0;
616 }
617