Ignore compiled binaries.
[tetrinet.git] / tetrinet.c
blobad877964276869fd64ef0f0faf8fdeea021dd1b5
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? */
25 int tetrifast = 0; /* TetriFast mode? */
27 int my_playernum = -1; /* What player number are we? */
28 char *my_nick; /* And what is our nick? */
29 WinInfo winlist[MAXWINLIST]; /* Winners' list from server */
30 int server_sock; /* Socket for server communication */
31 int dispmode; /* Current display mode */
32 char *players[6]; /* Player names (NULL for no such player) */
33 char *teams[6]; /* Team names (NULL for not on a team) */
34 int playing_game; /* Are we currently playing a game? */
35 int not_playing_game; /* Are we currently watching people play a game? */
36 int game_paused; /* Is the game currently paused? */
38 Interface *io; /* Input/output routines */
40 /*************************************************************************/
41 /*************************************************************************/
43 #ifndef SERVER_ONLY
45 /*************************************************************************/
47 /* Parse a line from the server. Destroys the buffer it's given as a side
48 * effect.
51 void parse(char *buf)
53 char *cmd, *s, *t;
55 cmd = strtok(buf, " ");
57 if (!cmd) {
58 return;
60 } else if (strcmp(cmd, "noconnecting") == 0) {
61 s = strtok(NULL, "");
62 if (!s)
63 s = "Unknown";
64 /* XXX not to stderr, please! -- we need to stay running w/o server */
65 fprintf(stderr, "Server error: %s\n", s);
66 exit(1);
68 } else if (strcmp(cmd, "winlist") == 0) {
69 int i = 0;
71 while (i < MAXWINLIST && (s = strtok(NULL, " "))) {
72 t = strchr(s, ';');
73 if (!t)
74 break;
75 *t++ = 0;
76 if (*s == 't')
77 winlist[i].team = 1;
78 else
79 winlist[i].team = 0;
80 s++;
81 strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1);
82 winlist[i].name[sizeof(winlist[i].name)] = 0;
83 winlist[i].points = atoi(t);
84 if ((t = strchr(t, ';')) != NULL)
85 winlist[i].games = atoi(t+1);
86 i++;
88 if (i < MAXWINLIST)
89 winlist[i].name[0] = 0;
90 if (dispmode == MODE_WINLIST)
91 io->setup_winlist();
93 } else if (strcmp(cmd, tetrifast ? ")#)(!@(*3" : "playernum") == 0) {
94 if (s = strtok(NULL, " "))
95 my_playernum = atoi(s);
96 /* Note: players[my_playernum-1] is set in init() */
97 /* But that doesn't work when joining other channel. */
98 players[my_playernum-1] = strdup(my_nick);
100 } else if (strcmp(cmd, "playerjoin") == 0) {
101 int player;
102 char buf[1024];
104 s = strtok(NULL, " ");
105 t = strtok(NULL, "");
106 if (!s || !t)
107 return;
108 player = atoi(s)-1;
109 if (player < 0 || player > 5)
110 return;
111 players[player] = strdup(t);
112 if (teams[player]) {
113 free(teams[player]);
114 teams[player] = NULL;
116 snprintf(buf, sizeof(buf), "*** %s is Now Playing", t);
117 io->draw_text(BUFFER_PLINE, buf);
118 if (dispmode == MODE_FIELDS)
119 io->setup_fields();
121 } else if (strcmp(cmd, "playerleave") == 0) {
122 int player;
123 char buf[1024];
125 s = strtok(NULL, " ");
126 if (!s)
127 return;
128 player = atoi(s)-1;
129 if (player < 0 || player > 5 || !players[player])
130 return;
131 snprintf(buf, sizeof(buf), "*** %s has Left", players[player]);
132 io->draw_text(BUFFER_PLINE, buf);
133 free(players[player]);
134 players[player] = NULL;
135 if (dispmode == MODE_FIELDS)
136 io->setup_fields();
138 } else if (strcmp(cmd, "team") == 0) {
139 int player;
140 char buf[1024];
142 s = strtok(NULL, " ");
143 t = strtok(NULL, "");
144 if (!s)
145 return;
146 player = atoi(s)-1;
147 if (player < 0 || player > 5 || !players[player])
148 return;
149 if (teams[player])
150 free(teams[player]);
151 if (t)
152 teams[player] = strdup(t);
153 else
154 teams[player] = NULL;
155 if (t)
156 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[player], t);
157 else
158 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[player]);
159 io->draw_text(BUFFER_PLINE, buf);
161 } else if (strcmp(cmd, "pline") == 0) {
162 int playernum;
163 char buf[1024], *name;
165 s = strtok(NULL, " ");
166 t = strtok(NULL, "");
167 if (!s)
168 return;
169 if (!t)
170 t = "";
171 playernum = atoi(s)-1;
172 if (playernum == -1) {
173 name = "Server";
174 } else {
175 if (playernum < 0 || playernum > 5 || !players[playernum])
176 return;
177 name = players[playernum];
179 snprintf(buf, sizeof(buf), "<%s> %s", name, t);
180 io->draw_text(BUFFER_PLINE, buf);
182 } else if (strcmp(cmd, "plineact") == 0) {
183 int playernum;
184 char buf[1024], *name;
186 s = strtok(NULL, " ");
187 t = strtok(NULL, "");
188 if (!s)
189 return;
190 if (!t)
191 t = "";
192 playernum = atoi(s)-1;
193 if (playernum == -1) {
194 name = "Server";
195 } else {
196 if (playernum < 0 || playernum > 5 || !players[playernum])
197 return;
198 name = players[playernum];
200 snprintf(buf, sizeof(buf), "* %s %s", name, t);
201 io->draw_text(BUFFER_PLINE, buf);
203 } else if (strcmp(cmd, tetrifast ? "*******" : "newgame") == 0) {
204 int i;
206 if (s = strtok(NULL, " "))
207 /* stack height */;
208 if (s = strtok(NULL, " "))
209 initial_level = atoi(s);
210 if (s = strtok(NULL, " "))
211 lines_per_level = atoi(s);
212 if (s = strtok(NULL, " "))
213 level_inc = atoi(s);
214 if (s = strtok(NULL, " "))
215 special_lines = atoi(s);
216 if (s = strtok(NULL, " "))
217 special_count = atoi(s);
218 if (s = strtok(NULL, " ")) {
219 special_capacity = atoi(s);
220 if (special_capacity > MAX_SPECIALS)
221 special_capacity = MAX_SPECIALS;
223 if (s = strtok(NULL, " ")) {
224 memset(piecefreq, 0, sizeof(piecefreq));
225 while (*s) {
226 i = *s - '1';
227 if (i >= 0 && i < 7)
228 piecefreq[i]++;
229 s++;
232 if (s = strtok(NULL, " ")) {
233 memset(specialfreq, 0, sizeof(specialfreq));
234 while (*s) {
235 i = *s - '1';
236 if (i >= 0 && i < 9)
237 specialfreq[i]++;
238 s++;
241 if (s = strtok(NULL, " "))
242 level_average = atoi(s);
243 if (s = strtok(NULL, " "))
244 old_mode = atoi(s);
245 lines = 0;
246 for (i = 0; i < 6; i++)
247 levels[i] = initial_level;
248 memset(&fields[my_playernum-1], 0, sizeof(Field));
249 specials[0] = -1;
250 io->clear_text(BUFFER_GMSG);
251 io->clear_text(BUFFER_ATTDEF);
252 new_game();
253 playing_game = 1;
254 game_paused = 0;
255 io->draw_text(BUFFER_PLINE, "*** The Game Has Started");
257 } else if (strcmp(cmd, "ingame") == 0) {
258 /* Sent when a player connects in the middle of a game */
259 int x, y;
260 char buf[1024], *s;
262 s = buf + sprintf(buf, "f %d ", my_playernum);
263 for (y = 0; y < FIELD_HEIGHT; y++) {
264 for (x = 0; x < FIELD_WIDTH; x++) {
265 fields[my_playernum-1][y][x] = rand()%5 + 1;
266 *s++ = '0' + fields[my_playernum-1][y][x];
269 *s = 0;
270 sputs(buf, server_sock);
271 playing_game = 0;
272 not_playing_game = 1;
274 } else if (strcmp(cmd, "pause") == 0) {
275 if (s = strtok(NULL, " "))
276 game_paused = atoi(s);
277 if (game_paused) {
278 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Paused");
279 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Paused");
280 } else {
281 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Unpaused");
282 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Unpaused");
285 } else if (strcmp(cmd, "endgame") == 0) {
286 playing_game = 0;
287 not_playing_game = 0;
288 memset(fields, 0, sizeof(fields));
289 specials[0] = -1;
290 io->clear_text(BUFFER_ATTDEF);
291 io->draw_text(BUFFER_PLINE, "*** The Game Has Ended");
292 if (dispmode == MODE_FIELDS) {
293 int i;
294 io->draw_own_field();
295 for (i = 1; i <= 6; i++) {
296 if (i != my_playernum)
297 io->draw_other_field(i);
301 } else if (strcmp(cmd, "playerwon") == 0) {
302 /* Syntax: playerwon # -- sent when all but one player lose */
304 } else if (strcmp(cmd, "playerlost") == 0) {
305 /* Syntax: playerlost # -- sent after playerleave on disconnect
306 * during a game, or when a player loses (sent by the losing
307 * player and from the server to all other players */
309 } else if (strcmp(cmd, "f") == 0) { /* field */
310 int player, x, y, tile;
312 /* This looks confusing, but what it means is, ignore this message
313 * if a game isn't going on. */
314 if (!playing_game && !not_playing_game)
315 return;
316 if (!(s = strtok(NULL, " ")))
317 return;
318 player = atoi(s);
319 player--;
320 if (!(s = strtok(NULL, "")))
321 return;
322 if (*s >= '0') {
323 /* Set field directly */
324 char *ptr = (char *) fields[player];
325 while (*s) {
326 if (*s <= '5')
327 *ptr++ = (*s++) - '0';
328 else switch (*s++) {
329 case 'a': *ptr++ = 6 + SPECIAL_A; break;
330 case 'b': *ptr++ = 6 + SPECIAL_B; break;
331 case 'c': *ptr++ = 6 + SPECIAL_C; break;
332 case 'g': *ptr++ = 6 + SPECIAL_G; break;
333 case 'n': *ptr++ = 6 + SPECIAL_N; break;
334 case 'o': *ptr++ = 6 + SPECIAL_O; break;
335 case 'q': *ptr++ = 6 + SPECIAL_Q; break;
336 case 'r': *ptr++ = 6 + SPECIAL_R; break;
337 case 's': *ptr++ = 6 + SPECIAL_S; break;
340 } else {
341 /* Set specific locations on field */
342 tile = 0;
343 while (*s) {
344 if (*s < '0') {
345 tile = *s - '!';
346 } else {
347 x = *s - '3';
348 y = (*++s) - '3';
349 fields[player][y][x] = tile;
351 s++;
354 if (player == my_playernum-1)
355 io->draw_own_field();
356 else
357 io->draw_other_field(player+1);
358 } else if (strcmp(cmd, "lvl") == 0) {
359 int player;
361 if (!(s = strtok(NULL, " ")))
362 return;
363 player = atoi(s)-1;
364 if (!(s = strtok(NULL, "")))
365 return;
366 levels[player] = atoi(s);
368 } else if (strcmp(cmd, "sb") == 0) {
369 int from, to;
370 char *type;
372 if (!(s = strtok(NULL, " ")))
373 return;
374 to = atoi(s);
375 if (!(type = strtok(NULL, " ")))
376 return;
377 if (!(s = strtok(NULL, " ")))
378 return;
379 from = atoi(s);
380 do_special(type, from, to);
382 } else if (strcmp(cmd, "gmsg") == 0) {
383 if (!(s = strtok(NULL, "")))
384 return;
385 io->draw_text(BUFFER_GMSG, s);
390 /*************************************************************************/
391 /*************************************************************************/
393 static char partyline_buffer[512];
394 static int partyline_pos = 0;
396 #define curpos (partyline_buffer+partyline_pos)
398 /*************************************************************************/
400 /* Add a character to the partyline buffer. */
402 void partyline_input(int c)
404 if (partyline_pos < sizeof(partyline_buffer) - 1) {
405 memmove(curpos+1, curpos, strlen(curpos)+1);
406 partyline_buffer[partyline_pos++] = c;
407 io->draw_partyline_input(partyline_buffer, partyline_pos);
411 /*************************************************************************/
413 /* Delete the current character from the partyline buffer. */
415 void partyline_delete(void)
417 if (partyline_buffer[partyline_pos]) {
418 memmove(curpos, curpos+1, strlen(curpos)-1+1);
419 io->draw_partyline_input(partyline_buffer, partyline_pos);
423 /*************************************************************************/
425 /* Backspace a character from the partyline buffer. */
427 void partyline_backspace(void)
429 if (partyline_pos > 0) {
430 partyline_pos--;
431 partyline_delete();
435 /*************************************************************************/
437 /* Kill the entire partyline input buffer. */
439 void partyline_kill(void)
441 partyline_pos = 0;
442 *partyline_buffer = 0;
443 io->draw_partyline_input(partyline_buffer, partyline_pos);
446 /*************************************************************************/
448 /* Move around the input buffer. Sign indicates direction; absolute value
449 * of 1 means one character, 2 means the whole line.
452 void partyline_move(int how)
454 if (how == -2) {
455 partyline_pos = 0;
456 io->draw_partyline_input(partyline_buffer, partyline_pos);
457 } else if (how == -1 && partyline_pos > 0) {
458 partyline_pos--;
459 io->draw_partyline_input(partyline_buffer, partyline_pos);
460 } else if (how == 1 && partyline_buffer[partyline_pos]) {
461 partyline_pos++;
462 io->draw_partyline_input(partyline_buffer, partyline_pos);
463 } else if (how == 2) {
464 partyline_pos = strlen(partyline_buffer);
465 io->draw_partyline_input(partyline_buffer, partyline_pos);
469 /*************************************************************************/
471 /* Send the input line to the server. */
473 void partyline_enter(void)
475 char buf[1024];
477 if (*partyline_buffer) {
478 if (strncasecmp(partyline_buffer, "/me ", 4) == 0) {
479 sockprintf(server_sock, "plineact %d %s", my_playernum, partyline_buffer+4);
480 snprintf(buf, sizeof(buf), "* %s %s", players[my_playernum-1], partyline_buffer+4);
481 io->draw_text(BUFFER_PLINE, buf);
482 } else if (strcasecmp(partyline_buffer, "/start") == 0) {
483 sockprintf(server_sock, "startgame 1 %d", my_playernum);
484 } else if (strcasecmp(partyline_buffer, "/end") == 0) {
485 sockprintf(server_sock, "startgame 0 %d", my_playernum);
486 } else if (strcasecmp(partyline_buffer, "/pause") == 0) {
487 sockprintf(server_sock, "pause 1 %d", my_playernum);
488 } else if (strcasecmp(partyline_buffer, "/unpause") == 0) {
489 sockprintf(server_sock, "pause 0 %d", my_playernum);
490 } else if (strncasecmp(partyline_buffer, "/team", 5) == 0) {
491 if (strlen(partyline_buffer) == 5)
492 strcpy(partyline_buffer+5, " "); /* make it "/team " */
493 sockprintf(server_sock, "team %d %s", my_playernum, partyline_buffer+6);
494 if (partyline_buffer[6]) {
495 if (teams[my_playernum-1])
496 free(teams[my_playernum-1]);
497 teams[my_playernum-1] = strdup(partyline_buffer+6);
498 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[my_playernum-1], partyline_buffer+6);
499 io->draw_text(BUFFER_PLINE, buf);
500 } else {
501 if (teams[my_playernum-1])
502 free(teams[my_playernum-1]);
503 teams[my_playernum-1] = NULL;
504 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[my_playernum-1]);
505 io->draw_text(BUFFER_PLINE, buf);
507 } else {
508 sockprintf(server_sock, "pline %d %s", my_playernum, partyline_buffer);
509 if (*partyline_buffer != '/'
510 || partyline_buffer[1] == 0 || partyline_buffer[1] == ' ') {
511 /* We do not show server-side commands. */
512 snprintf(buf, sizeof(buf), "<%s> %s", players[my_playernum-1], partyline_buffer);
513 io->draw_text(BUFFER_PLINE, buf);
516 partyline_pos = 0;
517 *partyline_buffer = 0;
518 io->draw_partyline_input(partyline_buffer, partyline_pos);
522 #undef curpos
524 /*************************************************************************/
525 /*************************************************************************/
527 void help()
529 fprintf(stderr,
530 "Usage: tetrinet [OPTION]... NICK SERVER\n"
531 "\n"
532 "Options (see README for details):\n"
533 " -fancy Use \"fancy\" TTY graphics.\n"
534 " -fast Connect to the server in the tetrifast mode.\n"
535 " -log <file> Log network traffic to the given file.\n"
536 " -noslide Do not allow pieces to \"slide\" after being dropped\n"
537 " with the spacebar.\n"
538 " -server Start the server instead of the client.\n"
539 " -slide Opposite of -noslide; allows pieces to \"slide\" after\n"
540 " being dropped. If both -slide and -noslide are given,\n"
541 " -slide takes precedence.\n"
542 " -windows Behave as much like the Windows version of Tetrinet as\n"
543 " possible. Implies -noslide.\n"
547 int init(int ac, char **av)
549 int i;
550 char *nick = NULL, *server = NULL;
551 char buf[1024];
552 char nickmsg[1024];
553 unsigned char ip[4];
554 char iphashbuf[32];
555 int len;
556 int start_server = 0; /* Start the server? (-server) */
557 int slide = 0; /* Do we definitely want to slide? (-slide) */
560 /* If there's a DISPLAY variable set in the environment, default to
561 * Xwindows I/O, else default to terminal I/O. */
562 if (getenv("DISPLAY"))
563 io = &xwin_interface;
564 else
565 io = &tty_interface;
566 io=&tty_interface; /* because Xwin isn't done yet */
568 srand(time(NULL));
569 init_shapes();
571 for (i = 1; i < ac; i++) {
572 if (*av[i] == '-') {
573 if (strcmp(av[i], "-server") == 0) {
574 start_server = 1;
575 } else if (strcmp(av[i], "-fancy") == 0) {
576 fancy = 1;
577 } else if (strcmp(av[i], "-log") == 0) {
578 log = 1;
579 i++;
580 if (i >= ac) {
581 fprintf(stderr, "Option -log requires an argument\n");
582 return 1;
584 logname = av[i];
585 } else if (strcmp(av[i], "-noslide") == 0) {
586 noslide = 1;
587 } else if (strcmp(av[i], "-slide") == 0) {
588 slide = 1;
589 } else if (strcmp(av[i], "-windows") == 0) {
590 windows_mode = 1;
591 noslide = 1;
592 } else if (strcmp(av[i], "-fast") == 0) {
593 tetrifast = 1;
594 } else {
595 fprintf(stderr, "Unknown option %s\n", av[i]);
596 help();
597 return 1;
599 } else if (!nick) {
600 my_nick = nick = av[i];
601 } else if (!server) {
602 server = av[i];
603 } else {
604 help();
605 return 1;
608 if (slide)
609 noslide = 0;
610 if (start_server)
611 exit(server_main());
612 if (!server) {
613 help();
614 return 1;
616 if (strlen(nick) > 63) /* put a reasonable limit on nick length */
617 nick[63] = 0;
619 if ((server_sock = conn(server, 31457, ip)) < 0) {
620 fprintf(stderr, "Couldn't connect to server %s: %s\n",
621 server, strerror(errno));
622 return 1;
624 sprintf(nickmsg, "tetri%s %s 1.13", tetrifast ? "faster" : "sstart", nick);
625 sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17);
626 /* buf[0] does not need to be initialized for this algorithm */
627 len = strlen(nickmsg);
628 for (i = 0; i < len; i++)
629 buf[i+1] = (((buf[i]&0xFF) + (nickmsg[i]&0xFF)) % 255) ^ iphashbuf[i % strlen(iphashbuf)];
630 len++;
631 for (i = 0; i < len; i++)
632 sprintf(nickmsg+i*2, "%02X", buf[i] & 0xFF);
633 sputs(nickmsg, server_sock);
635 do {
636 if (!sgets(buf, sizeof(buf), server_sock)) {
637 fprintf(stderr, "Server %s closed connection\n", server);
638 disconn(server_sock);
639 return 1;
641 parse(buf);
642 } while (my_playernum < 0);
643 sockprintf(server_sock, "team %d ", my_playernum);
645 players[my_playernum-1] = strdup(nick);
646 dispmode = MODE_PARTYLINE;
647 io->screen_setup();
648 io->setup_partyline();
650 return 0;
653 /*************************************************************************/
655 int main(int ac, char **av)
657 int i;
659 if ((i = init(ac, av)) != 0)
660 return i;
662 for (;;) {
663 int timeout;
664 if (playing_game && !game_paused)
665 timeout = tetris_timeout();
666 else
667 timeout = -1;
668 i = io->wait_for_input(timeout);
669 if (i == -1) {
670 char buf[1024];
671 if (sgets(buf, sizeof(buf), server_sock))
672 parse(buf);
673 else {
674 io->draw_text(BUFFER_PLINE, "*** Disconnected from Server");
675 break;
677 } else if (i == -2) {
678 tetris_timeout_action();
679 } else if (i == 12) { /* Ctrl-L */
680 io->screen_redraw();
681 } else if (i == K_F10) {
682 break; /* out of main loop */
683 } else if (i == K_F1) {
684 if (dispmode != MODE_FIELDS) {
685 dispmode = MODE_FIELDS;
686 io->setup_fields();
688 } else if (i == K_F2) {
689 if (dispmode != MODE_PARTYLINE) {
690 dispmode = MODE_PARTYLINE;
691 io->setup_partyline();
693 } else if (i == K_F3) {
694 if (dispmode != MODE_WINLIST) {
695 dispmode = MODE_WINLIST;
696 io->setup_winlist();
698 } else if (dispmode == MODE_FIELDS) {
699 tetris_input(i);
700 } else if (dispmode == MODE_PARTYLINE) {
701 if (i == 8 || i == 127) /* Backspace or Delete */
702 partyline_backspace();
703 else if (i == 4) /* Ctrl-D */
704 partyline_delete();
705 else if (i == 21) /* Ctrl-U */
706 partyline_kill();
707 else if (i == '\r' || i == '\n')
708 partyline_enter();
709 else if (i == K_LEFT)
710 partyline_move(-1);
711 else if (i == K_RIGHT)
712 partyline_move(1);
713 else if (i == 1) /* Ctrl-A */
714 partyline_move(-2);
715 else if (i == 5) /* Ctrl-E */
716 partyline_move(2);
717 else if (i >= 1 && i <= 0xFF)
718 partyline_input(i);
722 disconn(server_sock);
723 return 0;
726 /*************************************************************************/
728 #endif /* !SERVER_ONLY */
730 /*************************************************************************/