Include the version information in the usage output.
[tetrinet.git] / tetrinet.c
blobe80289e326c5a6e1767f4a2c1d1473d411a58a39
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"
17 #include "version.h"
19 /*************************************************************************/
21 int fancy = 0; /* Fancy TTY graphics? */
22 int log = 0; /* Log network traffic to file? */
23 char *logname; /* Log filename */
24 int windows_mode = 0; /* Try to be just like the Windows version? */
25 int noslide = 0; /* Disallow piece sliding? */
26 int tetrifast = 0; /* TetriFast mode? */
28 int my_playernum = -1; /* What player number are we? */
29 char *my_nick; /* And what is our nick? */
30 WinInfo winlist[MAXWINLIST]; /* Winners' list from server */
31 int server_sock; /* Socket for server communication */
32 int dispmode; /* Current display mode */
33 char *players[6]; /* Player names (NULL for no such player) */
34 char *teams[6]; /* Team names (NULL for not on a team) */
35 int playing_game; /* Are we currently playing a game? */
36 int not_playing_game; /* Are we currently watching people play a game? */
37 int game_paused; /* Is the game currently paused? */
39 Interface *io; /* Input/output routines */
41 /*************************************************************************/
42 /*************************************************************************/
44 #ifndef SERVER_ONLY
46 /*************************************************************************/
48 /* Parse a line from the server. Destroys the buffer it's given as a side
49 * effect.
52 void parse(char *buf)
54 char *cmd, *s, *t;
56 cmd = strtok(buf, " ");
58 if (!cmd) {
59 return;
61 } else if (strcmp(cmd, "noconnecting") == 0) {
62 s = strtok(NULL, "");
63 if (!s)
64 s = "Unknown";
65 /* XXX not to stderr, please! -- we need to stay running w/o server */
66 fprintf(stderr, "Server error: %s\n", s);
67 exit(1);
69 } else if (strcmp(cmd, "winlist") == 0) {
70 int i = 0;
72 while (i < MAXWINLIST && (s = strtok(NULL, " "))) {
73 t = strchr(s, ';');
74 if (!t)
75 break;
76 *t++ = 0;
77 if (*s == 't')
78 winlist[i].team = 1;
79 else
80 winlist[i].team = 0;
81 s++;
82 strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1);
83 winlist[i].name[sizeof(winlist[i].name)] = 0;
84 winlist[i].points = atoi(t);
85 if ((t = strchr(t, ';')) != NULL)
86 winlist[i].games = atoi(t+1);
87 i++;
89 if (i < MAXWINLIST)
90 winlist[i].name[0] = 0;
91 if (dispmode == MODE_WINLIST)
92 io->setup_winlist();
94 } else if (strcmp(cmd, tetrifast ? ")#)(!@(*3" : "playernum") == 0) {
95 if (s = strtok(NULL, " "))
96 my_playernum = atoi(s);
97 /* Note: players[my_playernum-1] is set in init() */
98 /* But that doesn't work when joining other channel. */
99 players[my_playernum-1] = strdup(my_nick);
101 } else if (strcmp(cmd, "playerjoin") == 0) {
102 int player;
103 char buf[1024];
105 s = strtok(NULL, " ");
106 t = strtok(NULL, "");
107 if (!s || !t)
108 return;
109 player = atoi(s)-1;
110 if (player < 0 || player > 5)
111 return;
112 players[player] = strdup(t);
113 if (teams[player]) {
114 free(teams[player]);
115 teams[player] = NULL;
117 snprintf(buf, sizeof(buf), "*** %s is Now Playing", t);
118 io->draw_text(BUFFER_PLINE, buf);
119 if (dispmode == MODE_FIELDS)
120 io->setup_fields();
122 } else if (strcmp(cmd, "playerleave") == 0) {
123 int player;
124 char buf[1024];
126 s = strtok(NULL, " ");
127 if (!s)
128 return;
129 player = atoi(s)-1;
130 if (player < 0 || player > 5 || !players[player])
131 return;
132 snprintf(buf, sizeof(buf), "*** %s has Left", players[player]);
133 io->draw_text(BUFFER_PLINE, buf);
134 free(players[player]);
135 players[player] = NULL;
136 if (dispmode == MODE_FIELDS)
137 io->setup_fields();
139 } else if (strcmp(cmd, "team") == 0) {
140 int player;
141 char buf[1024];
143 s = strtok(NULL, " ");
144 t = strtok(NULL, "");
145 if (!s)
146 return;
147 player = atoi(s)-1;
148 if (player < 0 || player > 5 || !players[player])
149 return;
150 if (teams[player])
151 free(teams[player]);
152 if (t)
153 teams[player] = strdup(t);
154 else
155 teams[player] = NULL;
156 if (t)
157 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[player], t);
158 else
159 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[player]);
160 io->draw_text(BUFFER_PLINE, buf);
162 } else if (strcmp(cmd, "pline") == 0) {
163 int playernum;
164 char buf[1024], *name;
166 s = strtok(NULL, " ");
167 t = strtok(NULL, "");
168 if (!s)
169 return;
170 if (!t)
171 t = "";
172 playernum = atoi(s)-1;
173 if (playernum == -1) {
174 name = "Server";
175 } else {
176 if (playernum < 0 || playernum > 5 || !players[playernum])
177 return;
178 name = players[playernum];
180 snprintf(buf, sizeof(buf), "<%s> %s", name, t);
181 io->draw_text(BUFFER_PLINE, buf);
183 } else if (strcmp(cmd, "plineact") == 0) {
184 int playernum;
185 char buf[1024], *name;
187 s = strtok(NULL, " ");
188 t = strtok(NULL, "");
189 if (!s)
190 return;
191 if (!t)
192 t = "";
193 playernum = atoi(s)-1;
194 if (playernum == -1) {
195 name = "Server";
196 } else {
197 if (playernum < 0 || playernum > 5 || !players[playernum])
198 return;
199 name = players[playernum];
201 snprintf(buf, sizeof(buf), "* %s %s", name, t);
202 io->draw_text(BUFFER_PLINE, buf);
204 } else if (strcmp(cmd, tetrifast ? "*******" : "newgame") == 0) {
205 int i;
207 if (s = strtok(NULL, " "))
208 /* stack height */;
209 if (s = strtok(NULL, " "))
210 initial_level = atoi(s);
211 if (s = strtok(NULL, " "))
212 lines_per_level = atoi(s);
213 if (s = strtok(NULL, " "))
214 level_inc = atoi(s);
215 if (s = strtok(NULL, " "))
216 special_lines = atoi(s);
217 if (s = strtok(NULL, " "))
218 special_count = atoi(s);
219 if (s = strtok(NULL, " ")) {
220 special_capacity = atoi(s);
221 if (special_capacity > MAX_SPECIALS)
222 special_capacity = MAX_SPECIALS;
224 if (s = strtok(NULL, " ")) {
225 memset(piecefreq, 0, sizeof(piecefreq));
226 while (*s) {
227 i = *s - '1';
228 if (i >= 0 && i < 7)
229 piecefreq[i]++;
230 s++;
233 if (s = strtok(NULL, " ")) {
234 memset(specialfreq, 0, sizeof(specialfreq));
235 while (*s) {
236 i = *s - '1';
237 if (i >= 0 && i < 9)
238 specialfreq[i]++;
239 s++;
242 if (s = strtok(NULL, " "))
243 level_average = atoi(s);
244 if (s = strtok(NULL, " "))
245 old_mode = atoi(s);
246 lines = 0;
247 for (i = 0; i < 6; i++)
248 levels[i] = initial_level;
249 memset(&fields[my_playernum-1], 0, sizeof(Field));
250 specials[0] = -1;
251 io->clear_text(BUFFER_GMSG);
252 io->clear_text(BUFFER_ATTDEF);
253 new_game();
254 playing_game = 1;
255 game_paused = 0;
256 io->draw_text(BUFFER_PLINE, "*** The Game Has Started");
258 } else if (strcmp(cmd, "ingame") == 0) {
259 /* Sent when a player connects in the middle of a game */
260 int x, y;
261 char buf[1024], *s;
263 s = buf + sprintf(buf, "f %d ", my_playernum);
264 for (y = 0; y < FIELD_HEIGHT; y++) {
265 for (x = 0; x < FIELD_WIDTH; x++) {
266 fields[my_playernum-1][y][x] = rand()%5 + 1;
267 *s++ = '0' + fields[my_playernum-1][y][x];
270 *s = 0;
271 sputs(buf, server_sock);
272 playing_game = 0;
273 not_playing_game = 1;
275 } else if (strcmp(cmd, "pause") == 0) {
276 if (s = strtok(NULL, " "))
277 game_paused = atoi(s);
278 if (game_paused) {
279 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Paused");
280 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Paused");
281 } else {
282 io->draw_text(BUFFER_PLINE, "*** The Game Has Been Unpaused");
283 io->draw_text(BUFFER_GMSG, "*** The Game Has Been Unpaused");
286 } else if (strcmp(cmd, "endgame") == 0) {
287 playing_game = 0;
288 not_playing_game = 0;
289 memset(fields, 0, sizeof(fields));
290 specials[0] = -1;
291 io->clear_text(BUFFER_ATTDEF);
292 io->draw_text(BUFFER_PLINE, "*** The Game Has Ended");
293 if (dispmode == MODE_FIELDS) {
294 int i;
295 io->draw_own_field();
296 for (i = 1; i <= 6; i++) {
297 if (i != my_playernum)
298 io->draw_other_field(i);
302 } else if (strcmp(cmd, "playerwon") == 0) {
303 /* Syntax: playerwon # -- sent when all but one player lose */
305 } else if (strcmp(cmd, "playerlost") == 0) {
306 /* Syntax: playerlost # -- sent after playerleave on disconnect
307 * during a game, or when a player loses (sent by the losing
308 * player and from the server to all other players */
310 } else if (strcmp(cmd, "f") == 0) { /* field */
311 int player, x, y, tile;
313 /* This looks confusing, but what it means is, ignore this message
314 * if a game isn't going on. */
315 if (!playing_game && !not_playing_game)
316 return;
317 if (!(s = strtok(NULL, " ")))
318 return;
319 player = atoi(s);
320 player--;
321 if (!(s = strtok(NULL, "")))
322 return;
323 if (*s >= '0') {
324 /* Set field directly */
325 char *ptr = (char *) fields[player];
326 while (*s) {
327 if (*s <= '5')
328 *ptr++ = (*s++) - '0';
329 else switch (*s++) {
330 case 'a': *ptr++ = 6 + SPECIAL_A; break;
331 case 'b': *ptr++ = 6 + SPECIAL_B; break;
332 case 'c': *ptr++ = 6 + SPECIAL_C; break;
333 case 'g': *ptr++ = 6 + SPECIAL_G; break;
334 case 'n': *ptr++ = 6 + SPECIAL_N; break;
335 case 'o': *ptr++ = 6 + SPECIAL_O; break;
336 case 'q': *ptr++ = 6 + SPECIAL_Q; break;
337 case 'r': *ptr++ = 6 + SPECIAL_R; break;
338 case 's': *ptr++ = 6 + SPECIAL_S; break;
341 } else {
342 /* Set specific locations on field */
343 tile = 0;
344 while (*s) {
345 if (*s < '0') {
346 tile = *s - '!';
347 } else {
348 x = *s - '3';
349 y = (*++s) - '3';
350 fields[player][y][x] = tile;
352 s++;
355 if (player == my_playernum-1)
356 io->draw_own_field();
357 else
358 io->draw_other_field(player+1);
359 } else if (strcmp(cmd, "lvl") == 0) {
360 int player;
362 if (!(s = strtok(NULL, " ")))
363 return;
364 player = atoi(s)-1;
365 if (!(s = strtok(NULL, "")))
366 return;
367 levels[player] = atoi(s);
369 } else if (strcmp(cmd, "sb") == 0) {
370 int from, to;
371 char *type;
373 if (!(s = strtok(NULL, " ")))
374 return;
375 to = atoi(s);
376 if (!(type = strtok(NULL, " ")))
377 return;
378 if (!(s = strtok(NULL, " ")))
379 return;
380 from = atoi(s);
381 do_special(type, from, to);
383 } else if (strcmp(cmd, "gmsg") == 0) {
384 if (!(s = strtok(NULL, "")))
385 return;
386 io->draw_text(BUFFER_GMSG, s);
391 /*************************************************************************/
392 /*************************************************************************/
394 static char partyline_buffer[512];
395 static int partyline_pos = 0;
397 #define curpos (partyline_buffer+partyline_pos)
399 /*************************************************************************/
401 /* Add a character to the partyline buffer. */
403 void partyline_input(int c)
405 if (partyline_pos < sizeof(partyline_buffer) - 1) {
406 memmove(curpos+1, curpos, strlen(curpos)+1);
407 partyline_buffer[partyline_pos++] = c;
408 io->draw_partyline_input(partyline_buffer, partyline_pos);
412 /*************************************************************************/
414 /* Delete the current character from the partyline buffer. */
416 void partyline_delete(void)
418 if (partyline_buffer[partyline_pos]) {
419 memmove(curpos, curpos+1, strlen(curpos)-1+1);
420 io->draw_partyline_input(partyline_buffer, partyline_pos);
424 /*************************************************************************/
426 /* Backspace a character from the partyline buffer. */
428 void partyline_backspace(void)
430 if (partyline_pos > 0) {
431 partyline_pos--;
432 partyline_delete();
436 /*************************************************************************/
438 /* Kill the entire partyline input buffer. */
440 void partyline_kill(void)
442 partyline_pos = 0;
443 *partyline_buffer = 0;
444 io->draw_partyline_input(partyline_buffer, partyline_pos);
447 /*************************************************************************/
449 /* Move around the input buffer. Sign indicates direction; absolute value
450 * of 1 means one character, 2 means the whole line.
453 void partyline_move(int how)
455 if (how == -2) {
456 partyline_pos = 0;
457 io->draw_partyline_input(partyline_buffer, partyline_pos);
458 } else if (how == -1 && partyline_pos > 0) {
459 partyline_pos--;
460 io->draw_partyline_input(partyline_buffer, partyline_pos);
461 } else if (how == 1 && partyline_buffer[partyline_pos]) {
462 partyline_pos++;
463 io->draw_partyline_input(partyline_buffer, partyline_pos);
464 } else if (how == 2) {
465 partyline_pos = strlen(partyline_buffer);
466 io->draw_partyline_input(partyline_buffer, partyline_pos);
470 /*************************************************************************/
472 /* Send the input line to the server. */
474 void partyline_enter(void)
476 char buf[1024];
478 if (*partyline_buffer) {
479 if (strncasecmp(partyline_buffer, "/me ", 4) == 0) {
480 sockprintf(server_sock, "plineact %d %s", my_playernum, partyline_buffer+4);
481 snprintf(buf, sizeof(buf), "* %s %s", players[my_playernum-1], partyline_buffer+4);
482 io->draw_text(BUFFER_PLINE, buf);
483 } else if (strcasecmp(partyline_buffer, "/start") == 0) {
484 sockprintf(server_sock, "startgame 1 %d", my_playernum);
485 } else if (strcasecmp(partyline_buffer, "/end") == 0) {
486 sockprintf(server_sock, "startgame 0 %d", my_playernum);
487 } else if (strcasecmp(partyline_buffer, "/pause") == 0) {
488 sockprintf(server_sock, "pause 1 %d", my_playernum);
489 } else if (strcasecmp(partyline_buffer, "/unpause") == 0) {
490 sockprintf(server_sock, "pause 0 %d", my_playernum);
491 } else if (strncasecmp(partyline_buffer, "/team", 5) == 0) {
492 if (strlen(partyline_buffer) == 5)
493 strcpy(partyline_buffer+5, " "); /* make it "/team " */
494 sockprintf(server_sock, "team %d %s", my_playernum, partyline_buffer+6);
495 if (partyline_buffer[6]) {
496 if (teams[my_playernum-1])
497 free(teams[my_playernum-1]);
498 teams[my_playernum-1] = strdup(partyline_buffer+6);
499 snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[my_playernum-1], partyline_buffer+6);
500 io->draw_text(BUFFER_PLINE, buf);
501 } else {
502 if (teams[my_playernum-1])
503 free(teams[my_playernum-1]);
504 teams[my_playernum-1] = NULL;
505 snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[my_playernum-1]);
506 io->draw_text(BUFFER_PLINE, buf);
508 } else {
509 sockprintf(server_sock, "pline %d %s", my_playernum, partyline_buffer);
510 if (*partyline_buffer != '/'
511 || partyline_buffer[1] == 0 || partyline_buffer[1] == ' ') {
512 /* We do not show server-side commands. */
513 snprintf(buf, sizeof(buf), "<%s> %s", players[my_playernum-1], partyline_buffer);
514 io->draw_text(BUFFER_PLINE, buf);
517 partyline_pos = 0;
518 *partyline_buffer = 0;
519 io->draw_partyline_input(partyline_buffer, partyline_pos);
523 #undef curpos
525 /*************************************************************************/
526 /*************************************************************************/
528 void help()
530 fprintf(stderr,
531 "Tetrinet " VERSION " - Text-mode tetrinet client\n"
532 "\n"
533 "Usage: tetrinet [OPTION]... NICK SERVER\n"
534 "\n"
535 "Options (see README for details):\n"
536 " -fancy Use \"fancy\" TTY graphics.\n"
537 " -fast Connect to the server in the tetrifast mode.\n"
538 " -log <file> Log network traffic to the given file.\n"
539 " -noslide Do not allow pieces to \"slide\" after being dropped\n"
540 " with the spacebar.\n"
541 " -server Start the server instead of the client.\n"
542 " -slide Opposite of -noslide; allows pieces to \"slide\" after\n"
543 " being dropped. If both -slide and -noslide are given,\n"
544 " -slide takes precedence.\n"
545 " -windows Behave as much like the Windows version of Tetrinet as\n"
546 " possible. Implies -noslide.\n"
550 int init(int ac, char **av)
552 int i;
553 char *nick = NULL, *server = NULL;
554 char buf[1024];
555 char nickmsg[1024];
556 unsigned char ip[4];
557 char iphashbuf[32];
558 int len;
559 int start_server = 0; /* Start the server? (-server) */
560 int slide = 0; /* Do we definitely want to slide? (-slide) */
563 /* If there's a DISPLAY variable set in the environment, default to
564 * Xwindows I/O, else default to terminal I/O. */
565 if (getenv("DISPLAY"))
566 io = &xwin_interface;
567 else
568 io = &tty_interface;
569 io=&tty_interface; /* because Xwin isn't done yet */
571 srand(time(NULL));
572 init_shapes();
574 for (i = 1; i < ac; i++) {
575 if (*av[i] == '-') {
576 if (strcmp(av[i], "-server") == 0) {
577 start_server = 1;
578 } else if (strcmp(av[i], "-fancy") == 0) {
579 fancy = 1;
580 } else if (strcmp(av[i], "-log") == 0) {
581 log = 1;
582 i++;
583 if (i >= ac) {
584 fprintf(stderr, "Option -log requires an argument\n");
585 return 1;
587 logname = av[i];
588 } else if (strcmp(av[i], "-noslide") == 0) {
589 noslide = 1;
590 } else if (strcmp(av[i], "-slide") == 0) {
591 slide = 1;
592 } else if (strcmp(av[i], "-windows") == 0) {
593 windows_mode = 1;
594 noslide = 1;
595 } else if (strcmp(av[i], "-fast") == 0) {
596 tetrifast = 1;
597 } else {
598 fprintf(stderr, "Unknown option %s\n", av[i]);
599 help();
600 return 1;
602 } else if (!nick) {
603 my_nick = nick = av[i];
604 } else if (!server) {
605 server = av[i];
606 } else {
607 help();
608 return 1;
611 if (slide)
612 noslide = 0;
613 if (start_server)
614 exit(server_main());
615 if (!server) {
616 help();
617 return 1;
619 if (strlen(nick) > 63) /* put a reasonable limit on nick length */
620 nick[63] = 0;
622 if ((server_sock = conn(server, 31457, ip)) < 0) {
623 fprintf(stderr, "Couldn't connect to server %s: %s\n",
624 server, strerror(errno));
625 return 1;
627 sprintf(nickmsg, "tetri%s %s 1.13", tetrifast ? "faster" : "sstart", nick);
628 sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17);
629 /* buf[0] does not need to be initialized for this algorithm */
630 len = strlen(nickmsg);
631 for (i = 0; i < len; i++)
632 buf[i+1] = (((buf[i]&0xFF) + (nickmsg[i]&0xFF)) % 255) ^ iphashbuf[i % strlen(iphashbuf)];
633 len++;
634 for (i = 0; i < len; i++)
635 sprintf(nickmsg+i*2, "%02X", buf[i] & 0xFF);
636 sputs(nickmsg, server_sock);
638 do {
639 if (!sgets(buf, sizeof(buf), server_sock)) {
640 fprintf(stderr, "Server %s closed connection\n", server);
641 disconn(server_sock);
642 return 1;
644 parse(buf);
645 } while (my_playernum < 0);
646 sockprintf(server_sock, "team %d ", my_playernum);
648 players[my_playernum-1] = strdup(nick);
649 dispmode = MODE_PARTYLINE;
650 io->screen_setup();
651 io->setup_partyline();
653 return 0;
656 /*************************************************************************/
658 int main(int ac, char **av)
660 int i;
662 if ((i = init(ac, av)) != 0)
663 return i;
665 for (;;) {
666 int timeout;
667 if (playing_game && !game_paused)
668 timeout = tetris_timeout();
669 else
670 timeout = -1;
671 i = io->wait_for_input(timeout);
672 if (i == -1) {
673 char buf[1024];
674 if (sgets(buf, sizeof(buf), server_sock))
675 parse(buf);
676 else {
677 io->draw_text(BUFFER_PLINE, "*** Disconnected from Server");
678 break;
680 } else if (i == -2) {
681 tetris_timeout_action();
682 } else if (i == 12) { /* Ctrl-L */
683 io->screen_redraw();
684 } else if (i == K_F10) {
685 break; /* out of main loop */
686 } else if (i == K_F1) {
687 if (dispmode != MODE_FIELDS) {
688 dispmode = MODE_FIELDS;
689 io->setup_fields();
691 } else if (i == K_F2) {
692 if (dispmode != MODE_PARTYLINE) {
693 dispmode = MODE_PARTYLINE;
694 io->setup_partyline();
696 } else if (i == K_F3) {
697 if (dispmode != MODE_WINLIST) {
698 dispmode = MODE_WINLIST;
699 io->setup_winlist();
701 } else if (dispmode == MODE_FIELDS) {
702 tetris_input(i);
703 } else if (dispmode == MODE_PARTYLINE) {
704 if (i == 8 || i == 127) /* Backspace or Delete */
705 partyline_backspace();
706 else if (i == 4) /* Ctrl-D */
707 partyline_delete();
708 else if (i == 21) /* Ctrl-U */
709 partyline_kill();
710 else if (i == '\r' || i == '\n')
711 partyline_enter();
712 else if (i == K_LEFT)
713 partyline_move(-1);
714 else if (i == K_RIGHT)
715 partyline_move(1);
716 else if (i == 1) /* Ctrl-A */
717 partyline_move(-2);
718 else if (i == 5) /* Ctrl-E */
719 partyline_move(2);
720 else if (i >= 1 && i <= 0xFF)
721 partyline_input(i);
725 disconn(server_sock);
726 return 0;
729 /*************************************************************************/
731 #endif /* !SERVER_ONLY */
733 /*************************************************************************/