1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
4 * Tetrinet main program.
7 /*************************************************************************/
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 /*************************************************************************/
46 /*************************************************************************/
48 /* Parse a line from the server. Destroys the buffer it's given as a side
56 cmd
= strtok(buf
, " ");
61 } else if (strcmp(cmd
, "noconnecting") == 0) {
65 /* XXX not to stderr, please! -- we need to stay running w/o server */
66 fprintf(stderr
, "Server error: %s\n", s
);
69 } else if (strcmp(cmd
, "winlist") == 0) {
72 while (i
< MAXWINLIST
&& (s
= strtok(NULL
, " "))) {
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);
90 winlist
[i
].name
[0] = 0;
91 if (dispmode
== MODE_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) {
105 s
= strtok(NULL
, " ");
106 t
= strtok(NULL
, "");
110 if (player
< 0 || player
> 5)
112 players
[player
] = strdup(t
);
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
)
122 } else if (strcmp(cmd
, "playerleave") == 0) {
126 s
= strtok(NULL
, " ");
130 if (player
< 0 || player
> 5 || !players
[player
])
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
)
139 } else if (strcmp(cmd
, "team") == 0) {
143 s
= strtok(NULL
, " ");
144 t
= strtok(NULL
, "");
148 if (player
< 0 || player
> 5 || !players
[player
])
153 teams
[player
] = strdup(t
);
155 teams
[player
] = NULL
;
157 snprintf(buf
, sizeof(buf
), "*** %s is Now on Team %s", players
[player
], t
);
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) {
164 char buf
[1024], *name
;
166 s
= strtok(NULL
, " ");
167 t
= strtok(NULL
, "");
172 playernum
= atoi(s
)-1;
173 if (playernum
== -1) {
176 if (playernum
< 0 || playernum
> 5 || !players
[playernum
])
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) {
185 char buf
[1024], *name
;
187 s
= strtok(NULL
, " ");
188 t
= strtok(NULL
, "");
193 playernum
= atoi(s
)-1;
194 if (playernum
== -1) {
197 if (playernum
< 0 || playernum
> 5 || !players
[playernum
])
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) {
207 if (s
= strtok(NULL
, " "))
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
, " "))
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
));
233 if (s
= strtok(NULL
, " ")) {
234 memset(specialfreq
, 0, sizeof(specialfreq
));
242 if (s
= strtok(NULL
, " "))
243 level_average
= atoi(s
);
244 if (s
= strtok(NULL
, " "))
247 for (i
= 0; i
< 6; i
++)
248 levels
[i
] = initial_level
;
249 memset(&fields
[my_playernum
-1], 0, sizeof(Field
));
251 io
->clear_text(BUFFER_GMSG
);
252 io
->clear_text(BUFFER_ATTDEF
);
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 */
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
];
271 sputs(buf
, server_sock
);
273 not_playing_game
= 1;
275 } else if (strcmp(cmd
, "pause") == 0) {
276 if (s
= strtok(NULL
, " "))
277 game_paused
= atoi(s
);
279 io
->draw_text(BUFFER_PLINE
, "*** The Game Has Been Paused");
280 io
->draw_text(BUFFER_GMSG
, "*** The Game Has Been Paused");
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) {
288 not_playing_game
= 0;
289 memset(fields
, 0, sizeof(fields
));
291 io
->clear_text(BUFFER_ATTDEF
);
292 io
->draw_text(BUFFER_PLINE
, "*** The Game Has Ended");
293 if (dispmode
== MODE_FIELDS
) {
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
)
317 if (!(s
= strtok(NULL
, " ")))
321 if (!(s
= strtok(NULL
, "")))
324 /* Set field directly */
325 char *ptr
= (char *) fields
[player
];
328 *ptr
++ = (*s
++) - '0';
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;
342 /* Set specific locations on field */
350 fields
[player
][y
][x
] = tile
;
355 if (player
== my_playernum
-1)
356 io
->draw_own_field();
358 io
->draw_other_field(player
+1);
359 } else if (strcmp(cmd
, "lvl") == 0) {
362 if (!(s
= strtok(NULL
, " ")))
365 if (!(s
= strtok(NULL
, "")))
367 levels
[player
] = atoi(s
);
369 } else if (strcmp(cmd
, "sb") == 0) {
373 if (!(s
= strtok(NULL
, " ")))
376 if (!(type
= strtok(NULL
, " ")))
378 if (!(s
= strtok(NULL
, " ")))
381 do_special(type
, from
, to
);
383 } else if (strcmp(cmd
, "gmsg") == 0) {
384 if (!(s
= strtok(NULL
, "")))
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) {
436 /*************************************************************************/
438 /* Kill the entire partyline input buffer. */
440 void partyline_kill(void)
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
)
457 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
458 } else if (how
== -1 && partyline_pos
> 0) {
460 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
461 } else if (how
== 1 && partyline_buffer
[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)
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
);
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
);
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
);
518 *partyline_buffer
= 0;
519 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
525 /*************************************************************************/
526 /*************************************************************************/
531 "Tetrinet " VERSION
" - Text-mode tetrinet client\n"
533 "Usage: tetrinet [OPTION]... NICK SERVER\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
)
553 char *nick
= NULL
, *server
= NULL
;
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
;
569 io
=&tty_interface
; /* because Xwin isn't done yet */
574 for (i
= 1; i
< ac
; i
++) {
576 if (strcmp(av
[i
], "-server") == 0) {
578 } else if (strcmp(av
[i
], "-fancy") == 0) {
580 } else if (strcmp(av
[i
], "-log") == 0) {
584 fprintf(stderr
, "Option -log requires an argument\n");
588 } else if (strcmp(av
[i
], "-noslide") == 0) {
590 } else if (strcmp(av
[i
], "-slide") == 0) {
592 } else if (strcmp(av
[i
], "-windows") == 0) {
595 } else if (strcmp(av
[i
], "-fast") == 0) {
598 fprintf(stderr
, "Unknown option %s\n", av
[i
]);
603 my_nick
= nick
= av
[i
];
604 } else if (!server
) {
619 if (strlen(nick
) > 63) /* put a reasonable limit on nick length */
622 if ((server_sock
= conn(server
, 31457, ip
)) < 0) {
623 fprintf(stderr
, "Couldn't connect to server %s: %s\n",
624 server
, strerror(errno
));
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
)];
634 for (i
= 0; i
< len
; i
++)
635 sprintf(nickmsg
+i
*2, "%02X", buf
[i
] & 0xFF);
636 sputs(nickmsg
, server_sock
);
639 if (!sgets(buf
, sizeof(buf
), server_sock
)) {
640 fprintf(stderr
, "Server %s closed connection\n", server
);
641 disconn(server_sock
);
645 } while (my_playernum
< 0);
646 sockprintf(server_sock
, "team %d ", my_playernum
);
648 players
[my_playernum
-1] = strdup(nick
);
649 dispmode
= MODE_PARTYLINE
;
651 io
->setup_partyline();
656 /*************************************************************************/
658 int main(int ac
, char **av
)
662 if ((i
= init(ac
, av
)) != 0)
667 if (playing_game
&& !game_paused
)
668 timeout
= tetris_timeout();
671 i
= io
->wait_for_input(timeout
);
674 if (sgets(buf
, sizeof(buf
), server_sock
))
677 io
->draw_text(BUFFER_PLINE
, "*** Disconnected from Server");
680 } else if (i
== -2) {
681 tetris_timeout_action();
682 } else if (i
== 12) { /* Ctrl-L */
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
;
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
;
701 } else if (dispmode
== MODE_FIELDS
) {
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 */
708 else if (i
== 21) /* Ctrl-U */
710 else if (i
== '\r' || i
== '\n')
712 else if (i
== K_LEFT
)
714 else if (i
== K_RIGHT
)
716 else if (i
== 1) /* Ctrl-A */
718 else if (i
== 5) /* Ctrl-E */
720 else if (i
>= 1 && i
<= 0xFF)
725 disconn(server_sock
);
729 /*************************************************************************/
731 #endif /* !SERVER_ONLY */
733 /*************************************************************************/