1 /* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
2 * This program is public domain.
4 * Tetrinet main program.
7 /*************************************************************************/
21 /*************************************************************************/
23 int fancy
= 0; /* Fancy TTY graphics? */
24 int log
= 0; /* Log network traffic to file? */
25 char *logname
; /* Log filename */
26 int windows_mode
= 0; /* Try to be just like the Windows version? */
27 int noslide
= 0; /* Disallow piece sliding? */
28 int tetrifast
= 0; /* TetriFast mode? */
29 int cast_shadow
= 1; /* Make pieces cast shadow? */
31 int my_playernum
= -1; /* What player number are we? */
32 char *my_nick
; /* And what is our nick? */
33 WinInfo winlist
[MAXWINLIST
]; /* Winners' list from server */
34 int server_sock
; /* Socket for server communication */
35 int dispmode
; /* Current display mode */
36 char *players
[6]; /* Player names (NULL for no such player) */
37 char *teams
[6]; /* Team names (NULL for not on a team) */
38 int playing_game
; /* Are we currently playing a game? */
39 int not_playing_game
; /* Are we currently watching people play a game? */
40 int game_paused
; /* Is the game currently paused? */
42 Interface
*io
; /* Input/output routines */
44 /*************************************************************************/
45 /*************************************************************************/
49 /*************************************************************************/
51 /* Output message to a message buffer, possibly decoding the text attributes
54 void msg_text(int bufnum
, const unsigned char *s
)
56 /* Stolen from gtetrinet: (leading space <=> undefined) */
57 static enum tattr map
[32] = {
61 TATTR_CCYAN
| TATTR_CXBRIGHT
,
63 TATTR_CBLUE
| TATTR_CXBRIGHT
,
69 TATTR_CBLACK
| TATTR_CXBRIGHT
,
72 TATTR_CGREEN
| TATTR_CXBRIGHT
,
77 TATTR_CMAGENTA
| TATTR_CXBRIGHT
,
78 TATTR_CRED
| TATTR_CXBRIGHT
,
82 TATTR_CGREY
| TATTR_CXBRIGHT
,
83 TATTR_CBROWN
| TATTR_CXBRIGHT
,
91 unsigned char tb
[1024], *t
;
93 for (t
= tb
; *s
&& t
- tb
< 1024; t
++, s
++) {
97 *t
= map
[(int) *s
] + 1;
102 if (t
- tb
>= 1024) t
= &tb
[1023];
105 io
->draw_text(bufnum
, tb
);
109 /* Parse a line from the server. Destroys the buffer it's given as a side
113 void parse(char *buf
)
117 cmd
= strtok(buf
, " ");
122 } else if (strcmp(cmd
, "noconnecting") == 0) {
123 s
= strtok(NULL
, "");
126 /* XXX not to stderr, please! -- we need to stay running w/o server */
127 fprintf(stderr
, "Server error: %s\n", s
);
130 } else if (strcmp(cmd
, "winlist") == 0) {
133 while (i
< MAXWINLIST
&& (s
= strtok(NULL
, " "))) {
143 strncpy(winlist
[i
].name
, s
, sizeof(winlist
[i
].name
)-1);
144 winlist
[i
].name
[sizeof(winlist
[i
].name
)] = 0;
145 winlist
[i
].points
= atoi(t
);
146 if ((t
= strchr(t
, ';')) != NULL
)
147 winlist
[i
].games
= atoi(t
+1);
151 winlist
[i
].name
[0] = 0;
152 if (dispmode
== MODE_WINLIST
)
155 } else if (strcmp(cmd
, tetrifast
? ")#)(!@(*3" : "playernum") == 0) {
156 if ((s
= strtok(NULL
, " ")))
157 my_playernum
= atoi(s
);
158 /* Note: players[my_playernum-1] is set in init() */
159 /* But that doesn't work when joining other channel. */
160 players
[my_playernum
-1] = strdup(my_nick
);
162 } else if (strcmp(cmd
, "playerjoin") == 0) {
166 s
= strtok(NULL
, " ");
167 t
= strtok(NULL
, "");
171 if (player
< 0 || player
> 5)
173 players
[player
] = strdup(t
);
176 teams
[player
] = NULL
;
178 snprintf(buf
, sizeof(buf
), "*** %s is Now Playing", t
);
179 msg_text(BUFFER_PLINE
, buf
);
180 if (dispmode
== MODE_FIELDS
)
183 } else if (strcmp(cmd
, "playerleave") == 0) {
187 s
= strtok(NULL
, " ");
191 if (player
< 0 || player
> 5 || !players
[player
])
193 snprintf(buf
, sizeof(buf
), "*** %s has Left", players
[player
]);
194 msg_text(BUFFER_PLINE
, buf
);
195 free(players
[player
]);
196 players
[player
] = NULL
;
197 if (dispmode
== MODE_FIELDS
)
200 } else if (strcmp(cmd
, "team") == 0) {
204 s
= strtok(NULL
, " ");
205 t
= strtok(NULL
, "");
209 if (player
< 0 || player
> 5 || !players
[player
])
214 teams
[player
] = strdup(t
);
216 teams
[player
] = NULL
;
218 snprintf(buf
, sizeof(buf
), "*** %s is Now on Team %s", players
[player
], t
);
220 snprintf(buf
, sizeof(buf
), "*** %s is Now Alone", players
[player
]);
221 msg_text(BUFFER_PLINE
, buf
);
223 } else if (strcmp(cmd
, "pline") == 0) {
225 char buf
[1024], *name
;
227 s
= strtok(NULL
, " ");
228 t
= strtok(NULL
, "");
233 playernum
= atoi(s
)-1;
234 if (playernum
== -1) {
237 if (playernum
< 0 || playernum
> 5 || !players
[playernum
])
239 name
= players
[playernum
];
241 snprintf(buf
, sizeof(buf
), "<%s> %s", name
, t
);
242 msg_text(BUFFER_PLINE
, buf
);
244 } else if (strcmp(cmd
, "plineact") == 0) {
246 char buf
[1024], *name
;
248 s
= strtok(NULL
, " ");
249 t
= strtok(NULL
, "");
254 playernum
= atoi(s
)-1;
255 if (playernum
== -1) {
258 if (playernum
< 0 || playernum
> 5 || !players
[playernum
])
260 name
= players
[playernum
];
262 snprintf(buf
, sizeof(buf
), "* %s %s", name
, t
);
263 msg_text(BUFFER_PLINE
, buf
);
265 } else if (strcmp(cmd
, tetrifast
? "*******" : "newgame") == 0) {
268 if ((s
= strtok(NULL
, " ")))
270 if ((s
= strtok(NULL
, " ")))
271 initial_level
= atoi(s
);
272 if ((s
= strtok(NULL
, " ")))
273 lines_per_level
= atoi(s
);
274 if ((s
= strtok(NULL
, " ")))
276 if ((s
= strtok(NULL
, " ")))
277 special_lines
= atoi(s
);
278 if ((s
= strtok(NULL
, " ")))
279 special_count
= atoi(s
);
280 if ((s
= strtok(NULL
, " "))) {
281 special_capacity
= atoi(s
);
282 if (special_capacity
> MAX_SPECIALS
)
283 special_capacity
= MAX_SPECIALS
;
285 if ((s
= strtok(NULL
, " "))) {
286 memset(piecefreq
, 0, sizeof(piecefreq
));
294 if ((s
= strtok(NULL
, " "))) {
295 memset(specialfreq
, 0, sizeof(specialfreq
));
303 if ((s
= strtok(NULL
, " ")))
304 level_average
= atoi(s
);
305 if ((s
= strtok(NULL
, " ")))
308 for (i
= 0; i
< 6; i
++)
309 levels
[i
] = initial_level
;
310 memset(&fields
[my_playernum
-1], 0, sizeof(Field
));
312 io
->clear_text(BUFFER_GMSG
);
313 io
->clear_text(BUFFER_ATTDEF
);
317 msg_text(BUFFER_PLINE
, "*** The Game Has Started");
318 if (dispmode
!= MODE_FIELDS
) {
319 dispmode
= MODE_FIELDS
;
323 } else if (strcmp(cmd
, "ingame") == 0) {
324 /* Sent when a player connects in the middle of a game */
328 s
= buf
+ sprintf(buf
, "f %d ", my_playernum
);
329 for (y
= 0; y
< FIELD_HEIGHT
; y
++) {
330 for (x
= 0; x
< FIELD_WIDTH
; x
++) {
331 fields
[my_playernum
-1][y
][x
] = rand()%5 + 1;
332 *s
++ = '0' + fields
[my_playernum
-1][y
][x
];
336 sputs(buf
, server_sock
);
338 not_playing_game
= 1;
340 } else if (strcmp(cmd
, "pause") == 0) {
341 if ((s
= strtok(NULL
, " ")))
342 game_paused
= atoi(s
);
344 msg_text(BUFFER_PLINE
, "*** The Game Has Been Paused");
345 msg_text(BUFFER_GMSG
, "*** The Game Has Been Paused");
347 msg_text(BUFFER_PLINE
, "*** The Game Has Been Unpaused");
348 msg_text(BUFFER_GMSG
, "*** The Game Has Been Unpaused");
351 } else if (strcmp(cmd
, "endgame") == 0) {
353 not_playing_game
= 0;
354 memset(fields
, 0, sizeof(fields
));
356 io
->clear_text(BUFFER_ATTDEF
);
357 msg_text(BUFFER_PLINE
, "*** The Game Has Ended");
358 if (dispmode
== MODE_FIELDS
) {
360 io
->draw_own_field();
361 for (i
= 1; i
<= 6; i
++) {
362 if (i
!= my_playernum
)
363 io
->draw_other_field(i
);
366 if (dispmode
!= MODE_PARTYLINE
) {
367 dispmode
= MODE_PARTYLINE
;
368 io
->setup_partyline();
371 } else if (strcmp(cmd
, "playerwon") == 0) {
372 /* Syntax: playerwon # -- sent when all but one player lose */
374 } else if (strcmp(cmd
, "playerlost") == 0) {
375 /* Syntax: playerlost # -- sent after playerleave on disconnect
376 * during a game, or when a player loses (sent by the losing
377 * player and from the server to all other players */
379 } else if (strcmp(cmd
, "f") == 0) { /* field */
380 int player
, x
, y
, tile
;
382 /* This looks confusing, but what it means is, ignore this message
383 * if a game isn't going on. */
384 if (!playing_game
&& !not_playing_game
)
386 if (!(s
= strtok(NULL
, " ")))
390 if (!(s
= strtok(NULL
, "")))
393 /* Set field directly */
394 char *ptr
= (char *) fields
[player
];
397 *ptr
++ = (*s
++) - '0';
399 case 'a': *ptr
++ = 6 + SPECIAL_A
; break;
400 case 'b': *ptr
++ = 6 + SPECIAL_B
; break;
401 case 'c': *ptr
++ = 6 + SPECIAL_C
; break;
402 case 'g': *ptr
++ = 6 + SPECIAL_G
; break;
403 case 'n': *ptr
++ = 6 + SPECIAL_N
; break;
404 case 'o': *ptr
++ = 6 + SPECIAL_O
; break;
405 case 'q': *ptr
++ = 6 + SPECIAL_Q
; break;
406 case 'r': *ptr
++ = 6 + SPECIAL_R
; break;
407 case 's': *ptr
++ = 6 + SPECIAL_S
; break;
411 /* Set specific locations on field */
419 fields
[player
][y
][x
] = tile
;
424 if (player
== my_playernum
-1)
425 io
->draw_own_field();
427 io
->draw_other_field(player
+1);
428 } else if (strcmp(cmd
, "lvl") == 0) {
431 if (!(s
= strtok(NULL
, " ")))
434 if (!(s
= strtok(NULL
, "")))
436 levels
[player
] = atoi(s
);
438 } else if (strcmp(cmd
, "sb") == 0) {
442 if (!(s
= strtok(NULL
, " ")))
445 if (!(type
= strtok(NULL
, " ")))
447 if (!(s
= strtok(NULL
, " ")))
450 do_special(type
, from
, to
);
452 } else if (strcmp(cmd
, "gmsg") == 0) {
453 if (!(s
= strtok(NULL
, "")))
455 msg_text(BUFFER_GMSG
, s
);
460 /*************************************************************************/
461 /*************************************************************************/
463 static char partyline_buffer
[512];
464 static int partyline_pos
= 0;
466 #define curpos (partyline_buffer+partyline_pos)
468 /*************************************************************************/
470 /* Add a character to the partyline buffer. */
472 void partyline_input(int c
)
474 if (partyline_pos
< sizeof(partyline_buffer
) - 1) {
475 memmove(curpos
+1, curpos
, strlen(curpos
)+1);
476 partyline_buffer
[partyline_pos
++] = c
;
477 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
481 /*************************************************************************/
483 /* Delete the current character from the partyline buffer. */
485 void partyline_delete(void)
487 if (partyline_buffer
[partyline_pos
]) {
488 memmove(curpos
, curpos
+1, strlen(curpos
)-1+1);
489 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
493 /*************************************************************************/
495 /* Backspace a character from the partyline buffer. */
497 void partyline_backspace(void)
499 if (partyline_pos
> 0) {
505 /*************************************************************************/
507 /* Kill the entire partyline input buffer. */
509 void partyline_kill(void)
512 *partyline_buffer
= 0;
513 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
516 /*************************************************************************/
518 /* Move around the input buffer. Sign indicates direction; absolute value
519 * of 1 means one character, 2 means the whole line.
522 void partyline_move(int how
)
526 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
527 } else if (how
== -1 && partyline_pos
> 0) {
529 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
530 } else if (how
== 1 && partyline_buffer
[partyline_pos
]) {
532 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
533 } else if (how
== 2) {
534 partyline_pos
= strlen(partyline_buffer
);
535 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
539 /*************************************************************************/
541 /* Send the input line to the server. */
543 void partyline_enter(void)
547 if (*partyline_buffer
) {
548 if (strncasecmp(partyline_buffer
, "/me ", 4) == 0) {
549 sockprintf(server_sock
, "plineact %d %s", my_playernum
, partyline_buffer
+4);
550 snprintf(buf
, sizeof(buf
), "* %s %s", players
[my_playernum
-1], partyline_buffer
+4);
551 msg_text(BUFFER_PLINE
, buf
);
552 } else if (strcasecmp(partyline_buffer
, "/start") == 0) {
553 sockprintf(server_sock
, "startgame 1 %d", my_playernum
);
554 } else if (strcasecmp(partyline_buffer
, "/end") == 0) {
555 sockprintf(server_sock
, "startgame 0 %d", my_playernum
);
556 } else if (strcasecmp(partyline_buffer
, "/pause") == 0) {
557 sockprintf(server_sock
, "pause 1 %d", my_playernum
);
558 } else if (strcasecmp(partyline_buffer
, "/unpause") == 0) {
559 sockprintf(server_sock
, "pause 0 %d", my_playernum
);
560 } else if (strncasecmp(partyline_buffer
, "/team", 5) == 0) {
561 if (strlen(partyline_buffer
) == 5)
562 strcpy(partyline_buffer
+5, " "); /* make it "/team " */
563 sockprintf(server_sock
, "team %d %s", my_playernum
, partyline_buffer
+6);
564 if (partyline_buffer
[6]) {
565 if (teams
[my_playernum
-1])
566 free(teams
[my_playernum
-1]);
567 teams
[my_playernum
-1] = strdup(partyline_buffer
+6);
568 snprintf(buf
, sizeof(buf
), "*** %s is Now on Team %s", players
[my_playernum
-1], partyline_buffer
+6);
569 msg_text(BUFFER_PLINE
, buf
);
571 if (teams
[my_playernum
-1])
572 free(teams
[my_playernum
-1]);
573 teams
[my_playernum
-1] = NULL
;
574 snprintf(buf
, sizeof(buf
), "*** %s is Now Alone", players
[my_playernum
-1]);
575 msg_text(BUFFER_PLINE
, buf
);
578 sockprintf(server_sock
, "pline %d %s", my_playernum
, partyline_buffer
);
579 if (*partyline_buffer
!= '/'
580 || partyline_buffer
[1] == 0 || partyline_buffer
[1] == ' ') {
581 /* We do not show server-side commands. */
582 snprintf(buf
, sizeof(buf
), "<%s> %s", players
[my_playernum
-1], partyline_buffer
);
583 msg_text(BUFFER_PLINE
, buf
);
587 *partyline_buffer
= 0;
588 io
->draw_partyline_input(partyline_buffer
, partyline_pos
);
594 /*************************************************************************/
595 /*************************************************************************/
600 "Tetrinet " VERSION
" - Text-mode tetrinet client\n"
602 "Usage: tetrinet [OPTION]... NICK SERVER\n"
604 "Options (see README for details):\n"
605 " -fancy Use \"fancy\" TTY graphics.\n"
606 " -fast Connect to the server in the tetrifast mode.\n"
607 " -log <file> Log network traffic to the given file.\n"
608 " -noshadow Do not make the pieces cast shadow.\n"
609 " -noslide Do not allow pieces to \"slide\" after being dropped\n"
610 " with the spacebar.\n"
611 " -server Start the server instead of the client.\n"
612 " -shadow Make the pieces cast shadow. Can speed up gameplay\n"
613 " considerably, but it can be considered as cheating by\n"
614 " some people since some other tetrinet clients lack this.\n"
615 " -slide Opposite of -noslide; allows pieces to \"slide\" after\n"
616 " being dropped. If both -slide and -noslide are given,\n"
617 " -slide takes precedence.\n"
618 " -windows Behave as much like the Windows version of Tetrinet as\n"
619 " possible. Implies -noslide and -noshadow.\n"
623 int init(int ac
, char **av
)
626 char *nick
= NULL
, *server
= NULL
;
632 #ifdef BUILTIN_SERVER
633 int start_server
= 0; /* Start the server? (-server) */
635 int slide
= 0; /* Do we definitely want to slide? (-slide) */
638 /* If there's a DISPLAY variable set in the environment, default to
639 * Xwindows I/O, else default to terminal I/O. */
640 /* if (getenv("DISPLAY"))
641 io = &xwin_interface;
643 io = &tty_interface; */
644 io
=&tty_interface
; /* because Xwin isn't done yet */
649 for (i
= 1; i
< ac
; i
++) {
651 #ifdef BUILTIN_SERVER
652 if (strcmp(av
[i
], "-server") == 0) {
656 if (strcmp(av
[i
], "-fancy") == 0) {
658 } else if (strcmp(av
[i
], "-log") == 0) {
662 fprintf(stderr
, "Option -log requires an argument\n");
666 } else if (strcmp(av
[i
], "-noslide") == 0) {
668 } else if (strcmp(av
[i
], "-noshadow") == 0) {
670 } else if (strcmp(av
[i
], "-shadow") == 0) {
672 } else if (strcmp(av
[i
], "-slide") == 0) {
674 } else if (strcmp(av
[i
], "-windows") == 0) {
678 } else if (strcmp(av
[i
], "-fast") == 0) {
681 fprintf(stderr
, "Unknown option %s\n", av
[i
]);
686 my_nick
= nick
= av
[i
];
687 } else if (!server
) {
696 #ifdef BUILTIN_SERVER
704 if (strlen(nick
) > 63) /* put a reasonable limit on nick length */
707 if ((server_sock
= conn(server
, 31457, ip
)) < 0) {
708 fprintf(stderr
, "Couldn't connect to server %s: %s\n",
709 server
, strerror(errno
));
712 sprintf(nickmsg
, "tetri%s %s 1.13", tetrifast
? "faster" : "sstart", nick
);
713 sprintf(iphashbuf
, "%d", ip
[0]*54 + ip
[1]*41 + ip
[2]*29 + ip
[3]*17);
714 /* buf[0] does not need to be initialized for this algorithm */
715 len
= strlen(nickmsg
);
716 for (i
= 0; i
< len
; i
++)
717 buf
[i
+1] = (((buf
[i
]&0xFF) + (nickmsg
[i
]&0xFF)) % 255) ^ iphashbuf
[i
% strlen(iphashbuf
)];
719 for (i
= 0; i
< len
; i
++)
720 sprintf(nickmsg
+i
*2, "%02X", buf
[i
] & 0xFF);
721 sputs(nickmsg
, server_sock
);
724 if (!sgets(buf
, sizeof(buf
), server_sock
)) {
725 fprintf(stderr
, "Server %s closed connection\n", server
);
726 disconn(server_sock
);
730 } while (my_playernum
< 0);
731 sockprintf(server_sock
, "team %d ", my_playernum
);
733 players
[my_playernum
-1] = strdup(nick
);
734 dispmode
= MODE_PARTYLINE
;
736 io
->setup_partyline();
741 /*************************************************************************/
743 int main(int ac
, char **av
)
747 if ((i
= init(ac
, av
)) != 0)
752 if (playing_game
&& !game_paused
)
753 timeout
= tetris_timeout();
756 i
= io
->wait_for_input(timeout
);
759 if (sgets(buf
, sizeof(buf
), server_sock
))
762 msg_text(BUFFER_PLINE
, "*** Disconnected from Server");
765 } else if (i
== -2) {
766 tetris_timeout_action();
767 } else if (i
== 12) { /* Ctrl-L */
769 } else if (i
== K_F10
) {
770 break; /* out of main loop */
771 } else if (i
== K_F1
) {
772 if (dispmode
!= MODE_FIELDS
) {
773 dispmode
= MODE_FIELDS
;
776 } else if (i
== K_F2
) {
777 if (dispmode
!= MODE_PARTYLINE
) {
778 dispmode
= MODE_PARTYLINE
;
779 io
->setup_partyline();
781 } else if (i
== K_F3
) {
782 if (dispmode
!= MODE_WINLIST
) {
783 dispmode
= MODE_WINLIST
;
786 } else if (dispmode
== MODE_FIELDS
) {
788 } else if (dispmode
== MODE_PARTYLINE
) {
789 if (i
== 8 || i
== 127) /* Backspace or Delete */
790 partyline_backspace();
791 else if (i
== 4) /* Ctrl-D */
793 else if (i
== 21) /* Ctrl-U */
795 else if (i
== '\r' || i
== '\n')
797 else if (i
== K_LEFT
)
799 else if (i
== K_RIGHT
)
801 else if (i
== 1) /* Ctrl-A */
803 else if (i
== 5) /* Ctrl-E */
805 else if (i
>= 1 && i
<= 0xFF)
810 disconn(server_sock
);
814 /*************************************************************************/
816 #endif /* !SERVER_ONLY */
818 /*************************************************************************/