Initial revision
[tetrinet.git] / tetrinet.c
blobdaba8f1c652947e496fb9cd40bf8c908be929c17
1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
4 * Tetrinet main program.
5 */
7 /*************************************************************************/
9 #include <stdio.h>
10 #include <string.h>
11 #include <errno.h>
12 #include "tetrinet.h"
13 #include "io.h"
14 #include "server.h"
15 #include "sockets.h"
16 #include "tetris.h"
18 /*************************************************************************/
20 int fancy = 0; /* Fancy TTY graphics? */
21 int log = 0; /* Log network traffic to file? */
22 char *logname; /* Log filename */
23 int windows_mode = 0; /* Try to be just like the Windows version? */
24 int noslide = 0; /* Disallow piece sliding? */
26 int my_playernum = -1; /* What player number are we? */
27 WinInfo winlist[MAXWINLIST]; /* Winners' list from server */
28 int server_sock; /* Socket for server communication */
29 int dispmode; /* Current display mode */
30 char *players[6]; /* Player names (NULL for no such player) */
31 char *teams[6]; /* Team names (NULL for not on a team) */
32 int playing_game; /* Are we currently playing a game? */
33 int not_playing_game; /* Are we currently watching people play a game? */
34 int game_paused; /* Is the game currently paused? */
36 Interface *io; /* Input/output routines */
38 /*************************************************************************/
39 /*************************************************************************/
41 #ifndef SERVER_ONLY
43 /*************************************************************************/
45 /* Parse a line from the server. Destroys the buffer it's given as a side
46 * effect.
49 void parse(char *buf)
51 char *cmd, *s, *t;
53 cmd = strtok(buf, " ");
55 if (!cmd) {
56 return;
58 } else if (strcmp(cmd, "noconnecting") == 0) {
59 s = strtok(NULL, "");
60 if (!s)
61 s = "Unknown";
62 /* XXX not to stderr, please! -- we need to stay running w/o server */
63 fprintf(stderr, "Server error: %s\n", s);
64 exit(1);
66 } else if (strcmp(cmd, "winlist") == 0) {
67 int i = 0;
69 while (i < MAXWINLIST && (s = strtok(NULL, " "))) {
70 t = strchr(s, ';');
71 if (!t)
72 break;
73 *t++ = 0;
74 if (*s == 't')
75 winlist[i].team = 1;
76 else
77 winlist[i].team = 0;
78 s++;
79 strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1);
80 winlist[i].name[sizeof(winlist[i].name)] = 0;
81 winlist[i].points = atoi(t);
82 if ((t = strchr(t, ';')) != NULL)
83 winlist[i].games = atoi(t+1);
84 i++;
86 if (i < MAXWINLIST)
87 winlist[i].name[0] = 0;
88 if (dispmode == MODE_WINLIST)
89 io->setup_winlist();
91 } else if (strcmp(cmd, "playernum") == 0) {
92 if (s = strtok(NULL, " "))
93 my_playernum = atoi(s);
94 /* Note: players[my_playernum-1] is set in init() */
96 } else if (strcmp(cmd, "playerjoin") == 0) {
97 int player;
98 char buf[1024];
100 s = strtok(NULL, " ");
101 t = strtok(NULL, "");
102 if (!s || !t)
103 return;
104 player = atoi(s)-1;
105 if (player < 0 || player > 5)
106 return;
107 players[player] = strdup(t);
108 if (teams[player]) {
109 free(teams[player]);
110 teams[player] = NULL;
112 snprintf(buf, sizeof(buf), "*** %s is Now Playing", t);
113 io->draw_text(BUFFER_PLINE, buf);
114 if (dispmode == MODE_FIELDS)
115 io->setup_fields();
117 } else if (strcmp(cmd, "playerleave") == 0) {
118 int player;
119 char buf[1024];
121 s = strtok(NULL, " ");
122 if (!s)
123 return;
124 player = atoi(s)-1;
125 if (player < 0 || player > 5 || !players[player])
126 return;
127 snprintf(buf, sizeof(buf), "*** %s has Left", players[player]);
128 io->draw_text(BUFFER_PLINE, buf);
129 free(players[player]);
130 players[player] = NULL;
131 if (dispmode == MODE_FIELDS)
132 io->setup_fields();
134 } else if (strcmp(cmd, "team") == 0) {
135 int player;
136 char buf[1024];
138 s = strtok(NULL, " ");
139 t = strtok(NULL, "");
140 if (!s)
141 return;
142 player = atoi(s)-1;
143 if (player < 0 || player > 5 || !players[player])
144 return;
145 if (teams[player])
146 free(teams[player]);
147 if (t)
148 teams[player] = strdup(t);
149 else
150 teams[player] = NULL;
151 if (t)
152 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[player], t);
153 else
154 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[player]);
155 io->draw_text(BUFFER_PLINE, buf);
157 } else if (strcmp(cmd, "pline") == 0) {
158 int playernum;
159 char buf[1024], *name;
161 s = strtok(NULL, " ");
162 t = strtok(NULL, "");
163 if (!s)
164 return;
165 if (!t)
166 t = "";
167 playernum = atoi(s)-1;
168 if (playernum == -1) {
169 name = "Server";
170 } else {
171 if (playernum < 0 || playernum > 5 || !players[playernum])
172 return;
173 name = players[playernum];
175 snprintf(buf, sizeof(buf), "<%s> %s", name, t);
176 io->draw_text(BUFFER_PLINE, buf);
178 } else if (strcmp(cmd, "plineact") == 0) {
179 int playernum;
180 char buf[1024], *name;
182 s = strtok(NULL, " ");
183 t = strtok(NULL, "");
184 if (!s)
185 return;
186 if (!t)
187 t = "";
188 playernum = atoi(s)-1;
189 if (playernum == -1) {
190 name = "Server";
191 } else {
192 if (playernum < 0 || playernum > 5 || !players[playernum])
193 return;
194 name = players[playernum];
196 snprintf(buf, sizeof(buf), "* %s %s", name, t);
197 io->draw_text(BUFFER_PLINE, buf);
199 } else if (strcmp(cmd, "newgame") == 0) {
200 int i;
202 if (s = strtok(NULL, " "))
203 /* stack height */;
204 if (s = strtok(NULL, " "))
205 initial_level = atoi(s);
206 if (s = strtok(NULL, " "))
207 lines_per_level = atoi(s);
208 if (s = strtok(NULL, " "))
209 level_inc = atoi(s);
210 if (s = strtok(NULL, " "))
211 special_lines = atoi(s);
212 if (s = strtok(NULL, " "))
213 special_count = atoi(s);
214 if (s = strtok(NULL, " ")) {
215 special_capacity = atoi(s);
216 if (special_capacity > MAX_SPECIALS)
217 special_capacity = MAX_SPECIALS;
219 if (s = strtok(NULL, " ")) {
220 memset(piecefreq, 0, sizeof(piecefreq));
221 while (*s) {
222 i = *s - '1';
223 if (i >= 0 && i < 7)
224 piecefreq[i]++;
225 s++;
228 if (s = strtok(NULL, " ")) {
229 memset(specialfreq, 0, sizeof(specialfreq));
230 while (*s) {
231 i = *s - '1';
232 if (i >= 0 && i < 9)
233 specialfreq[i]++;
234 s++;
237 if (s = strtok(NULL, " "))
238 level_average = atoi(s);
239 if (s = strtok(NULL, " "))
240 old_mode = atoi(s);
241 lines = 0;
242 for (i = 0; i < 6; i++)
243 levels[i] = initial_level;
244 memset(&fields[my_playernum-1], 0, sizeof(Field));
245 specials[0] = -1;
246 io->clear_text(BUFFER_GMSG);
247 io->clear_text(BUFFER_ATTDEF);
248 new_game();
249 playing_game = 1;
250 game_paused = 0;
251 io->draw_text(BUFFER_PLINE, "*** The Game Has Started");
253 } else if (strcmp(cmd, "ingame") == 0) {
254 /* Sent when a player connects in the middle of a game */
255 int x, y;
256 char buf[1024], *s;
258 s = buf + sprintf(buf, "f %d ", my_playernum);
259 for (y = 0; y < FIELD_HEIGHT; y++) {
260 for (x = 0; x < FIELD_WIDTH; x++) {
261 fields[my_playernum-1][y][x] = rand()%5 + 1;
262 *s++ = '0' + fields[my_playernum-1][y][x];
265 *s = 0;
266 sputs(buf, server_sock);
267 playing_game = 0;
268 not_playing_game = 1;
270 } else if (strcmp(cmd, "pause") == 0) {
271 if (s = strtok(NULL, " "))
272 game_paused = atoi(s);
273 if (game_paused) {
274 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Paused");
275 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Paused");
276 } else {
277 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Unpaused");
278 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Unpaused");
281 } else if (strcmp(cmd, "endgame") == 0) {
282 playing_game = 0;
283 not_playing_game = 0;
284 memset(fields, 0, sizeof(fields));
285 specials[0] = -1;
286 io->clear_text(BUFFER_ATTDEF);
287 io->draw_text(BUFFER_PLINE, "*** The Game Has Ended");
288 if (dispmode == MODE_FIELDS) {
289 int i;
290 io->draw_own_field();
291 for (i = 1; i <= 6; i++) {
292 if (i != my_playernum)
293 io->draw_other_field(i);
297 } else if (strcmp(cmd, "playerwon") == 0) {
298 /* Syntax: playerwon # -- sent when all but one player lose */
300 } else if (strcmp(cmd, "playerlost") == 0) {
301 /* Syntax: playerlost # -- sent after playerleave on disconnect
302 * during a game, or when a player loses (sent by the losing
303 * player and from the server to all other players */
305 } else if (strcmp(cmd, "f") == 0) { /* field */
306 int player, x, y, tile;
308 /* This looks confusing, but what it means is, ignore this message
309 * if a game isn't going on. */
310 if (!playing_game && !not_playing_game)
311 return;
312 if (!(s = strtok(NULL, " ")))
313 return;
314 player = atoi(s);
315 player--;
316 if (!(s = strtok(NULL, "")))
317 return;
318 if (*s >= '0') {
319 /* Set field directly */
320 char *ptr = (char *) fields[player];
321 while (*s) {
322 if (*s <= '5')
323 *ptr++ = (*s++) - '0';
324 else switch (*s++) {
325 case 'a': *ptr++ = 6 + SPECIAL_A; break;
326 case 'b': *ptr++ = 6 + SPECIAL_B; break;
327 case 'c': *ptr++ = 6 + SPECIAL_C; break;
328 case 'g': *ptr++ = 6 + SPECIAL_G; break;
329 case 'n': *ptr++ = 6 + SPECIAL_N; break;
330 case 'o': *ptr++ = 6 + SPECIAL_O; break;
331 case 'q': *ptr++ = 6 + SPECIAL_Q; break;
332 case 'r': *ptr++ = 6 + SPECIAL_R; break;
333 case 's': *ptr++ = 6 + SPECIAL_S; break;
336 } else {
337 /* Set specific locations on field */
338 tile = 0;
339 while (*s) {
340 if (*s < '0') {
341 tile = *s - '!';
342 } else {
343 x = *s - '3';
344 y = (*++s) - '3';
345 fields[player][y][x] = tile;
347 s++;
350 if (player == my_playernum-1)
351 io->draw_own_field();
352 else
353 io->draw_other_field(player+1);
354 } else if (strcmp(cmd, "lvl") == 0) {
355 int player;
357 if (!(s = strtok(NULL, " ")))
358 return;
359 player = atoi(s)-1;
360 if (!(s = strtok(NULL, "")))
361 return;
362 levels[player] = atoi(s);
364 } else if (strcmp(cmd, "sb") == 0) {
365 int from, to;
366 char *type;
368 if (!(s = strtok(NULL, " ")))
369 return;
370 to = atoi(s);
371 if (!(type = strtok(NULL, " ")))
372 return;
373 if (!(s = strtok(NULL, " ")))
374 return;
375 from = atoi(s);
376 do_special(type, from, to);
378 } else if (strcmp(cmd, "gmsg") == 0) {
379 if (!(s = strtok(NULL, "")))
380 return;
381 io->draw_text(BUFFER_GMSG, s);
386 /*************************************************************************/
387 /*************************************************************************/
389 static char partyline_buffer[512];
390 static int partyline_pos = 0;
392 #define curpos (partyline_buffer+partyline_pos)
394 /*************************************************************************/
396 /* Add a character to the partyline buffer. */
398 void partyline_input(int c)
400 if (partyline_pos < sizeof(partyline_buffer) - 1) {
401 memmove(curpos+1, curpos, strlen(curpos)+1);
402 partyline_buffer[partyline_pos++] = c;
403 io->draw_partyline_input(partyline_buffer, partyline_pos);
407 /*************************************************************************/
409 /* Delete the current character from the partyline buffer. */
411 void partyline_delete(void)
413 if (partyline_buffer[partyline_pos]) {
414 memmove(curpos, curpos+1, strlen(curpos)-1+1);
415 io->draw_partyline_input(partyline_buffer, partyline_pos);
419 /*************************************************************************/
421 /* Backspace a character from the partyline buffer. */
423 void partyline_backspace(void)
425 if (partyline_pos > 0) {
426 partyline_pos--;
427 partyline_delete();
431 /*************************************************************************/
433 /* Kill the entire partyline input buffer. */
435 void partyline_kill(void)
437 partyline_pos = 0;
438 *partyline_buffer = 0;
439 io->draw_partyline_input(partyline_buffer, partyline_pos);
442 /*************************************************************************/
444 /* Move around the input buffer. Sign indicates direction; absolute value
445 * of 1 means one character, 2 means the whole line.
448 void partyline_move(int how)
450 if (how == -2) {
451 partyline_pos = 0;
452 io->draw_partyline_input(partyline_buffer, partyline_pos);
453 } else if (how == -1 && partyline_pos > 0) {
454 partyline_pos--;
455 io->draw_partyline_input(partyline_buffer, partyline_pos);
456 } else if (how == 1 && partyline_buffer[partyline_pos]) {
457 partyline_pos++;
458 io->draw_partyline_input(partyline_buffer, partyline_pos);
459 } else if (how == 2) {
460 partyline_pos = strlen(partyline_buffer);
461 io->draw_partyline_input(partyline_buffer, partyline_pos);
465 /*************************************************************************/
467 /* Send the input line to the server. */
469 void partyline_enter(void)
471 char buf[1024];
473 if (*partyline_buffer) {
474 if (strncasecmp(partyline_buffer, "/me ", 4) == 0) {
475 sockprintf(server_sock, "plineact %d %s", my_playernum, partyline_buffer+4);
476 snprintf(buf, sizeof(buf), "* %s %s", players[my_playernum-1], partyline_buffer+4);
477 io->draw_text(BUFFER_PLINE, buf);
478 } else if (strcasecmp(partyline_buffer, "/start") == 0) {
479 sockprintf(server_sock, "startgame 1 %d", my_playernum);
480 } else if (strcasecmp(partyline_buffer, "/end") == 0) {
481 sockprintf(server_sock, "startgame 0 %d", my_playernum);
482 } else if (strcasecmp(partyline_buffer, "/pause") == 0) {
483 sockprintf(server_sock, "pause 1 %d", my_playernum);
484 } else if (strcasecmp(partyline_buffer, "/unpause") == 0) {
485 sockprintf(server_sock, "pause 0 %d", my_playernum);
486 } else if (strncasecmp(partyline_buffer, "/team", 5) == 0) {
487 if (strlen(partyline_buffer) == 5)
488 strcpy(partyline_buffer+5, " "); /* make it "/team " */
489 sockprintf(server_sock, "team %d %s", my_playernum, partyline_buffer+6);
490 if (partyline_buffer[6]) {
491 if (teams[my_playernum-1])
492 free(teams[my_playernum-1]);
493 teams[my_playernum-1] = strdup(partyline_buffer+6);
494 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[my_playernum-1], partyline_buffer+6);
495 io->draw_text(BUFFER_PLINE, buf);
496 } else {
497 if (teams[my_playernum-1])
498 free(teams[my_playernum-1]);
499 teams[my_playernum-1] = NULL;
500 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[my_playernum-1]);
501 io->draw_text(BUFFER_PLINE, buf);
503 } else if (*partyline_buffer == '/'
504 && partyline_buffer[1] != 0 && partyline_buffer[1] != ' ') {
505 char *s = strtok(partyline_buffer+1, " ");
506 if (!s)
507 s = "(null)";
508 snprintf(buf, sizeof(buf), "*** Unknown command: %s", s);
509 io->draw_text(BUFFER_PLINE, buf);
510 } else {
511 sockprintf(server_sock, "pline %d %s", my_playernum, partyline_buffer);
512 snprintf(buf, sizeof(buf), "<%s> %s", players[my_playernum-1], partyline_buffer);
513 io->draw_text(BUFFER_PLINE, buf);
515 partyline_pos = 0;
516 *partyline_buffer = 0;
517 io->draw_partyline_input(partyline_buffer, partyline_pos);
521 #undef curpos
523 /*************************************************************************/
524 /*************************************************************************/
526 int init(int ac, char **av)
528 int i;
529 char *nick = NULL, *server = NULL;
530 char buf[1024];
531 char nickmsg[1024];
532 unsigned char ip[4];
533 char iphashbuf[32];
534 int len;
535 int start_server = 0; /* Start the server? (-server) */
536 int slide = 0; /* Do we definitely want to slide? (-slide) */
539 /* If there's a DISPLAY variable set in the environment, default to
540 * Xwindows I/O, else default to terminal I/O. */
541 if (getenv("DISPLAY"))
542 io = &xwin_interface;
543 else
544 io = &tty_interface;
545 io=&tty_interface; /* because Xwin isn't done yet */
547 srand(time(NULL));
548 init_shapes();
550 for (i = 1; i < ac; i++) {
551 if (*av[i] == '-') {
552 if (strcmp(av[i], "-server") == 0) {
553 start_server = 1;
554 } else if (strcmp(av[i], "-fancy") == 0) {
555 fancy = 1;
556 } else if (strcmp(av[i], "-log") == 0) {
557 log = 1;
558 i++;
559 if (i >= ac) {
560 fprintf(stderr, "Option -log requires an argument\n");
561 return 1;
563 logname = av[i];
564 } else if (strcmp(av[i], "-noslide") == 0) {
565 noslide = 1;
566 } else if (strcmp(av[i], "-slide") == 0) {
567 slide = 1;
568 } else if (strcmp(av[i], "-windows") == 0) {
569 windows_mode = 1;
570 noslide = 1;
571 } else {
572 fprintf(stderr, "Unknown option %s\n", av[i]);
573 return 1;
575 } else if (!nick) {
576 nick = av[i];
577 } else if (!server) {
578 server = av[i];
579 } else {
580 fprintf(stderr, "Usage: %s <nick> <server>\n", av[0]);
581 return 1;
584 if (slide)
585 noslide = 0;
586 if (start_server)
587 exit(server_main());
588 if (!server) {
589 fprintf(stderr, "Usage: %s <nick> <server>\n", av[0]);
590 return 1;
592 if (strlen(nick) > 63) /* put a reasonable limit on nick length */
593 nick[63] = 0;
595 if ((server_sock = conn(server, 31457, ip)) < 0) {
596 fprintf(stderr, "Couldn't connect to server %s: %s\n",
597 server, strerror(errno));
598 return 1;
600 sprintf(nickmsg, "tetrisstart %s 1.13", nick);
601 sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17);
602 /* buf[0] does not need to be initialized for this algorithm */
603 len = strlen(nickmsg);
604 for (i = 0; i < len; i++)
605 buf[i+1] = (((buf[i]&0xFF) + (nickmsg[i]&0xFF)) % 255) ^ iphashbuf[i % strlen(iphashbuf)];
606 len++;
607 for (i = 0; i < len; i++)
608 sprintf(nickmsg+i*2, "%02X", buf[i] & 0xFF);
609 sputs(nickmsg, server_sock);
611 do {
612 if (!sgets(buf, sizeof(buf), server_sock)) {
613 fprintf(stderr, "Server %s closed connection\n", server);
614 disconn(server_sock);
615 return 1;
617 parse(buf);
618 } while (my_playernum < 0);
619 sockprintf(server_sock, "team %d ", my_playernum);
621 players[my_playernum-1] = strdup(nick);
622 dispmode = MODE_PARTYLINE;
623 io->screen_setup();
624 io->setup_partyline();
626 return 0;
629 /*************************************************************************/
631 int main(int ac, char **av)
633 int i;
635 if ((i = init(ac, av)) != 0)
636 return i;
638 for (;;) {
639 int timeout;
640 if (playing_game && !game_paused)
641 timeout = tetris_timeout();
642 else
643 timeout = -1;
644 i = io->wait_for_input(timeout);
645 if (i == -1) {
646 char buf[1024];
647 if (sgets(buf, sizeof(buf), server_sock))
648 parse(buf);
649 else {
650 io->draw_text(BUFFER_PLINE, "*** Disconnected from Server");
651 break;
653 } else if (i == -2) {
654 tetris_timeout_action();
655 } else if (i == 12) { /* Ctrl-L */
656 io->screen_redraw();
657 } else if (i == K_F10) {
658 break; /* out of main loop */
659 } else if (i == K_F1) {
660 if (dispmode != MODE_FIELDS) {
661 dispmode = MODE_FIELDS;
662 io->setup_fields();
664 } else if (i == K_F2) {
665 if (dispmode != MODE_PARTYLINE) {
666 dispmode = MODE_PARTYLINE;
667 io->setup_partyline();
669 } else if (i == K_F3) {
670 if (dispmode != MODE_WINLIST) {
671 dispmode = MODE_WINLIST;
672 io->setup_winlist();
674 } else if (dispmode == MODE_FIELDS) {
675 tetris_input(i);
676 } else if (dispmode == MODE_PARTYLINE) {
677 if (i == 8 || i == 127) /* Backspace or Delete */
678 partyline_backspace();
679 else if (i == 4) /* Ctrl-D */
680 partyline_delete();
681 else if (i == 21) /* Ctrl-U */
682 partyline_kill();
683 else if (i == '\r' || i == '\n')
684 partyline_enter();
685 else if (i == K_LEFT)
686 partyline_move(-1);
687 else if (i == K_RIGHT)
688 partyline_move(1);
689 else if (i == 1) /* Ctrl-A */
690 partyline_move(-2);
691 else if (i == 5) /* Ctrl-E */
692 partyline_move(2);
693 else if (i >= 1 && i <= 0xFF)
694 partyline_input(i);
698 disconn(server_sock);
699 return 0;
702 /*************************************************************************/
704 #endif /* !SERVER_ONLY */
706 /*************************************************************************/