tetrinet/0000775000175000017500000000000010671443751012236 5ustar rhondarhondatetrinet/AUTHORS0000664000175000017500000000116710211427506013301 0ustar rhondarhondaPeople who contributed to this tetrinet package, in alphabetical order (if you are missing here or some of your contributions is, tell us): Andrew Church The original implementation The original package maintainer Chris Clark Counterclockwise rotation Christoph Weiss Teamplay fix Cougar IPv6 support Gadall FreeBSD compilation fixes Gerfried Fuchs Extensive random hacking Petr Baudis The current maintainer Multichannel support Tetrifast support Random hacking tetrinet/ChangeLog0000664000175000017500000001371310323152567014011 0ustar rhondarhonda2005-03-02 Wednesday 23:14 Petr Baudis * TODO: The Blocktrix protocol, X11 and TODO rewrite. 2005-03-02 Wednesday 23:08 Petr Baudis * TODO, tetrinet.c: Automagically switch from/to the playfield on game end/start. 2005-03-02 Wednesday 22:55 Petr Baudis * TODO, io.h, tetrinet.c, tty.c: Support for server text attributes in messages. No more ugly control codes. Mixed with a portability fix, use pstring() instead of strstring() - still nothing shiny, but much better than before. 2005-03-02 Wednesday 21:25 Petr Baudis * .vimrc: A fairie. 2005-03-02 Wednesday 21:25 Petr Baudis * AUTHORS: Random small changes. 2004-12-23 Thursday 08:02 alfie * tetrinet-server.6: Small corrections. 2004-12-06 Monday 22:04 Petr Baudis * tty.c: Tell the world if the signal was SIGINT, too. 2004-12-06 Monday 21:40 alfie * TODO, tetris.c: Fixed the clean of the talk window on empty input. 2004-12-06 Monday 21:39 alfie * tty.c: Added some more curs_set 2004-12-06 Monday 19:48 alfie * tetrinet-server.6: added manpage for tetrinet-server 2004-11-23 Tuesday 12:41 alfie * TODO: Filled the TODO list a little more. 2004-11-23 Tuesday 09:00 alfie * tetrinet.6: Finally, the manpage for the client. 2003-12-24 Wednesday 08:28 alfie * tty.c: Add some curs_set calls to show/hide the cursor where appropriate. 2003-11-24 Monday 16:28 Petr Baudis * Makefile: Added -fno-builtin-log to the default CFLAGS. 2003-11-24 Monday 16:26 Petr Baudis * Makefile: Reworked Makefile to contain configuration area. 2003-11-24 Monday 16:16 Petr Baudis * README: Removed notice about xwin. There's no xwin.c ;-). 2003-11-24 Monday 16:16 Petr Baudis * Makefile: Removed legacy xwin.o stuff. 2003-11-24 Monday 16:14 Petr Baudis * server.c: Tiny compilation fix. 2003-11-24 Monday 16:12 Petr Baudis * server.c: Optimized the brute-force decryption. Now it still first tries the correct hash and if doesn't succed it goes for bruteforce. 2003-11-24 Monday 16:08 Petr Baudis * server.c: The decrypting of messages based on IP moved to decrypt_message(). 2003-11-24 Monday 16:04 Petr Baudis * Makefile, server.c: Brute-force decryption of the tetrisstart message, now it works even for clients behind NATs. 2003-11-24 Monday 16:03 Petr Baudis * README: See Makefile for various obscure compile-time switches. 2003-10-24 Friday 10:48 Petr Baudis * tty.c: Removed plenty of useless (and possible confusing and dangerous) signal handlers. 2003-10-05 Sunday 18:39 alfie * Makefile, tetrinet.c, xwin.c: Removed xwin.c for now because it doesn't include anything useful currently. 2003-10-02 Thursday 16:59 Petr Baudis * Makefile: Remove *.o in binonly as well. 2003-10-02 Thursday 16:58 Petr Baudis * Makefile: Introduced maintainer target binonly. 2003-10-02 Thursday 16:35 Petr Baudis * Changes, README, version.h: tetrinet-0.11 2003-09-11 Thursday 20:26 alfie * tty.c: Add support to display team name in playfield. Required to move player name to the left. 2003-09-09 Tuesday 19:15 alfie * sockets.c: #include to silence memcpy and memset warning. 2003-09-07 Sunday 16:30 Petr Baudis * README, version.h: 0.10-pb4 2003-09-07 Sunday 16:29 Petr Baudis * Makefile, server.c, sockets.c, tetrinet.c, tetris.c, tetris.h, tty.c, xwin.c: Added -Wall parameter to the compilation and fixed the tons of warnings. The result is a rather massive cleanup. 2003-09-07 Sunday 16:12 Petr Baudis * README, tetrinet.c, tetrinet.h, tetris.c, tetris.h, tty.c: Added support for pieces casting 'shadow'. This feature is controlled by -shadow/-noshadow options and it is on by default. A little bit more messy than I originally wanted it to :-(. 2003-09-05 Friday 14:32 Petr Baudis * Makefile, README, tetrinet.c: By default the client does not contain the server code anymore. It can still be enabled manually at the compile time. Idea by alfie. 2003-09-05 Friday 11:39 Petr Baudis * README, version.h: 0.10-pb3 2003-09-05 Friday 11:37 Petr Baudis * tetrinet.c, version.h: Include the version information in the usage output. 2003-09-05 Friday 11:33 Petr Baudis * .cvsignore: Ignore compiled binaries. 2003-09-05 Friday 11:25 Petr Baudis * server.c: _Untested_ server-side tetrifast support. 2003-09-05 Friday 11:15 Petr Baudis * README, tetrinet.c: Documented the -fast option. 2003-09-05 Friday 11:09 Petr Baudis * tetrinet.c: Print verbose usage help, also when an unknown option is passed. 2003-09-05 Friday 10:56 Petr Baudis * Changes: -pb branch changes are in ChangeLog. 2003-09-05 Friday 10:54 Petr Baudis * AUTHORS: Put together some AUTHORS file. 2003-09-05 Friday 10:46 Petr Baudis * README: 0.10-pb2 (pb1 was done outside of CVS yet) 2003-09-05 Friday 10:39 Petr Baudis * tetrinet.c, tetrinet.h, tetris.c: Tetrifast support. 2003-09-05 Friday 10:38 Petr Baudis * README, tetrinet.c: Introduced multichannel support (it was rather just a trivial fix). 2003-09-05 Friday 10:37 Petr Baudis * tetrinet.c: Still send unknown commands to the server, patch by Gerfried Fuchs and me. 2003-09-05 Friday 10:30 Petr Baudis * README: Administrative commit - 0.10-pb0. 2003-09-05 Friday 10:28 Petr Baudis * Changes, Makefile, README, TODO, io.h, server.c, server.h, sockets.c, sockets.h, tetrinet.c, tetrinet.h, tetrinet.txt, tetris.c, tetris.h, tty.c, xwin.c: Initial revision tetrinet/Changes0000664000175000017500000000420007737051757013537 0ustar rhondarhondaPlease see the ChangeLog file for future changes. Version 0 --------- 2003/06/24 .10 Unknown commands in partyline mode now cause "unknown command" errors rather than being sent out as ordinary text. 2003/06/16 .9 Fixed bug causing players to never get junk lines if not on a team or the sending player is not on a team. Patch provided by Petr Baudis 2003/04/01 .8 Junk lines are no longer added for double/triple/Tetris clears by team members. Patch provided by Christoph Weiss 2002/08/13 Added IPv6 support. Patch for client provided by Cougar 2002/08/10 Added clean and spotless targets to Makefile. 2002/01/28 .7a Fixed compilation error on FreeBSD. Reported by Gadall 2000/12/09 Added counter to special window, like Windows version. 2000/09/15 .7 Added note on distribution/modification to the README. 2000/09/15 Added counterclockwise rotation. From Chris Clark 2000/08/11 Fixed server bug where game does not end if a player joins during the game. 1999/04/13 Various minor fixes. 1999/01/23 Pause/unpause messages are now printed to the show-fields and partyline text buffers. 1999/01/23 Any player is now permitted to pause the game. 1999/01/23 Lines/Level display now in the right place. 1998/11/12 .6 Server now ends game if only one player is playing and that player leaves before losing. 1998/11/12 Modified display code to use line-drawing characters with "-fancy" option. 1998/11/12 Added support for screens larger than 80x50. 1998/11/12 Added next-piece display in status area. 1998/11/11 You can no longer use a special on a nonexistent player. 1998/11/11 "Switch fields" should now work correctly. 1998/11/03 Added code to send (in "linuxmode") and process game counts for winlist entries. 1998/10/31 Fixed server to not send "classic mode" lines to teammates of a player who clears multiple lines at once. 1998/10/31 Added delay before first piece in a game. 1998/10/31 Server now resends winlist upon receiving SIGHUP. 1998/10/27 .5 Changes file created. tetrinet/Makefile0000664000175000017500000000275507760430677013717 0ustar rhondarhonda######## Configuration area # Your C compiler CC = cc # The passed compilation flags CFLAGS = -O2 -I/usr/include/ncurses -g -Wall -fno-builtin-log # Whether to enable IPv6 support IPV6 = 1 # Whether to have builtin server in the tetrinet client (available through # -server argument) (tetrinet-server will be built always regardless this) # BUILTIN_SERVER = 1 # If you experience random delays and server freezes when accepting new # clients, enable this. # NO_BRUTE_FORCE_DECRYPTION = 1 ######## End of configuration area OBJS = sockets.o tetrinet.o tetris.o tty.o ifdef IPV6 CFLAGS += -DHAVE_IPV6 endif ifdef BUILTIN_SERVER CFLAGS += -DBUILTIN_SERVER OBJS += server.o endif ifdef NO_BRUTE_FORCE_DECRYPTION CFLAGS += -DNO_BRUTE_FORCE_DECRYPTION endif ######## all: tetrinet tetrinet-server install: all cp -p tetrinet tetrinet-server /usr/games clean: rm -f tetrinet tetrinet-server *.o spotless: clean binonly: rm -f *.[cho] Makefile rm -rf CVS/ ######## tetrinet: $(OBJS) $(CC) -o $@ $(OBJS) -lncurses tetrinet-server: server.c sockets.c tetrinet.c tetris.c server.h sockets.h tetrinet.h tetris.h $(CC) $(CFLAGS) -o $@ -DSERVER_ONLY server.c sockets.c tetrinet.c tetris.c .c.o: $(CC) $(CFLAGS) -c $< server.o: server.c tetrinet.h tetris.h server.h sockets.h sockets.o: sockets.c sockets.h tetrinet.h tetrinet.o: tetrinet.c tetrinet.h io.h server.h sockets.h tetris.h tetris.o: tetris.c tetris.h tetrinet.h io.h sockets.h tty.o: tty.c tetrinet.h tetris.h io.h tetrinet.h: io.h tetrinet/README0000664000175000017500000002613407760427326013131 0ustar rhondarhonda Tetrinet for Linux ------------------ by Andrew Church and Petr Baudis Version 0.11 For general information on Tetrinet, consult the file tetrinet.txt (the text file distributed with the original Windows version). The following notes apply to the Linux version of Tetrinet: Distribution/license information -------------------------------- This program is public domain, and may be modified and distributed without limitation. Requirements ------------ You must be using a 50-line text display to run this version of Tetrinet; Xwindows is not yet supported. One option is to open an xterm window in Xwindows and resize it to be 50 lines high. The other option (recommended) is to use a 50-line text console. To get a 50-line text console, if you use LILO to boot, add the following line to the top of your /etc/lilo.conf file: vga = extended run /sbin/lilo, and reboot. If you use a boot disk without LILO, insert it into your floppy drive, give the following command: rdev -v /dev/fd0 -2 and reboot. Another option is to use the SVGATextMode program, available on Sunsite ({http,ftp}://sunsite.unc.edu/pub/Linux/) and other places, to switch your console to 50-line mode without rebooting. You may also use that program to set up a larger display (for example, I use 100x60); Tetrinet will detect this and rearrange the display to make the best use of the available space. Compilation ----------- Type "make". This will generate two programs: "tetrinet" and "tetrinet-server". The former is the main program; the latter is a standalone server. It is recommended to have a brief look at the start of Makefile, it may contain some rather obscure but potentially invaluable compilation switches. It might not be necessary to change anything there at all, but it is good to be aware of the switches' existence. Starting the client ------------------- Tetrinet requires two command-line arguments: your nickname and the server to connect to, in that order. For example: tetrinet MyNick tetrinet.somerandom.net Tetrinet will function only as long as it remains connected to the server; there is no "Client Settings" option as in the Windows version. This may be remedied in a future version. You can also give Tetrinet any of the following options: -fancy Use "fancy" TTY graphics. (Note that this will slow down redraws somewhat.) -fast Use the "tetrifast" mode to connect to the server. This mode eliminates the delay before a new cube appears, thus speeding the game up noticeably. This mode is incompatible with the classic mode and the server has to support it. If in doubt, ask the other players. -log Log network traffic to the given file. All lines start with an absolute time (seconds) in brackets. Lines sent from the client to the server are prefixed with ">>>", and lines from the server to the client are prefixed with "<<<". This could be used with a utility program to replay a game later on (though such a program is not currently included in the Tetrinet distribution.) -noshadow Do not make pieces cast "shadows" when they are slowly falling. (Normally the area under piece is filled by dim dots to help to determine where the piece would hit the ground if one would press the spacebar.) -noslide Do not allow pieces to "slide" after being dropped with the spacebar. (Normally, there is a short time after pressing the spacebar during which a piece can "slide" left or right before it solidifies.) -slide Opposite of -noslide; allows pieces to "slide" after being dropped. If both -slide and -noslide are given, -slide takes precedence. If both -windows and -slide are given, this overrides the "no sliding" part of -windows without affecting the other changes in program behavior. -shadow Opposite of -noshadow; makes pieces cast "shadows". -windows Behave as much like the Windows version of Tetrinet as possible. (See "Differences from Windows Tetrinet".) Implies -noslide and -noshadow. Starting the server ------------------- There are two ways to start the Tetrinet server. One way is to give the "-server" option to the Tetrinet program: tetrinet -server Note that this is the deprecated way and support for this may be removed in the future releases. You must also explicitly enable it in the Makefile during compilation. The other is to run the "tetrinet-server" program. Both of these are exactly equivalent. The server can be stopped with ^C or a "kill" command. If you want the server to run in the background, use an "&" after the command, for example: tetrinet -server & Configuring the server ---------------------- The server is configured via the ".tetrinet" file in your home directory. This contains all the settings for the server in a simple format. The following is a sample .tetrinet file: winlist Alcan;0;3;1 AndrewK;0;2;1 classic 1 initiallevel 1 linesperlevel 2 levelinc 1 averagelevels 1 speciallines 1 specialcount 1 specialcapacity 18 pieces 14 14 15 14 14 14 15 specials 18 18 3 12 0 16 3 12 18 linuxmode 0 ipv6_only 0 Note that this file is automatically re-written at the end of a game or when the server is terminated. If you want to modify parameters for a running server, send the server a HUP signal, using the command: kill -HUP where is the process ID of the server. A simpler alternative is: killall -HUP tetrinet-server Three of the configuration lines require special explanation. The winlist line is, as its name suggests, the winlist for the server; each parameter contains four semicolon-separated fields: name ; team ; points ; games "team" is a flag which is either 1 if the entry is for a team or 0 if the entry is for a player. "points" is just the number of points for the player (see the main Tetrinet documentation); "games" is the number of games in which that player has participated since getting on the winlist. The pieces line contains percentage frequencies for each type of piece. The order is: bar, square, reverse-L (green), L (purple), Z (red), S (blue), and T. The specials line, likewise, contains percentage frequencies for each type of special. The order is: A, C, N, R, S, B, G, Q, O. The "linuxmode" setting selects whether the client should try to remain compatible with Windows clients. This only affects the winlist display; if linuxmode is set to 1, the server will send the number of games played by each player as well as points won. This is set to zero by default. If the "ipv6_only" setting is set to a nonzero value, the server will only listen for IPv6 connections; if zero (default), the server will listen on both IPv4 and IPv6 if possible. Keys ---- The display mode can be selected by one of the following keys: F1 Show Fields F2 Partyline F3 Winlist F10 can be used to quit at any time. In Partyline mode, the following commands are available. To use a command, simply type the command and arguments into the Partyline input buffer and press Return (just like IRC). /team [name] Set your team name. If a name is not given, play alone. /start Start a game (if you are the first player on the server). /stop, /end Stop the game currently in progress (either command may be used). /pause Pause the game. /unpause Unpause the game. / Quote a following slash, for example: "/ /start starts a game." The following keys are used for controls on the "Show Fields" screen: Up, X Rotate piece clockwise Z Rotate piece counterclockwise Left Move piece left Right Move piece right Down Accelerate piece downward Space Drop piece (note that by default, pieces can still "slide" after dropping!) D Discard the current (leftmost) special item 1..6 Use the current special item on the given player T Open a window for sending a message to other players Ctrl-G Close the text input window (text there is saved for the next time you press T) The following keys are used for editing text, both in the Partyline screen and in the text buffer on the Show Fields screen: Left Move cursor left one space Right Move cursor right one space Ctrl-A Move cursor to beginning of line Ctrl-E Move cursor to end of line Backspace, Delete character to left of cursor Delete Ctrl-D Delete character under cursor Ctrl-U Delete entire line Enter Send text (closes input window in Show Fields mode) Differences from Windows Tetrinet --------------------------------- Although Linux Tetrinet is designed to play more or less the same as the original Windows version, there are a few differences; some of these are simply "missing" features in the Linux version, and some are features I have introduced into the Linux version because I believe they make the game more interesting or fun. Features marked with (*) below can all be disabled with the -windows command-line option to make playing against Windows opponents fairer. - Messages about specials (i.e. in the Attack/Defense window) are not numbered. - If a Block Bomb is done on someone who has two "o" (bomb) specials right next to each other, one of them will be sent flying rather than exploding. (This is a bug.) - Blocks scattered by a Block Bomb will only go to empty spaces on the board, rather than appearing on top of already-existing blocks. "Holes" will not be scattered. (*) - Pieces may go over the top of the board. In the Windows version, a player loses if at any time any square goes off the top of the board. In this version, a player only loses if there is no room for the next piece to enter the board. - Pieces dropped (with the spacebar) can still slide left and right after dropping. Idea from Mark H. Weaver's Netris. (*) This feature alone can be disabled with the -noslide command-line option. It can also be enabled with -slide even if other Linux-specific features are disabled with the -windows option. - Blockquakes will cause blocks to wrap around the edge of the screen rather than disappearing off the edge. (*) - Blockquakes will never move rows more than one block to the left or right. (Can anyone determine how quakes work in the Windows version?) - Specials collected will always appear at the end of the specials bar (in the Windows version, they randomly appear at the beginning or the end). (*) Acknowledgements ---------------- Tetrinet was originally written by St0rmCat, who has asked not to be contacted with respect to Tetrinet. tetrinet/TODO0000664000175000017500000000117110211444311012705 0ustar rhondarhondaTeams broken: (usually) doesn't stop when only 1 team is left; team joins not always propogated, teams case-sensitive If in game, server should send current fields to new-connecting players "Stack Height at Start" not implemented "Server lines" not implemented If gmsg input window is open and we switch to Partyline mode, things get confused -) Next block and stats are displayed during a game even in Partyline mode -) really hide the cursor on the playfield completely -) pause / unpause key in playfield -) the Blocktrix protocol -) X11 frontend (might not be as difficult as it seems) -) rewrite and reorder this file :-) tetrinet/io.h0000664000175000017500000000500710211442167013006 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Input/output interface declaration and constant definitions. */ #ifndef IO_H #define IO_H /* Text buffers: */ #define BUFFER_PLINE 0 #define BUFFER_GMSG 1 #define BUFFER_ATTDEF 2 typedef struct { /**** Input routine. ****/ /* Wait for input and return either an ASCII code, a K_* value, -1 if * server input is waiting, or -2 if we time out. */ int (*wait_for_input)(int msec); /**** Output routines. ****/ /* Initialize for output. */ void (*screen_setup)(void); /* Redraw the screen. */ void (*screen_refresh)(void); /* Redraw the screen after clearing it. */ void (*screen_redraw)(void); /* Draw text into the given buffer (@s can contain enum tattr fields; * these are the ones with < TATTR_MAX). */ void (*draw_text)(int bufnum, const char *s); /* Clear the given text buffer. */ void (*clear_text)(int bufnum); /* Set up the fields display. */ void (*setup_fields)(void); /* Draw our own field. */ void (*draw_own_field)(void); /* Draw someone else's field. */ void (*draw_other_field)(int player); /* Draw the game status information. */ void (*draw_status)(void); /* Draw specials stuff */ void (*draw_specials)(void); /* Write a text string for usage of a special. */ void (*draw_attdef)(const char *type, int from, int to); /* Draw the game message input window. */ void (*draw_gmsg_input)(const char *s, int pos); /* Clear the game message input window. */ void (*clear_gmsg_input)(void); /* Set up the partyline display. */ void (*setup_partyline)(void); /* Draw the partyline input string with the cursor at the given position. */ void (*draw_partyline_input)(const char *s, int pos); /* Set up the winlist display. */ void (*setup_winlist)(void); } Interface; extern Interface tty_interface, xwin_interface; /* Text attributes; note that in strings, they are always encoded with +1 to * avoid black terminating the string. */ enum tattr { /* Note that TATTR_CBLACK text should be visible on a black background, too. */ TATTR_CBLACK, TATTR_CRED, TATTR_CGREEN, TATTR_CBROWN, TATTR_CBLUE, TATTR_CMAGENTA, TATTR_CCYAN, TATTR_CGREY, TATTR_CXBRIGHT, /* | this with the colors above to get the bright variant. */ TATTR_CMAX = 16, TATTR_BOLD, TATTR_ITALIC, TATTR_UNDERLINE, TATTR_RESET, TATTR_MAX }; #endif /* IO_H */ tetrinet/server.c0000664000175000017500000006114707760427113013720 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Tetrinet server code */ #include #include #include #include #include #include #include #include /* Due to glibc brokenness, we can't blindly include this. Yet another * reason to not use glibc. */ /* #include */ #include #include #include #include #include "tetrinet.h" #include "tetris.h" #include "server.h" #include "sockets.h" /*************************************************************************/ static int linuxmode = 0; /* 1: don't try to be compatible with Windows */ static int ipv6_only = 0; /* 1: only use IPv6 (when available) */ static int quit = 0; static int listen_sock = -1; #ifdef HAVE_IPV6 static int listen_sock6 = -1; #endif static int player_socks[6] = {-1,-1,-1,-1,-1,-1}; static unsigned char player_ips[6][4]; static int player_modes[6]; /* Which players have already lost in the current game? */ static int player_lost[6]; /* We re-use a lot of variables from the main code */ /*************************************************************************/ /*************************************************************************/ /* Convert a 2-byte hex value to an integer. */ int xtoi(const char *buf) { int val; if (buf[0] <= '9') val = (buf[0] - '0') << 4; else val = (toupper(buf[0]) - 'A' + 10) << 4; if (buf[1] <= '9') val |= buf[1] - '0'; else val |= toupper(buf[1]) - 'A' + 10; return val; } /*************************************************************************/ /* Return a string containing the winlist in a format suitable for sending * to clients. */ static char *winlist_str() { static char buf[1024]; char *s; int i; s = buf; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { s += snprintf(s, sizeof(buf)-(s-buf), linuxmode ? " %c%s;%d;%d" : " %c%s;%d", winlist[i].team ? 't' : 'p', winlist[i].name, winlist[i].points, winlist[i].games); } return buf; } /*************************************************************************/ /*************************************************************************/ /* Read the configuration file. */ void read_config(void) { char buf[1024], *s, *t; FILE *f; int i; s = getenv("HOME"); if (!s) s = "/etc"; snprintf(buf, sizeof(buf), "%s/.tetrinet", s); if (!(f = fopen(buf, "r"))) return; while (fgets(buf, sizeof(buf), f)) { s = strtok(buf, " "); if (!s) { continue; } else if (strcmp(s, "linuxmode") == 0) { if ((s = strtok(NULL, " "))) linuxmode = atoi(s); } else if (strcmp(s, "ipv6_only") == 0) { if ((s = strtok(NULL, " "))) ipv6_only = atoi(s); } else if (strcmp(s, "averagelevels") == 0) { if ((s = strtok(NULL, " "))) level_average = atoi(s); } else if (strcmp(s, "classic") == 0) { if ((s = strtok(NULL, " "))) old_mode = atoi(s); } else if (strcmp(s, "initiallevel") == 0) { if ((s = strtok(NULL, " "))) initial_level = atoi(s); } else if (strcmp(s, "levelinc") == 0) { if ((s = strtok(NULL, " "))) level_inc = atoi(s); } else if (strcmp(s, "linesperlevel") == 0) { if ((s = strtok(NULL, " "))) lines_per_level = atoi(s); } else if (strcmp(s, "pieces") == 0) { i = 0; while (i < 7 && (s = strtok(NULL, " "))) piecefreq[i++] = atoi(s); } else if (strcmp(s, "specialcapacity") == 0) { if ((s = strtok(NULL, " "))) special_capacity = atoi(s); } else if (strcmp(s, "specialcount") == 0) { if ((s = strtok(NULL, " "))) special_count = atoi(s); } else if (strcmp(s, "speciallines") == 0) { if ((s = strtok(NULL, " "))) special_lines = atoi(s); } else if (strcmp(s, "specials") == 0) { i = 0; while (i < 9 && (s = strtok(NULL, " "))) specialfreq[i++] = atoi(s); } else if (strcmp(s, "winlist") == 0) { i = 0; while (i < MAXWINLIST && (s = strtok(NULL, " "))) { t = strchr(s, ';'); if (!t) break; *t++ = 0; strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; s = t; t = strchr(s, ';'); if (!t) { *winlist[i].name = 0; break; } winlist[i].team = atoi(s); s = t+1; t = strchr(s, ';'); if (!t) { *winlist[i].name = 0; break; } winlist[i].points = atoi(s); winlist[i].games = atoi(t+1); i++; } } } fclose(f); } /*************************************************************************/ /* Re-write the configuration file. */ void write_config(void) { char buf[1024], *s; FILE *f; int i; s = getenv("HOME"); if (!s) s = "/etc"; snprintf(buf, sizeof(buf), "%s/.tetrinet", s); if (!(f = fopen(buf, "w"))) return; fprintf(f, "winlist"); for (i = 0; i < MAXSAVEWINLIST && *winlist[i].name; i++) { fprintf(f, " %s;%d;%d;%d", winlist[i].name, winlist[i].team, winlist[i].points, winlist[i].games); } fputc('\n', f); fprintf(f, "classic %d\n", old_mode); fprintf(f, "initiallevel %d\n", initial_level); fprintf(f, "linesperlevel %d\n", lines_per_level); fprintf(f, "levelinc %d\n", level_inc); fprintf(f, "averagelevels %d\n", level_average); fprintf(f, "speciallines %d\n", special_lines); fprintf(f, "specialcount %d\n", special_count); fprintf(f, "specialcapacity %d\n", special_capacity); fprintf(f, "pieces"); for (i = 0; i < 7; i++) fprintf(f, " %d", piecefreq[i]); fputc('\n', f); fprintf(f, "specials"); for (i = 0; i < 9; i++) fprintf(f, " %d", specialfreq[i]); fputc('\n', f); fprintf(f, "linuxmode %d\n", linuxmode); fprintf(f, "ipv6_only %d\n", ipv6_only); fclose(f); } /*************************************************************************/ /*************************************************************************/ /* Send a message to a single player. */ static void send_to(int player, const char *format, ...) { va_list args; char buf[1024]; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); if (player_socks[player-1] >= 0) sockprintf(player_socks[player-1], "%s", buf); } /*************************************************************************/ /* Send a message to all players. */ static void send_to_all(const char *format, ...) { va_list args; char buf[1024]; int i; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (player_socks[i] >= 0) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /* Send a message to all players but the given one. */ static void send_to_all_but(int player, const char *format, ...) { va_list args; char buf[1024]; int i; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (i+1 != player && player_socks[i] >= 0) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /* Send a message to all players but those on the same team as the given * player. */ static void send_to_all_but_team(int player, const char *format, ...) { va_list args; char buf[1024]; int i; char *team = teams[player-1]; va_start(args, format); vsnprintf(buf, sizeof(buf), format, args); for (i = 0; i < 6; i++) { if (i+1 != player && player_socks[i] >= 0 && (!team || !teams[i] || strcmp(teams[i], team) != 0)) sockprintf(player_socks[i], "%s", buf); } } /*************************************************************************/ /*************************************************************************/ /* Add points to a given player's [team's] winlist entry, or make a new one * if they rank. */ static void add_points(int player, int points) { int i; if (!players[player-1]) return; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { if (!winlist[i].team && !teams[player-1] && strcmp(winlist[i].name, players[player-1]) == 0) break; if (winlist[i].team && teams[player-1] && strcmp(winlist[i].name, teams[player-1]) == 0) break; } if (i == MAXWINLIST) { for (i = 0; i < MAXWINLIST && winlist[i].points >= points; i++) ; } if (i == MAXWINLIST) return; if (!*winlist[i].name) { if (teams[player-1]) { strncpy(winlist[i].name, teams[player-1], sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; winlist[i].team = 1; } else { strncpy(winlist[i].name, players[player-1], sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)-1] = 0; winlist[i].team = 0; } } winlist[i].points += points; } /*************************************************************************/ /* Add a game to a given player's [team's] winlist entry. */ static void add_game(int player) { int i; if (!players[player-1]) return; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { if (!winlist[i].team && !teams[player-1] && strcmp(winlist[i].name, players[player-1]) == 0) break; if (winlist[i].team && teams[player-1] && strcmp(winlist[i].name, teams[player-1]) == 0) break; } if (i == MAXWINLIST || !*winlist[i].name) return; winlist[i].games++; } /*************************************************************************/ /* Sort the winlist. */ static void sort_winlist() { int i, j, best, bestindex; for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { best = winlist[i].points; bestindex = i; for (j = i+1; j < MAXWINLIST && *winlist[j].name; j++) { if (winlist[j].points > best) { best = winlist[j].points; bestindex = j; } } if (bestindex != i) { WinInfo tmp; memcpy(&tmp, &winlist[i], sizeof(WinInfo)); memcpy(&winlist[i], &winlist[bestindex], sizeof(WinInfo)); memcpy(&winlist[bestindex], &tmp, sizeof(WinInfo)); } } } /*************************************************************************/ /* Take care of a player losing (which may end the game). */ static void player_loses(int player) { int i, j, order, end = 1, winner = -1, second = -1, third = -1; if (player < 1 || player > 6 || player_socks[player-1] < 0) return; order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order) order = player_lost[i-1]; } player_lost[player-1] = order+1; for (i = 1; i <= 6; i++) { if (player_socks[i-1] >= 0 && !player_lost[i-1]) { if (winner < 0) { winner = i; } else if (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0) { end = 0; break; } } } if (end) { send_to_all("endgame"); playing_game = 0; /* Catch the case where no players are left (1-player game) */ if (winner > 0) { send_to_all("playerwon %d", winner); add_points(winner, 3); order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order && (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0)) { order = player_lost[i-1]; second = i; } } if (order) { add_points(second, 2); player_lost[second-1] = 0; } order = 0; for (i = 1; i <= 6; i++) { if (player_lost[i-1] > order && (!teams[winner-1] || !teams[i-1] || strcasecmp(teams[winner-1],teams[i-1]) != 0) && (!teams[second-1] || !teams[i-1] || strcasecmp(teams[second-1],teams[i-1]) != 0)) { order = player_lost[i-1]; third = i; } } if (order) add_points(third, 1); for (i = 1; i <= 6; i++) { if (teams[i-1]) { for (j = 1; j < i; j++) { if (teams[j-1] && strcasecmp(teams[i-1],teams[j-1])==0) break; } if (j < i) continue; } if (player_socks[i-1] >= 0) add_game(i); } } sort_winlist(); write_config(); send_to_all("winlist %s", winlist_str()); } /* One more possibility: the only player playing left the game, which * means there are now no players left. */ if (!players[0] && !players[1] && !players[2] && !players[3] && !players[4] && !players[5]) playing_game = 0; } /*************************************************************************/ /*************************************************************************/ /* Parse a line from a client. Destroys the buffer it's given as a side * effect. Return 0 if the command is unknown (or bad syntax), else 1. */ static int server_parse(int player, char *buf) { char *cmd, *s, *t; int i, tetrifast = 0; cmd = strtok(buf, " "); if (!cmd) { return 1; } else if (strcmp(cmd, "tetrisstart") == 0) { newplayer: s = strtok(NULL, " "); t = strtok(NULL, " "); if (!t) return 0; for (i = 1; i <= 6; i++) { if (players[i-1] && strcasecmp(s, players[i-1]) == 0) { send_to(player, "noconnecting Nickname already exists on server!"); return 0; } } players[player-1] = strdup(s); if (teams[player-1]) free(teams[player-1]); teams[player-1] = NULL; player_modes[player-1] = tetrifast; send_to(player, "%s %d", tetrifast ? ")#)(!@(*3" : "playernum", player); send_to(player, "winlist %s", winlist_str()); for (i = 1; i <= 6; i++) { if (i != player && players[i-1]) { send_to(player, "playerjoin %d %s", i, players[i-1]); send_to(player, "team %d %s", i, teams[i-1] ? teams[i-1] : ""); } } if (playing_game) { send_to(player, "ingame"); player_lost[player-1] = 1; } send_to_all_but(player, "playerjoin %d %s", player, players[player-1]); } else if (strcmp(cmd, "tetrifaster") == 0) { tetrifast = 1; goto newplayer; } else if (strcmp(cmd, "team") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (teams[player]) free(teams[player]); if (t) teams[player] = strdup(t); else teams[player] = NULL; send_to_all_but(player, "team %d %s", player, t ? t : ""); } else if (strcmp(cmd, "pline") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (!t) t = ""; send_to_all_but(player, "pline %d %s", player, t); } else if (strcmp(cmd, "plineact") == 0) { s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || atoi(s) != player) return 0; if (!t) t = ""; send_to_all_but(player, "plineact %d %s", player, t); } else if (strcmp(cmd, "startgame") == 0) { int total; char piecebuf[101], specialbuf[101]; for (i = 1; i < player; i++) { if (player_socks[i-1] >= 0) return 1; } s = strtok(NULL, " "); t = strtok(NULL, " "); if (!s) return 1; i = atoi(s); if ((i && playing_game) || (!i && !playing_game)) return 1; if (!i) { /* end game */ send_to_all("endgame"); playing_game = 0; return 1; } total = 0; for (i = 0; i < 7; i++) { if (piecefreq[i]) memset(piecebuf+total, '1'+i, piecefreq[i]); total += piecefreq[i]; } piecebuf[100] = 0; if (total != 100) { send_to_all("plineact 0 cannot start game: Piece frequencies do not total 100 percent!"); return 1; } total = 0; for (i = 0; i < 9; i++) { if (specialfreq[i]) memset(specialbuf+total, '1'+i, specialfreq[i]); total += specialfreq[i]; } specialbuf[100] = 0; if (total != 100) { send_to_all("plineact 0 cannot start game: Special frequencies do not total 100 percent!"); return 1; } playing_game = 1; game_paused = 0; for (i = 1; i <= 6; i++) { if (player_socks[i-1] < 0) continue; /* XXX First parameter is stack height */ send_to(i, "%s %d %d %d %d %d %d %d %s %s %d %d", player_modes[i-1] ? "*******" : "newgame", 0, initial_level, lines_per_level, level_inc, special_lines, special_count, special_capacity, piecebuf, specialbuf, level_average, old_mode); } memset(player_lost, 0, sizeof(player_lost)); } else if (strcmp(cmd, "pause") == 0) { if (!playing_game) return 1; s = strtok(NULL, " "); if (!s) return 1; i = atoi(s); if (i) i = 1; /* to make sure it's not anything else */ if ((i && game_paused) || (!i && !game_paused)) return 1; game_paused = i; send_to_all("pause %d", i); } else if (strcmp(cmd, "playerlost") == 0) { if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; player_loses(player); } else if (strcmp(cmd, "f") == 0) { /* field */ if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; if (!(s = strtok(NULL, ""))) s = ""; send_to_all_but(player, "f %d %s", player, s); } else if (strcmp(cmd, "lvl") == 0) { if (!(s = strtok(NULL, " ")) || atoi(s) != player) return 1; if (!(s = strtok(NULL, " "))) return 1; levels[player] = atoi(s); send_to_all_but(player, "lvl %d %d", player, levels[player]); } else if (strcmp(cmd, "sb") == 0) { int from, to; char *type; if (!(s = strtok(NULL, " "))) return 1; to = atoi(s); if (!(type = strtok(NULL, " "))) return 1; if (!(s = strtok(NULL, " "))) return 1; from = atoi(s); if (from != player) return 1; if (to < 0 || to > 6 || player_socks[to-1] < 0 || player_lost[to-1]) return 1; if (to == 0) send_to_all_but_team(player, "sb %d %s %d", to, type, from); else send_to_all_but(player, "sb %d %s %d", to, type, from); } else if (strcmp(cmd, "gmsg") == 0) { if (!(s = strtok(NULL, ""))) return 1; send_to_all("gmsg %s", s); } else { /* unrecognized command */ return 0; } return 1; } /*************************************************************************/ /*************************************************************************/ static void sigcatcher(int sig) { if (sig == SIGHUP) { read_config(); signal(SIGHUP, sigcatcher); send_to_all("winlist %s", winlist_str()); } else if (sig == SIGTERM || sig == SIGINT) { quit = 1; signal(sig, SIG_IGN); } } /*************************************************************************/ /* Returns 0 on success, desired program exit code on failure */ static int init() { struct sockaddr_in sin; #ifdef HAVE_IPV6 struct sockaddr_in6 sin6; #endif int i; /* Set up some sensible defaults */ *winlist[0].name = 0; old_mode = 1; initial_level = 1; lines_per_level = 2; level_inc = 1; level_average = 1; special_lines = 1; special_count = 1; special_capacity = 18; piecefreq[0] = 14; piecefreq[1] = 14; piecefreq[2] = 15; piecefreq[3] = 14; piecefreq[4] = 14; piecefreq[5] = 14; piecefreq[6] = 15; specialfreq[0] = 18; specialfreq[1] = 18; specialfreq[2] = 3; specialfreq[3] = 12; specialfreq[4] = 0; specialfreq[5] = 16; specialfreq[6] = 3; specialfreq[7] = 12; specialfreq[8] = 18; /* (Try to) read the config file */ read_config(); /* Catch some signals */ signal(SIGHUP, sigcatcher); signal(SIGINT, sigcatcher); signal(SIGTERM, sigcatcher); /* Set up a listen socket */ if (!ipv6_only) listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listen_sock >= 0){ i = 1; if (setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))==0) { memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(31457); if (bind(listen_sock, (struct sockaddr *)&sin, sizeof(sin)) == 0) { if (listen(listen_sock, 5) == 0) { goto ipv4_success; } } } i = errno; close(listen_sock); errno = i; listen_sock = -1; } ipv4_success: #ifdef HAVE_IPV6 /* Set up an IPv6 listen socket if possible */ listen_sock6 = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); if (listen_sock6 >= 0) { i = 1; if (setsockopt(listen_sock6,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(i))==0) { memset(&sin6, 0, sizeof(sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = htons(31457); if (bind(listen_sock6,(struct sockaddr *)&sin6,sizeof(sin6))==0) { if (listen(listen_sock6, 5) == 0) { goto ipv6_success; } } } i = errno; close(listen_sock6); errno = i; listen_sock6 = -1; } ipv6_success: #else /* !HAVE_IPV6 */ if (ipv6_only) { fprintf(stderr,"ipv6_only specified but IPv6 support not available\n"); return 1; } #endif /* HAVE_IPV6 */ if (listen_sock < 0 #ifdef HAVE_IPV6 && listen_sock6 < 0 #endif ) { return 1; } return 0; } /*************************************************************************/ static void decrypt_message(char *buf, char *newbuf, char *iphashbuf) { int j, c, l = strlen(iphashbuf); c = xtoi(buf); for (j = 2; buf[j] && buf[j+1]; j += 2) { int temp, d; temp = d = xtoi(buf+j); d ^= iphashbuf[((j/2)-1) % l]; d += 255 - c; d %= 255; newbuf[j/2-1] = d; c = temp; } newbuf[j/2-1] = 0; } static void check_sockets() { fd_set fds; int i, fd, maxfd; FD_ZERO(&fds); if (listen_sock >= 0) FD_SET(listen_sock, &fds); maxfd = listen_sock; #ifdef HAVE_IPV6 if (listen_sock6 >= 0) FD_SET(listen_sock6, &fds); if (listen_sock6 > maxfd) maxfd = listen_sock6; #endif for (i = 0; i < 6; i++) { if (player_socks[i] != -1) { if (player_socks[i] < 0) fd = (~player_socks[i]) - 1; else fd = player_socks[i]; FD_SET(fd, &fds); if (fd > maxfd) maxfd = fd; } } if (select(maxfd+1, &fds, NULL, NULL, NULL) <= 0) return; if (listen_sock >= 0 && FD_ISSET(listen_sock, &fds)) { struct sockaddr_in sin; int len = sizeof(sin); fd = accept(listen_sock, (struct sockaddr *)&sin, &len); if (fd >= 0) { for (i = 0; i < 6 && player_socks[i] != -1; i++) ; if (i == 6) { sockprintf(fd, "noconnecting Too many players on server!"); close(fd); } else { player_socks[i] = ~(fd+1); memcpy(player_ips[i], &sin.sin_addr, 4); } } } /* if (FD_ISSET(listen_sock)) */ #ifdef HAVE_IPV6 if (listen_sock6 >= 0 && FD_ISSET(listen_sock6, &fds)) { struct sockaddr_in6 sin6; int len = sizeof(sin6); fd = accept(listen_sock6, (struct sockaddr *)&sin6, &len); if (fd >= 0) { for (i = 0; i < 6 && player_socks[i] != -1; i++) ; if (i == 6) { sockprintf(fd, "noconnecting Too many players on server!"); close(fd); } else { player_socks[i] = ~(fd+1); memcpy(player_ips[i], (char *)(&sin6.sin6_addr)+12, 4); } } } /* if (FD_ISSET(listen_sock6)) */ #endif for (i = 0; i < 6; i++) { char buf[1024]; if (player_socks[i] == -1) continue; if (player_socks[i] < 0) fd = (~player_socks[i]) - 1; else fd = player_socks[i]; if (!FD_ISSET(fd, &fds)) continue; sgets(buf, sizeof(buf), fd); if (player_socks[i] < 0) { /* Our extension: the client can give up on the meaningless * encryption completely. */ if (strncmp(buf,"tetrisstart ",12) != 0) { /* Messy decoding stuff */ char iphashbuf[16], newbuf[1024]; unsigned char *ip; #ifndef NO_BRUTE_FORCE_DECRYPTION int hashval; #endif if (strlen(buf) < 2*13) { /* "tetrisstart " + initial byte */ close(fd); player_socks[i] = -1; continue; } ip = player_ips[i]; sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17); decrypt_message(buf, newbuf, iphashbuf); if(strncmp(newbuf,"tetrisstart ",12) == 0) goto cryptok; #ifndef NO_BRUTE_FORCE_DECRYPTION /* The IP-based crypt does not work for clients behind NAT. So * help them by brute-forcing the crypt. This should not be * even noticeable unless you are running this under ucLinux on * some XT machine. */ for (hashval = 0; hashval < 35956; hashval++) { sprintf(iphashbuf, "%d", hashval); decrypt_message(buf, newbuf, iphashbuf); if(strncmp(newbuf,"tetrisstart ",12) == 0) goto cryptok; } /* for (hashval) */ #endif if (strncmp(newbuf, "tetrisstart ", 12) != 0) { close(fd); player_socks[i] = -1; continue; } cryptok: /* Buffers should be the same size, but let's be paranoid */ strncpy(buf, newbuf, sizeof(buf)); buf[sizeof(buf)-1] = 0; } /* if encrypted */ player_socks[i] = fd; /* Has now registered */ } /* if client not registered */ if (!server_parse(i+1, buf)) { close(fd); player_socks[i] = -1; if (players[i]) { send_to_all("playerleave %d", i+1); if (playing_game) player_loses(i+1); free(players[i]); players[i] = NULL; if (teams[i]) { free(teams[i]); teams[i] = NULL; } } } } /* for each player socket */ } /*************************************************************************/ #ifdef SERVER_ONLY int main() #else int server_main() #endif { int i; if ((i = init()) != 0) return i; while (!quit) check_sockets(); write_config(); if (listen_sock >= 0) close(listen_sock); #ifdef HAVE_IPV6 if (listen_sock6 >= 0) close(listen_sock6); #endif for (i = 0; i < 6; i++) close(player_socks[i]); return 0; } /*************************************************************************/ tetrinet/server.h0000664000175000017500000000022707726062547013725 0ustar rhondarhonda#ifndef SERVER_H #define SERVER_H extern void read_config(void); extern void write_config(void); extern int server_main(void); #endif /* SERVER_H */ tetrinet/sockets.c0000664000175000017500000001076607727423270014071 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Socket routines. */ #include #include #include #include #include #include #include #include #include #include #include #include "sockets.h" #include "tetrinet.h" static FILE *logfile; /*************************************************************************/ static int lastchar = EOF; int sgetc(int s) { int c; char ch; if (lastchar != EOF) { c = lastchar; lastchar = EOF; return c; } if (read(s, &ch, 1) != 1) return EOF; c = ch & 0xFF; return c; } int sungetc(int c, int s) { return lastchar = c; } /*************************************************************************/ /* Read a string, stopping with (and discarding) 0xFF as line terminator. * If connection was broken, return NULL. */ char *sgets(char *buf, int len, int s) { int c; unsigned char *ptr = (unsigned char *) buf; if (len == 0) return NULL; c = sgetc(s); while (--len && (*ptr++ = c) != 0xFF && (c = sgetc(s)) >= 0) ; if (c < 0) return NULL; if (c == 0xFF) ptr--; *ptr = 0; if (log) { if (!logfile) logfile = fopen(logname, "a"); if (logfile) { struct timeval tv; gettimeofday(&tv, NULL); fprintf(logfile, "[%d.%03d] <<< %s\n", (int) tv.tv_sec, (int) tv.tv_usec/1000, buf); fflush(logfile); } } return buf; } /*************************************************************************/ /* Adds a 0xFF line terminator. */ int sputs(const char *str, int s) { unsigned char c = 0xFF; int n = 0; if (log) { if (!logfile) logfile = fopen(logname, "a"); if (logfile) { struct timeval tv; gettimeofday(&tv, NULL); fprintf(logfile, "[%d.%03d] >>> %s\n", (int) tv.tv_sec, (int) tv.tv_usec/1000, str); } } if (*str != 0) { n = write(s, str, strlen(str)); if (n <= 0) return n; } if (write(s, &c, 1) <= 0) return n; return n+1; } /*************************************************************************/ /* Adds a 0xFF line terminator. */ int sockprintf(int s, const char *fmt, ...) { va_list args; char buf[16384]; /* Really huge, to try and avoid truncation */ va_start(args, fmt); vsnprintf(buf, sizeof(buf), fmt, args); return sputs(buf, s); } /*************************************************************************/ /*************************************************************************/ int conn(const char *host, int port, char ipbuf[4]) { #ifdef HAVE_IPV6 char hbuf[NI_MAXHOST]; struct addrinfo hints, *res, *res0; char service[11]; #else struct hostent *hp; struct sockaddr_in sa; #endif int sock = -1; #ifdef HAVE_IPV6 snprintf(service, sizeof(service), "%d", port); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; if (getaddrinfo(host, service, &hints, &res0)) return -1; for (res = res0; res; res = res->ai_next) { int errno_save; sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sock < 0) continue; getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, sizeof(hbuf), NULL, 0, 0); if (connect(sock, res->ai_addr, res->ai_addrlen) == 0) { if (ipbuf) { if (res->ai_family == AF_INET6) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)(res->ai_addr); memcpy(ipbuf, (char *)(&sin6->sin6_addr) + 12, 4); } else { struct sockaddr_in *sin = (struct sockaddr_in *)(res->ai_addr); memcpy(ipbuf, &sin->sin_addr, 4); } } break; } errno_save = errno; close(sock); sock = -1; errno = errno_save; } freeaddrinfo(res0); #else /* !HAVE_IPV6 */ memset(&sa, 0, sizeof(sa)); if (!(hp = gethostbyname(host))) return -1; memcpy((char *)&sa.sin_addr, hp->h_addr, hp->h_length); sa.sin_family = hp->h_addrtype; sa.sin_port = htons((unsigned short)port); if ((sock = socket(sa.sin_family, SOCK_STREAM, 0)) < 0) return -1; if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) { int errno_save = errno; close(sock); errno = errno_save; return -1; } if (ipbuf) memcpy(retbuf, &sa.sin_addr, 4); #endif return sock; } /*************************************************************************/ void disconn(int s) { shutdown(s, 2); close(s); } /*************************************************************************/ tetrinet/sockets.h0000664000175000017500000000074707726062547014101 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Socket routine declarations. */ #ifndef SOCKETS_H #define SOCKETS_H extern int sgetc(int s); extern int sungetc(int c, int s); extern char *sgets(char *buf, int len, int s); extern int sputs(const char *buf, int len); extern int sockprintf(int s, const char *fmt, ...); extern int conn(const char *host, int port, char ipbuf[4]); extern void disconn(int s); #endif /* SOCKETS_H */ tetrinet/tetrinet-server.60000664000175000017500000000564410162475614015476 0ustar rhondarhonda.TH "TETRINET-SERVER" "6" "2004-12-06" .\" Please adjust this date whenever revising the manpage. .SH "NAME" tetrinet-server \- server program for tetrinet .SH "SYNOPSIS" .B tetrinet-server .SH "DESCRIPTION" .B tetrinet-server is a server program for .BR tetrinet (6), a networked version of tetris. You can use it to server both a TetriFast and an original server for up to 6 people to connect and play. It doesn't support any fancy features like different channels, but still includes support for configureable cookie mode and a small winlist. .PP .SH "OPTIONS" .B tetrinet-server doesn't take any options at all. It reads all its settings from .I ~/.tetrinet on startup and creates a default file if there isn't one already there. .SH "EXAMPLES" This is a short explenation of the configurationfile .I ~/.tetrinet together with its default entries. It is written after every game or when the server quits. If it is not there it will be created automatically. .TP .BI winlist\ "Alcan;0;3;1 AndrewK;0;2;1" This is the winlist the server keeps. Each parameter consists of four semicolon-seperated fields: .IR Name ; Team ; Points ; Games .\ Team is a flag which is either .I 1 if the entry is for a team or .I 0 if the entry is for a player. .I Points is just the number of points for the entry, and .I Games is the number of games the entry has participated. .TP .BI classic\ 1 Sets classic mode for the game - that means, no cookies. .TP .BI initiallevel\ 1 Sets the level in which the game will start. .TP .BI linesperlevel\ 2 Defines how many lines will issue a level increase. .TP .BI levelinc\ 1 How many levels are increased per .B linesperlevel removed lines. .TP .BI averagelevels\ 1 The levels of all player get averaged if this is set to .IR 1 . .TP .BI speciallines\ 1 How many lines must be removed to get specials. .TP .BI specialcount\ 1 The number of specials that are added each time .BR speciallines ' lines are removed. .TP .BI specialcapacity\ 18 This number tells you how many specials you can hold. .TP .BI pieces\ "14 14 15 14 14 14 15" Sets the likeliness of the different pieces. Must sum up to 100. The order is: bar (dark blue), square (yellow), reverse-L (green), L (purple), Z (red), S (light blue), and T (yellow). .TP .BI specials\ "18 18 3 12 0 16 3 12 18" Sets the likeliness of the different specials. Must sum up to 100. The order is: A, C, N, R, S, B, G, Q, O. .TP .BI linuxmode\ 0 This setting selects whether the client should try to remain compatible with Windows clients. This only affects the winlist display; if .B linuxmode is set to .IR 1 , the server will send the number of games played by each player as well as points won. This is set to zero by default. .TP .BI ipv6_only\ 0 Listen on ipv6 only. .SH "FILES" .TP .I ~/.tetrinet The configuration file for .BR tetrinet-server . .SH "AUTHOR" This manual page was written by Gerfried Fuchs . .SH "SEE ALSO" .BR tetrinet (6). tetrinet/tetrinet.60000664000175000017500000000762010150576100014153 0ustar rhondarhonda.TH TETRINET 6 "2004-11-23" .\" Please adjust this date whenever revising the manpage. .SH NAME tetrinet \- textmode client program for tetrinet .SH SYNOPSIS .B tetrinet .RB [\| \-fancy \|] .RB [\| \-fast \|] .RB [\| \-log .IR file \|] .RB [\| \-noshadow \|] .RB [\| \-noslide \|] .RB [\| \-slide \|] .RB [\| \-shadow \|] .RB [\| \-windows \|] .I nickname server .SH DESCRIPTION .B tetrinet is a textmode client program for tetrinet, a networked version of tetris. You can play both on TetriFast server and on the original servers with it. Please notice that you need at least 50 lines to be able to play it. .SH OPTIONS A summary of options is included below. .TP .B \-fancy Use "fancy" TTY graphics. (Note that this will slow down redraws somewhat.) .TP .B \-fast Use the "tetrifast" mode to connect to the server. This mode eliminates the delay before a new cube appears, thus speeding the game up noticeably. This mode is incompatible with the classic mode and the server has to support it. If in doubt, ask the other players. .TP .BI \-log\ file Log network traffic to the given file. All lines start with an absolute time (seconds) in brackets. Lines sent from the client to the server are prefixed with ">>>", and lines from the server to the client are prefixed with "<<<". This could be used with a utility program to replay a game later on (though such a program is not currently included in the Tetrinet distribution.) .TP .B \-noshadow Do not make pieces cast "shadows" when they are slowly falling. (Normally the area under piece is filled by dim dots to help to determine where the piece would hit the ground if one would press the spacebar.) .TP .B \-noslide Do not allow pieces to "slide" after being dropped with the spacebar. (Normally, there is a short time after pressing the spacebar during which a piece can "slide" left or right before it solidifies.) .TP .B \-shadow Opposite of .BR \-noshadow ; make the pieces cast "shadows". Can speed up gameplay considerably, but it can be considered as cheating by some people since some other tetrinet clients lack this. .TP .B \-slide Opposite of .BR \-noslide ; allows pieces to "slide" after being dropped. If both .BR \-slide\ and\ \-noslide are given, .B \-slide takes precedence. If both .BR \-windows\ and\ \-slide are given, this overrides the "no sliding" part of .B \-windows without affecting the other changes in program behavior. .TP .B \-windows Behave as much like the Windows version of Tetrinet as possible. Implies .BR \-noslide\ and\ \-noshadow . .SH USAGE When you start .B tetrinet\-client you will find yourself in the partyline. In here you can chat with the other players. With .I /help you will get the list of the server supported commands. You can switch between three screens with .I F1 through to .IR F3 . .P With .I /start you can start a game (if you are in position one). You will have to switch over to the Fields with the .I F1 key, an automatic switch over on game start is planed. To pause a running game you have to enter .I /pause in the partyline. To restart the game you enter .IR /unpause . .P With the cursor keys .I left and .I right you can move the stones, with cursor .I up you turn the stones clockwise, with cursor .I down you pull the stones to the bottom, and with .I space you drop them. You have to fill the lines completely, because complete lines will get removed from the display. The more lines you can eliminate with a single stone the better. If the play room supports specials you will eventually see a list of letters below your playfield that you can use with the number keys from .I 1 to .IR 6 for the corresponding playfield. If you want to get rid of a special, drop it with .IR d . You can also chat in the playfield, to open the chat box press .I t (for .BR t alk.) That's it, more or less. .SH AUTHOR This manual page was written by Gerfried Fuchs . .SH "SEE ALSO" .BR tetrinet-server (6). tetrinet/tetrinet.c0000664000175000017500000005312410211443540014227 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Tetrinet main program. */ /*************************************************************************/ #include #include #include #include #include #include "tetrinet.h" #include "io.h" #include "server.h" #include "sockets.h" #include "tetris.h" #include "version.h" /*************************************************************************/ int fancy = 0; /* Fancy TTY graphics? */ int log = 0; /* Log network traffic to file? */ char *logname; /* Log filename */ int windows_mode = 0; /* Try to be just like the Windows version? */ int noslide = 0; /* Disallow piece sliding? */ int tetrifast = 0; /* TetriFast mode? */ int cast_shadow = 1; /* Make pieces cast shadow? */ int my_playernum = -1; /* What player number are we? */ char *my_nick; /* And what is our nick? */ WinInfo winlist[MAXWINLIST]; /* Winners' list from server */ int server_sock; /* Socket for server communication */ int dispmode; /* Current display mode */ char *players[6]; /* Player names (NULL for no such player) */ char *teams[6]; /* Team names (NULL for not on a team) */ int playing_game; /* Are we currently playing a game? */ int not_playing_game; /* Are we currently watching people play a game? */ int game_paused; /* Is the game currently paused? */ Interface *io; /* Input/output routines */ /*************************************************************************/ /*************************************************************************/ #ifndef SERVER_ONLY /*************************************************************************/ /* Output message to a message buffer, possibly decoding the text attributes * tetrinet code. */ void msg_text(int bufnum, const unsigned char *s) { /* Stolen from gtetrinet: (leading space <=> undefined) */ static enum tattr map[32] = { 0, /* N/A */ TATTR_CBLACK, TATTR_BOLD, TATTR_CCYAN | TATTR_CXBRIGHT, TATTR_CBLACK, TATTR_CBLUE | TATTR_CXBRIGHT, TATTR_CGREY, TATTR_CBLACK, TATTR_CMAGENTA, TATTR_CBLACK, TATTR_CBLACK, TATTR_CBLACK | TATTR_CXBRIGHT, TATTR_CGREEN, TATTR_CBLACK, TATTR_CGREEN | TATTR_CXBRIGHT, TATTR_CGREY, TATTR_CRED, TATTR_CBLUE, TATTR_CBROWN, TATTR_CMAGENTA | TATTR_CXBRIGHT, TATTR_CRED | TATTR_CXBRIGHT, TATTR_CGREY, TATTR_ITALIC, TATTR_CCYAN, TATTR_CGREY | TATTR_CXBRIGHT, TATTR_CBROWN | TATTR_CXBRIGHT, TATTR_CBLACK, TATTR_CBLACK, TATTR_CBLACK, TATTR_CBLACK, TATTR_CBLACK, TATTR_UNDERLINE, }; unsigned char tb[1024], *t; for (t = tb; *s && t - tb < 1024; t++, s++) { if (*s == 0xFF) { *t = TATTR_RESET + 1; } else if (*s < 32) { *t = map[(int) *s] + 1; } else { *t = *s; } } if (t - tb >= 1024) t = &tb[1023]; *t = 0; io->draw_text(bufnum, tb); } /* Parse a line from the server. Destroys the buffer it's given as a side * effect. */ void parse(char *buf) { char *cmd, *s, *t; cmd = strtok(buf, " "); if (!cmd) { return; } else if (strcmp(cmd, "noconnecting") == 0) { s = strtok(NULL, ""); if (!s) s = "Unknown"; /* XXX not to stderr, please! -- we need to stay running w/o server */ fprintf(stderr, "Server error: %s\n", s); exit(1); } else if (strcmp(cmd, "winlist") == 0) { int i = 0; while (i < MAXWINLIST && (s = strtok(NULL, " "))) { t = strchr(s, ';'); if (!t) break; *t++ = 0; if (*s == 't') winlist[i].team = 1; else winlist[i].team = 0; s++; strncpy(winlist[i].name, s, sizeof(winlist[i].name)-1); winlist[i].name[sizeof(winlist[i].name)] = 0; winlist[i].points = atoi(t); if ((t = strchr(t, ';')) != NULL) winlist[i].games = atoi(t+1); i++; } if (i < MAXWINLIST) winlist[i].name[0] = 0; if (dispmode == MODE_WINLIST) io->setup_winlist(); } else if (strcmp(cmd, tetrifast ? ")#)(!@(*3" : "playernum") == 0) { if ((s = strtok(NULL, " "))) my_playernum = atoi(s); /* Note: players[my_playernum-1] is set in init() */ /* But that doesn't work when joining other channel. */ players[my_playernum-1] = strdup(my_nick); } else if (strcmp(cmd, "playerjoin") == 0) { int player; char buf[1024]; s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s || !t) return; player = atoi(s)-1; if (player < 0 || player > 5) return; players[player] = strdup(t); if (teams[player]) { free(teams[player]); teams[player] = NULL; } snprintf(buf, sizeof(buf), "*** %s is Now Playing", t); msg_text(BUFFER_PLINE, buf); if (dispmode == MODE_FIELDS) io->setup_fields(); } else if (strcmp(cmd, "playerleave") == 0) { int player; char buf[1024]; s = strtok(NULL, " "); if (!s) return; player = atoi(s)-1; if (player < 0 || player > 5 || !players[player]) return; snprintf(buf, sizeof(buf), "*** %s has Left", players[player]); msg_text(BUFFER_PLINE, buf); free(players[player]); players[player] = NULL; if (dispmode == MODE_FIELDS) io->setup_fields(); } else if (strcmp(cmd, "team") == 0) { int player; char buf[1024]; s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s) return; player = atoi(s)-1; if (player < 0 || player > 5 || !players[player]) return; if (teams[player]) free(teams[player]); if (t) teams[player] = strdup(t); else teams[player] = NULL; if (t) snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[player], t); else snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[player]); msg_text(BUFFER_PLINE, buf); } else if (strcmp(cmd, "pline") == 0) { int playernum; char buf[1024], *name; s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s) return; if (!t) t = ""; playernum = atoi(s)-1; if (playernum == -1) { name = "Server"; } else { if (playernum < 0 || playernum > 5 || !players[playernum]) return; name = players[playernum]; } snprintf(buf, sizeof(buf), "<%s> %s", name, t); msg_text(BUFFER_PLINE, buf); } else if (strcmp(cmd, "plineact") == 0) { int playernum; char buf[1024], *name; s = strtok(NULL, " "); t = strtok(NULL, ""); if (!s) return; if (!t) t = ""; playernum = atoi(s)-1; if (playernum == -1) { name = "Server"; } else { if (playernum < 0 || playernum > 5 || !players[playernum]) return; name = players[playernum]; } snprintf(buf, sizeof(buf), "* %s %s", name, t); msg_text(BUFFER_PLINE, buf); } else if (strcmp(cmd, tetrifast ? "*******" : "newgame") == 0) { int i; if ((s = strtok(NULL, " "))) /* stack height */; if ((s = strtok(NULL, " "))) initial_level = atoi(s); if ((s = strtok(NULL, " "))) lines_per_level = atoi(s); if ((s = strtok(NULL, " "))) level_inc = atoi(s); if ((s = strtok(NULL, " "))) special_lines = atoi(s); if ((s = strtok(NULL, " "))) special_count = atoi(s); if ((s = strtok(NULL, " "))) { special_capacity = atoi(s); if (special_capacity > MAX_SPECIALS) special_capacity = MAX_SPECIALS; } if ((s = strtok(NULL, " "))) { memset(piecefreq, 0, sizeof(piecefreq)); while (*s) { i = *s - '1'; if (i >= 0 && i < 7) piecefreq[i]++; s++; } } if ((s = strtok(NULL, " "))) { memset(specialfreq, 0, sizeof(specialfreq)); while (*s) { i = *s - '1'; if (i >= 0 && i < 9) specialfreq[i]++; s++; } } if ((s = strtok(NULL, " "))) level_average = atoi(s); if ((s = strtok(NULL, " "))) old_mode = atoi(s); lines = 0; for (i = 0; i < 6; i++) levels[i] = initial_level; memset(&fields[my_playernum-1], 0, sizeof(Field)); specials[0] = -1; io->clear_text(BUFFER_GMSG); io->clear_text(BUFFER_ATTDEF); new_game(); playing_game = 1; game_paused = 0; msg_text(BUFFER_PLINE, "*** The Game Has Started"); if (dispmode != MODE_FIELDS) { dispmode = MODE_FIELDS; io->setup_fields(); } } else if (strcmp(cmd, "ingame") == 0) { /* Sent when a player connects in the middle of a game */ int x, y; char buf[1024], *s; s = buf + sprintf(buf, "f %d ", my_playernum); for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { fields[my_playernum-1][y][x] = rand()%5 + 1; *s++ = '0' + fields[my_playernum-1][y][x]; } } *s = 0; sputs(buf, server_sock); playing_game = 0; not_playing_game = 1; } else if (strcmp(cmd, "pause") == 0) { if ((s = strtok(NULL, " "))) game_paused = atoi(s); if (game_paused) { msg_text(BUFFER_PLINE, "*** The Game Has Been Paused"); msg_text(BUFFER_GMSG, "*** The Game Has Been Paused"); } else { msg_text(BUFFER_PLINE, "*** The Game Has Been Unpaused"); msg_text(BUFFER_GMSG, "*** The Game Has Been Unpaused"); } } else if (strcmp(cmd, "endgame") == 0) { playing_game = 0; not_playing_game = 0; memset(fields, 0, sizeof(fields)); specials[0] = -1; io->clear_text(BUFFER_ATTDEF); msg_text(BUFFER_PLINE, "*** The Game Has Ended"); if (dispmode == MODE_FIELDS) { int i; io->draw_own_field(); for (i = 1; i <= 6; i++) { if (i != my_playernum) io->draw_other_field(i); } } if (dispmode != MODE_PARTYLINE) { dispmode = MODE_PARTYLINE; io->setup_partyline(); } } else if (strcmp(cmd, "playerwon") == 0) { /* Syntax: playerwon # -- sent when all but one player lose */ } else if (strcmp(cmd, "playerlost") == 0) { /* Syntax: playerlost # -- sent after playerleave on disconnect * during a game, or when a player loses (sent by the losing * player and from the server to all other players */ } else if (strcmp(cmd, "f") == 0) { /* field */ int player, x, y, tile; /* This looks confusing, but what it means is, ignore this message * if a game isn't going on. */ if (!playing_game && !not_playing_game) return; if (!(s = strtok(NULL, " "))) return; player = atoi(s); player--; if (!(s = strtok(NULL, ""))) return; if (*s >= '0') { /* Set field directly */ char *ptr = (char *) fields[player]; while (*s) { if (*s <= '5') *ptr++ = (*s++) - '0'; else switch (*s++) { case 'a': *ptr++ = 6 + SPECIAL_A; break; case 'b': *ptr++ = 6 + SPECIAL_B; break; case 'c': *ptr++ = 6 + SPECIAL_C; break; case 'g': *ptr++ = 6 + SPECIAL_G; break; case 'n': *ptr++ = 6 + SPECIAL_N; break; case 'o': *ptr++ = 6 + SPECIAL_O; break; case 'q': *ptr++ = 6 + SPECIAL_Q; break; case 'r': *ptr++ = 6 + SPECIAL_R; break; case 's': *ptr++ = 6 + SPECIAL_S; break; } } } else { /* Set specific locations on field */ tile = 0; while (*s) { if (*s < '0') { tile = *s - '!'; } else { x = *s - '3'; y = (*++s) - '3'; fields[player][y][x] = tile; } s++; } } if (player == my_playernum-1) io->draw_own_field(); else io->draw_other_field(player+1); } else if (strcmp(cmd, "lvl") == 0) { int player; if (!(s = strtok(NULL, " "))) return; player = atoi(s)-1; if (!(s = strtok(NULL, ""))) return; levels[player] = atoi(s); } else if (strcmp(cmd, "sb") == 0) { int from, to; char *type; if (!(s = strtok(NULL, " "))) return; to = atoi(s); if (!(type = strtok(NULL, " "))) return; if (!(s = strtok(NULL, " "))) return; from = atoi(s); do_special(type, from, to); } else if (strcmp(cmd, "gmsg") == 0) { if (!(s = strtok(NULL, ""))) return; msg_text(BUFFER_GMSG, s); } } /*************************************************************************/ /*************************************************************************/ static char partyline_buffer[512]; static int partyline_pos = 0; #define curpos (partyline_buffer+partyline_pos) /*************************************************************************/ /* Add a character to the partyline buffer. */ void partyline_input(int c) { if (partyline_pos < sizeof(partyline_buffer) - 1) { memmove(curpos+1, curpos, strlen(curpos)+1); partyline_buffer[partyline_pos++] = c; io->draw_partyline_input(partyline_buffer, partyline_pos); } } /*************************************************************************/ /* Delete the current character from the partyline buffer. */ void partyline_delete(void) { if (partyline_buffer[partyline_pos]) { memmove(curpos, curpos+1, strlen(curpos)-1+1); io->draw_partyline_input(partyline_buffer, partyline_pos); } } /*************************************************************************/ /* Backspace a character from the partyline buffer. */ void partyline_backspace(void) { if (partyline_pos > 0) { partyline_pos--; partyline_delete(); } } /*************************************************************************/ /* Kill the entire partyline input buffer. */ void partyline_kill(void) { partyline_pos = 0; *partyline_buffer = 0; io->draw_partyline_input(partyline_buffer, partyline_pos); } /*************************************************************************/ /* Move around the input buffer. Sign indicates direction; absolute value * of 1 means one character, 2 means the whole line. */ void partyline_move(int how) { if (how == -2) { partyline_pos = 0; io->draw_partyline_input(partyline_buffer, partyline_pos); } else if (how == -1 && partyline_pos > 0) { partyline_pos--; io->draw_partyline_input(partyline_buffer, partyline_pos); } else if (how == 1 && partyline_buffer[partyline_pos]) { partyline_pos++; io->draw_partyline_input(partyline_buffer, partyline_pos); } else if (how == 2) { partyline_pos = strlen(partyline_buffer); io->draw_partyline_input(partyline_buffer, partyline_pos); } } /*************************************************************************/ /* Send the input line to the server. */ void partyline_enter(void) { char buf[1024]; if (*partyline_buffer) { if (strncasecmp(partyline_buffer, "/me ", 4) == 0) { sockprintf(server_sock, "plineact %d %s", my_playernum, partyline_buffer+4); snprintf(buf, sizeof(buf), "* %s %s", players[my_playernum-1], partyline_buffer+4); msg_text(BUFFER_PLINE, buf); } else if (strcasecmp(partyline_buffer, "/start") == 0) { sockprintf(server_sock, "startgame 1 %d", my_playernum); } else if (strcasecmp(partyline_buffer, "/end") == 0) { sockprintf(server_sock, "startgame 0 %d", my_playernum); } else if (strcasecmp(partyline_buffer, "/pause") == 0) { sockprintf(server_sock, "pause 1 %d", my_playernum); } else if (strcasecmp(partyline_buffer, "/unpause") == 0) { sockprintf(server_sock, "pause 0 %d", my_playernum); } else if (strncasecmp(partyline_buffer, "/team", 5) == 0) { if (strlen(partyline_buffer) == 5) strcpy(partyline_buffer+5, " "); /* make it "/team " */ sockprintf(server_sock, "team %d %s", my_playernum, partyline_buffer+6); if (partyline_buffer[6]) { if (teams[my_playernum-1]) free(teams[my_playernum-1]); teams[my_playernum-1] = strdup(partyline_buffer+6); snprintf(buf, sizeof(buf), "*** %s is Now on Team %s", players[my_playernum-1], partyline_buffer+6); msg_text(BUFFER_PLINE, buf); } else { if (teams[my_playernum-1]) free(teams[my_playernum-1]); teams[my_playernum-1] = NULL; snprintf(buf, sizeof(buf), "*** %s is Now Alone", players[my_playernum-1]); msg_text(BUFFER_PLINE, buf); } } else { sockprintf(server_sock, "pline %d %s", my_playernum, partyline_buffer); if (*partyline_buffer != '/' || partyline_buffer[1] == 0 || partyline_buffer[1] == ' ') { /* We do not show server-side commands. */ snprintf(buf, sizeof(buf), "<%s> %s", players[my_playernum-1], partyline_buffer); msg_text(BUFFER_PLINE, buf); } } partyline_pos = 0; *partyline_buffer = 0; io->draw_partyline_input(partyline_buffer, partyline_pos); } } #undef curpos /*************************************************************************/ /*************************************************************************/ void help() { fprintf(stderr, "Tetrinet " VERSION " - Text-mode tetrinet client\n" "\n" "Usage: tetrinet [OPTION]... NICK SERVER\n" "\n" "Options (see README for details):\n" " -fancy Use \"fancy\" TTY graphics.\n" " -fast Connect to the server in the tetrifast mode.\n" " -log Log network traffic to the given file.\n" " -noshadow Do not make the pieces cast shadow.\n" " -noslide Do not allow pieces to \"slide\" after being dropped\n" " with the spacebar.\n" " -server Start the server instead of the client.\n" " -shadow Make the pieces cast shadow. Can speed up gameplay\n" " considerably, but it can be considered as cheating by\n" " some people since some other tetrinet clients lack this.\n" " -slide Opposite of -noslide; allows pieces to \"slide\" after\n" " being dropped. If both -slide and -noslide are given,\n" " -slide takes precedence.\n" " -windows Behave as much like the Windows version of Tetrinet as\n" " possible. Implies -noslide and -noshadow.\n" ); } int init(int ac, char **av) { int i; char *nick = NULL, *server = NULL; char buf[1024]; char nickmsg[1024]; unsigned char ip[4]; char iphashbuf[32]; int len; #ifdef BUILTIN_SERVER int start_server = 0; /* Start the server? (-server) */ #endif int slide = 0; /* Do we definitely want to slide? (-slide) */ /* If there's a DISPLAY variable set in the environment, default to * Xwindows I/O, else default to terminal I/O. */ /* if (getenv("DISPLAY")) io = &xwin_interface; else io = &tty_interface; */ io=&tty_interface; /* because Xwin isn't done yet */ srand(time(NULL)); init_shapes(); for (i = 1; i < ac; i++) { if (*av[i] == '-') { #ifdef BUILTIN_SERVER if (strcmp(av[i], "-server") == 0) { start_server = 1; } else #endif if (strcmp(av[i], "-fancy") == 0) { fancy = 1; } else if (strcmp(av[i], "-log") == 0) { log = 1; i++; if (i >= ac) { fprintf(stderr, "Option -log requires an argument\n"); return 1; } logname = av[i]; } else if (strcmp(av[i], "-noslide") == 0) { noslide = 1; } else if (strcmp(av[i], "-noshadow") == 0) { cast_shadow = 0; } else if (strcmp(av[i], "-shadow") == 0) { cast_shadow = 1; } else if (strcmp(av[i], "-slide") == 0) { slide = 1; } else if (strcmp(av[i], "-windows") == 0) { windows_mode = 1; noslide = 1; cast_shadow = 0; } else if (strcmp(av[i], "-fast") == 0) { tetrifast = 1; } else { fprintf(stderr, "Unknown option %s\n", av[i]); help(); return 1; } } else if (!nick) { my_nick = nick = av[i]; } else if (!server) { server = av[i]; } else { help(); return 1; } } if (slide) noslide = 0; #ifdef BUILTIN_SERVER if (start_server) exit(server_main()); #endif if (!server) { help(); return 1; } if (strlen(nick) > 63) /* put a reasonable limit on nick length */ nick[63] = 0; if ((server_sock = conn(server, 31457, ip)) < 0) { fprintf(stderr, "Couldn't connect to server %s: %s\n", server, strerror(errno)); return 1; } sprintf(nickmsg, "tetri%s %s 1.13", tetrifast ? "faster" : "sstart", nick); sprintf(iphashbuf, "%d", ip[0]*54 + ip[1]*41 + ip[2]*29 + ip[3]*17); /* buf[0] does not need to be initialized for this algorithm */ len = strlen(nickmsg); for (i = 0; i < len; i++) buf[i+1] = (((buf[i]&0xFF) + (nickmsg[i]&0xFF)) % 255) ^ iphashbuf[i % strlen(iphashbuf)]; len++; for (i = 0; i < len; i++) sprintf(nickmsg+i*2, "%02X", buf[i] & 0xFF); sputs(nickmsg, server_sock); do { if (!sgets(buf, sizeof(buf), server_sock)) { fprintf(stderr, "Server %s closed connection\n", server); disconn(server_sock); return 1; } parse(buf); } while (my_playernum < 0); sockprintf(server_sock, "team %d ", my_playernum); players[my_playernum-1] = strdup(nick); dispmode = MODE_PARTYLINE; io->screen_setup(); io->setup_partyline(); return 0; } /*************************************************************************/ int main(int ac, char **av) { int i; if ((i = init(ac, av)) != 0) return i; for (;;) { int timeout; if (playing_game && !game_paused) timeout = tetris_timeout(); else timeout = -1; i = io->wait_for_input(timeout); if (i == -1) { char buf[1024]; if (sgets(buf, sizeof(buf), server_sock)) parse(buf); else { msg_text(BUFFER_PLINE, "*** Disconnected from Server"); break; } } else if (i == -2) { tetris_timeout_action(); } else if (i == 12) { /* Ctrl-L */ io->screen_redraw(); } else if (i == K_F10) { break; /* out of main loop */ } else if (i == K_F1) { if (dispmode != MODE_FIELDS) { dispmode = MODE_FIELDS; io->setup_fields(); } } else if (i == K_F2) { if (dispmode != MODE_PARTYLINE) { dispmode = MODE_PARTYLINE; io->setup_partyline(); } } else if (i == K_F3) { if (dispmode != MODE_WINLIST) { dispmode = MODE_WINLIST; io->setup_winlist(); } } else if (dispmode == MODE_FIELDS) { tetris_input(i); } else if (dispmode == MODE_PARTYLINE) { if (i == 8 || i == 127) /* Backspace or Delete */ partyline_backspace(); else if (i == 4) /* Ctrl-D */ partyline_delete(); else if (i == 21) /* Ctrl-U */ partyline_kill(); else if (i == '\r' || i == '\n') partyline_enter(); else if (i == K_LEFT) partyline_move(-1); else if (i == K_RIGHT) partyline_move(1); else if (i == 1) /* Ctrl-A */ partyline_move(-2); else if (i == 5) /* Ctrl-E */ partyline_move(2); else if (i >= 1 && i <= 0xFF) partyline_input(i); } } disconn(server_sock); return 0; } /*************************************************************************/ #endif /* !SERVER_ONLY */ /*************************************************************************/ tetrinet/tetrinet.h0000664000175000017500000000456507726654403014264 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Tetrinet main include file. */ #ifndef TETRINET_H #define TETRINET_H #ifndef IO_H # include "io.h" #endif /*************************************************************************/ /* Basic types */ #define FIELD_WIDTH 12 #define FIELD_HEIGHT 22 typedef char Field[FIELD_HEIGHT][FIELD_WIDTH]; typedef struct { char name[32]; int team; /* 0 = individual player, 1 = team */ int points; int games; /* Number of games played */ } WinInfo; #define MAXWINLIST 64 /* Maximum size of winlist */ #define MAXSENDWINLIST 10 /* Maximum number of winlist entries to send * (this avoids triggering a buffer * overflow in Windows Tetrinet 1.13) */ #define MAXSAVEWINLIST 32 /* Maximum number of winlist entries to save * (this allows new players to get into * a winlist with very high scores) */ /*************************************************************************/ /* Overall display modes */ #define MODE_FIELDS 0 #define MODE_PARTYLINE 1 #define MODE_WINLIST 2 #define MODE_SETTINGS 3 #define MODE_CLIENT 4 /* Client settings */ #define MODE_SERVER 5 /* Server settings */ /*************************************************************************/ /* Key definitions for input. We use K_* to avoid conflict with ncurses */ #define K_UP 0x100 #define K_DOWN 0x101 #define K_LEFT 0x102 #define K_RIGHT 0x103 #define K_F1 0x104 #define K_F2 0x105 #define K_F3 0x106 #define K_F4 0x107 #define K_F5 0x108 #define K_F6 0x109 #define K_F7 0x10A #define K_F8 0x10B #define K_F9 0x10C #define K_F10 0x10D #define K_F11 0x10E #define K_F12 0x10F /* For function keys that don't correspond to something above, i.e. that we * don't care about: */ #define K_INVALID 0x7FFF /*************************************************************************/ /* Externs */ extern int fancy; extern int log; extern char *logname; extern int windows_mode; extern int noslide; extern int tetrifast; extern int cast_shadow; extern int my_playernum; extern WinInfo winlist[MAXWINLIST]; extern int server_sock; extern int dispmode; extern char *players[6]; extern char *teams[6]; extern int playing_game; extern int not_playing_game; extern int game_paused; extern Interface *io; /*************************************************************************/ #endif tetrinet/tetrinet.txt0000664000175000017500000005672407726062547014662 0ustar rhondarhondaTetriNET v1.13 PUBLIC By: St0rmCat (If he's not in #tetrinet, he's the wrong St0rmCat) E-mail: stormcat@citilink.com Channel: EFNET: #TetriNET - Come join us to get some active servers, advertise your server, find out more about TetriNET, or just chat!! Gee, Tetris? Whats that? Yeah, yeah, so it's been done 2 billion times before (give or take a few bil), but this is different! Yes, all you tetris addicts out there be prepared for even MORE excitement.. internet tetris! Now you can play your five best buddies in one of the most well known games in existence! TetriNET is for Windows95/NT machines and WILL NOT RUN WITH WIN32S. TetriNET operates best on a display capable of 256 colors or more. If your screen resolution is 640x480, you may have to set your windows taskbar to auto-hide while playing to see both windows entirely. PLEASE CONSIDER CONTRIBUTING - READ SCREEN ON TETRINET STARTUP FOR DETAILS CONTENTS: Concept - Game explanation Inventory - Explanation of inventory feature (READ!!) Win list - How the game keeps score Partyline - Explanation and features Moderators - What moderators are for Server - What is and how to set up a SERVER Client - What a client is Teams - Explanation of teams Modifying - How to modify TetriNET Acknowledgements - People who helped alot in the making of TetriNET VERSIONS - Whats been changed throughout the versions Concept: According to how the server has modified the settings, occasionally a sphere block with a letter will appear in your playing field. When you make one or more lines with any of the lines containing one or more of these special blocks, the special blocks in the lines will be added to your inventory which is located near the bottom of the window. These special blocks do different things to players. Each effect for the special blocks will be explained below. The more lines you clear at the same time and the more special blocks that are in those lines will put more special blocks in your inventory. A player loses when his/her blockstack reaches the top of the field. The Winner is the player left after the other players have lost. Again, Depending on how the server has modified settings, you will gain a level (or more) everytime you clear out a certain amount of lines. When you gain levels, the fall speed of the blocks coming down will increase making it harder to find a decent place for the blocks. The maximum level you can be at is 100. Inventory: (READ!!!) As said above, special blocks are added to your inventory when you clear lines with special blocks in them. You are only able to use the special block inside the red box. The red box cannot be moved or the game would be incredibly easy. The description of what the special block does to a player is located to the left of the inventory. To use the current special block, press the player number of the player you want to attack. If the special block is a GOOD block, use it on yourself by pressing your player number. Here is a list of all the special blocks: * 'A' Block: This is the add line block. It will add a "junk line" to the players field that you choose. $ 'C' Block: This is the clear line block. It clears the line farthest to the bottom in the playing field. $ 'N' Block: This removes all blocks from a playing field giving the player a "fresh start". * 'R' Block: This removes 10 random blocks from a player's field. * 'S' Block: This switches your playing field with another player's field depending on which player number you pressed. If either of the fields' block stack is over 16 blocks high, the stack will be lowered. * 'B' Block: This removes all special blocks from a players field. $ 'G' Block: This "gravity" block takes all the blocks on the field and "pulls" them all towards the bottom of the field eliminating any gaps in the blockstack. * 'Q' Block: This causes each of the lines of blocks on a players field to randomly shift left or right or not at all. * 'O' Block: This is the block bomb, when used on a player, it clears 3x3 portions on their field anywhere there are 'o' blocks. Any blocks that were in the 3x3 areas are scattered around the field. IMPORTANT: Blocks with a '*' at the beginning of the line are ATTACK special blocks; use these on other players. Blocks with a '$' at the beginning are DEFENSE blocks and should be used on yourself because they are GOOD You can also press the letter 'D' and it will discard the first block in your inventory. When your inventory becomes full, any special blocks you get cannot go into your inventory. You will have to use up some of your inventory blocks. Win list: The win list keeps track of how points each person or team has. The person or team with the most points is at the top. A player or team gets 3 points when they are the last standing in a game. 2 points to the player/team that is the last person to lose and 1 point to the 2nd to last loser. When a team wins, their team name is put in the win list along with a infront of it to show everyone that it's a team and not a person. The win list on each of the player's clients are the same. The server can reset the win list at any time by pressing the 'Reset Winlist' button on the Server screen. Partyline Screen: This is where the players connected to the server can talk among each other. You can do bold/underline/italic by surrounding the text you want affected with characters made from pressing ctl-b/u/i respectively. You can also do actions on the partyline by typing: /ME Next to the partyline box is a list of all the players currently connected to the server. The server can kick a player out by selecting his/her name in the nicklist and pressing the KICK button. The server can also ban someone from the server completely by selecting their nickname and pressing Ban. For the ban to take effect, you must kick them. Also, when you click on the player's nickname in the list, (only if you're a server) their IP will be shown above the box. To change the font of the main partyline box, press the right mouse button in the window and then click on the 'Change Font...' menu item. You can also easily talk while the game is going by pressing 'T' while on the Playing Fields window and then entering your message. After you press return the message will be broadcasted to the rest of the players and will be shown in the black box near the bottom. You can press ESCAPE while putting text in the edit box and it will hide the edit box. When you press 'T', you can start typing again. Moderators: A moderator is the first person on a server whether it is the person that is actually operating the server, or if the server isn't playing, the first player that connects to the server. A moderator person has a '*' next to their nickname in the nickname list on the partyline screen. A moderator can start/stop/pause/unpause games on a server. Server: To play, you must have someone be the TetriNET server. The server should be fast so the game isn't too lagged. A 28.8 modem is great for a fun TetriNET game. The server can set all of the settings available for the game, and decides when the game starts, etc. Fill in your Nickname into the correct edit box and any options under that. When you press the Block Occurancy button on the server screen, a window will pop up. On this window you can set how often the different pieces and special blocks appear (or not at all) throughout the game. You enter in the percentage for each piece/block. 100 means the piece/block will appear every single time; 0 means it wont appear at all. Note: All the percents of the pieces and special blocks must add up to be 100 percent before you can play. The ban box on the server screen is where you can put IPs of people you dont want on your TetriNET server. To ban a person, place only their IP ADDRESS on its own line in the ban box. You can use wildcards like '*' and '?' Besides NOT allowing someone on the server, you can also ALLOW certain people on it with the '!' prefix before a person's IP. When you prefix an IP with '!' in the ban box, this overrides any other bans in the box and lets the person with this IP join. If you put '*' as the first line in the box (it bans EVERYONE), and then put '!204.246.67.9' on a line after that, the person with that IP would be allowed in but noone else would be. When done, press the Start Server button. Now players can connect to your IP. When a player connects, a join message will be shown in the partyline box. When a message is added to the partyline box and you are currently on another screen, the partyline button text will turn green to notify you. When everyone is ready to play, click on the Start New Game button. Originally, the levels of each player are individual and when a player's level increases, only THEIR level increases. The 'All Have Players' Averaged Levels' option will make all players have the same level throughout the game. When a player's REAL level increases, it is averaged in with the rest of the players' levels and that is the ACTIVE level of everyone on the server. The server also has the option to play or not to. Enabling the 'Server Play' checkbox on the server screen will put yourself in the game as player 1. If there was any player on as player 1, they are kicked out. Unchecking this box will take you off and you will not play. To speed up a game of TetriNET, there is an option on the server screen for after a certain amount of minutes playing, the game will start adding lines to all players every certain amount of seconds. This is useful to keep players on your server if they lose early and get bored. Another option is the Classic Multiplayer Rules. This makes the game play like the old 2 player gameboy version of tetris. When a player clears lines, lines are added to everyone else's field. If you clear 1 line, no lines are added to everyone else. If you clear 2 lines, 1 line is added to everyone else. If you clear 3, 2 is added. If you clear 4, 4 lines are added to everyone. NOTE: This option is best when you set the 'Special Blocks Added at a Time' option to '0'. Client: A client is just a person playing that is not the server. A client connects to a server. Find the IP of a server with people you want to play with and put that in to the correct edit box. Then put your nickname in the Nickname box and press the Connect button. You should then see the nicknames playing pop up in the partyline window and any teams the players are on. When the game has started, click on the Show Fields button and a window with all 6 fields will be shown. The fields will be updated every time a player's block is placed onto their field to keep everyone updated on who's winning. Teams: Teams are now possible in TetriNET. You can now have matches with 3 teams of 2 people or whatever you want! To be on a team, just fill out the Team edit box with the team you would like to be on, if on a server click the button right next to the box to update your team name. The team name is not case-sensitive. When you are on a team, if one of the players that is on your team wins a TetriNET game, you win too and your team name is placed in the Win List instead of your individual names. Modifying TetriNET: Themes are handled by a "project file" to tell TetriNET where it should find all the theme components. It is just an INI file. When you create a theme, copy over the default.tnp file to a new file that represents your theme and make a new directory in your TetriNET directory where all your theme stuff will be. Any files you want different or modified, just put them in your theme directory and put the paths for the new files in your project file. If you want to make a theme pack with like only the graphics changed but you want it to have same sounds and music, only change the graphics releated lines in your theme project file and leave the rest of the lines pointing to the default DATA\ directory. Explanations of Graphics/Music/Sounds: When you create a new graphics file, the file MUST have the same image dimensions and have the same block layout of the one included with the original tetrinet ZIP file. Here is a description of the layout: - The top row of blocks are the blocks for the big field in TetriNET. They are 16 pixels wide and 16 pixels high. There should be 14 blocks in the row. - The row under that are the same blocks as the normal 16 pixel blocks, but are half as wide. These smaller blocks are for the 5 other fields on the TetriNET field. They are 8 pixels across and 8 pixels high. * The Upper-left coordinates of the first block in this row are at (0,16) - The picture under this is the background image for the large field in TetriNET. It is 192 pixels wide and 352 pixels high. * The Upper-left coordinates of this image is (0,24) - The picture to the right is the miniture version of the main background image. It is used as the background for the smaller fields in TetriNET. It is 96 pixels wide and 176 pixels high. * The Upper-left coordinates of this image is (352,24) Your new graphic file does NOT have to have the same palette as the one included in the TetriNET zip. If you use Photoshop, work in RGB mode while modifying the file. When you're done, convert it back to 256 colors to make sure the colors look as good as possible. You can also leave the BMP in 16million colors mode if you have a monitor capable of that. If you are going to distribute your theme, it is best to convert it to 256 colors because many don't have systems capable of high color mode. The sound descriptions are listed in default.tnp file. Modify any of the sounds you want, but make sure you modify the project file if you changed any of the WAV filenames. You can also include a MIDI file inside the theme package. Any sound/graphic themes created can be distributed freely. You can also modify the keys. Click on the Misc. Settings button. Click in any of the boxes and press a key and the text in the edit box will change to the key that you pressed. Acknowledgements: Darktick for all his ideas and help with graphics coding on TetriNET. myname, [bart], and Galardo because they kept playing and playing as betas went by - also for all the bugs they found and suggestions they gave. Knowledge, ReT, and the #square crew for beta testing, holding the #TetriNET channel, and giving suggestions. Also, netmonk, ATA, and phib for all of the above :) The many other people I know ;) for their support and testing they've done for me. VERSIONS: New in 1.13 PUBLIC: 1. Fixed teams of other players not being shown to people sometimes 2. Fixed win list from counting people that come in during a game as a person that lost New in 1.12 PUBLIC: 1. Fixed server not allowing clients to connect in many conditions 2. Added team switching while connected to a server 3. Fixed not saving key settings 4. Code for receiving text from winsock is smaller 5. Any lines transfered with classic style rules to the server and the server isn't playing, they are ignored. 6. Fixed it from adding lines to other team members in classic rules New in 1.11 PUBLIC: 1. Fixed people not being able to start working servers or not being able to connect to servers. New in 1.1 PUBLIC: 1. While playing a game with classic multiplayer rules turned on, anyone on your team wont be affected if you add lines to everyone else. 2. Fixed minor display quirks 3. If system doesn't support MIDI, it won't crash 4. Fixed a player double losing 5. Fixed out of memory error 6. Fixed player being added to win list when player isnt on server 7. RELEASE! New in 1.1f BETA: 1. Fixed EConvert errors (right? :)) 2. Fixed displaying of teams on the playing fields New in 1.1e BETA: 1. Tabbing between edit boxes/checkboxes/etc on the windows is now in order 2. Fixed win list from making doubles of winners 3. Playing fields window is now hidden when a game ends 4. Fixed background/blocks not appearing sometimes when setting focus to the playing fields window 5. Fixed game from saying you gave other people lines when you only made one line. (with classic multiplayer rules turned on) 6. Fixed 'o' blocks sometimes not "blowing up" when attacked with Block Bomb 7. Sounds 3.WAV and 6.WAV's description were switched around in the docs New in 1.1d BETA: 1. Fixed font in main chatbox not changing on start. 2. Fixed blinking when a message arrives 3. Fixed bug in win list routines 4. Fixed bug where if a person lost, server always won 5. Fixed random EConvertError crashes New in 1.1c BETA: 1. Made the classic multiplayer rules like gameboy now :) 2. Fixed partyline not scrolling 3. Ingame chat box should show all 3 lines on all types of systems 4. Added ability for multiple themes.. it uses a project file that points to the graphics,sounds, and music files 5. Server can change its nickname and team its on if the Server Playing checkbox is unchecked 6. Fixed server not losing when the Server Playing is checked while a game is in progress 7. Now puts a before any team in the win list. 8. Changed the way Block Bomb works New in 1.1b BETA: 1. Fixed bug in text recieving 2. Fixed other minor bugs New in 1.1a BETA: 1. Fixed the scrolling on the attacks/defenses box and mini-partyline box AGAIN 2. Now when the Switch Special Block is used on someone each client updates itself first to prevent field copying if switch block is used 2 or more times in a row 3. Ingame chat box is now 3 lines 4. Secured connect negotiation between server and client 5. Added bomb special block 6. Added /me to ingame chat box 7. Added teams 8. Added MIDI 9. Added background images 10. Added start up sound 11. The server now has the option to not play 12. The first person on a server (if the server isn't playing) is a moderator - he can start/stop and pause/unpause the game 13. Option to average the player's levels 14. Classic rules option - when a player clears a line, it adds a line to the other players 15. Fixed bug in IP ban box 16. Players can join a game already in progress 17. Winlist is now saved when you close and reload game 18. Option for after a certain amount of time, the game will start adding lines to all the player's fields. This speeds up games 19. Now, the winning team/player recieves 3 points, last loser gets 2, second to last loser gets 1 point. 20. The server can set the stack height when the game starts for each player individually. 21. Levels go up to 100 instead of 50 22. Field displaying has been sped up alot 23. Better handling of winsock routines HOPEFULLY eliminating the Access Violation errors 24. Fixed server screen not being enabled when a client game has ended. 25. You can press ESCAPE when editing a msg in game, and it will keep the text in the editbox until you press 't' to edit it again. 26. You can change the font of the partyline box by right clicking. 27. Moved Pause and Start New Game buttons to partyline screen. 28. Any special blocks you get are now randomly placed in your inventory New in 1.0 FINAL: 1. First released version!! 2. Fixed Attacks/Defenses box's scrolling *again* 3. Fixed range check error when typing in a box while tetrinet is connecting to a server 4. Now saves your nickname in the INI New in BETA 1.0b: 1. Fixed Attacks/Defenses box's scrolling and now it doesn't say a player is attacking you when in fact they aren't 2. Fixed Pause button not enabling and disabling properly 3. Partyline edit box now disables at the right times 4. New icon New in BETA 1.0a: 1. Kicking users is fixed and works/looks alot better 2. When a player dies, their field is filled with random blocks 3. You can now pause the game. 4. Fixed not removing player from server if they quit abnormally 5. Tabbing between edit fields on the different screens is now in order 6. You can now see all attacks on players, even from other players 7. Fixed when making the piece fall down immediately, sometimes it would jump up New in BETA 1.0: AKA BETA .88 New in BETA .88: 1. Fixed distortion of window if your monitor's pixels per inch settings were set akwardly 2. Moderated Switch Field block now, no more cheating ;) New in BETA .87: 1. Hopefully fixed the bug that randomly kicked players off the server. 2. Added description label next to inventory for current special block. 3. Made windows smaller to better accommodate users with 640x480 resolutions. (You will still have to auto-hide your win95/nt taskbar) New in BETA .86: 1. New WAV for when a message arrives in the playing window 2. Added IP Banlist 3. TetriNET now pops up the reason why you can't connect to a server 4. Now you have more time to move a piece left or right when it's just about to hit the bottom. 5. Other minor bugs fixed New in BETA .85: 1. Greatly improved handling of data coming in from multiple sockets. It shouldn't crash anymore (?) 2. TetriNET now passes a little less data to the other players. A fast modem is still recommended if you're the server. 3. Added easy talking while playing New in BETA .84: 1. New name because of copyrights ;) 2. Rearranged playing field window 3. Rotating certain pieces at the edge of the field now works 4. Pieces drop slower than before when you hold DOWN (for greater accuracy) New in BETA .83: 1. Fixed partyline jumping every once in awhile 2. Kick button is disable properly New in BETA .82: 1. I've added 2 more players. Now you can compete with a total of 5 players! 2. Added player list to the partyline screen 3. The Server can kick someone off if needed 4. Added actions to partyline 5. Added ctl-b/u/i character attributes to partyline 6. Now allows playing by yourself so you can practice 7. Many new special blocks added 8. The show fields screen is now a seperate window (to fix all that stupid palette handling) 9. Added a winlist. It shows all the players who won and how many times they've won in the current game. 10. Added ability to discard the special block in inventory 11. A title screen with some nifty graphics! :) 12. Graphics are just one file now. 13. Fixed a few barely noticable bugs New in BETA .81: 1. Inventory system plus other special blocks -- more to come! 2. Added sound! 3. New graphics 4. You can change Key config, sounds, and graphics 5. Made playing fields of other player's smaller to reduce lag 6. Before, TetriNET sent the entire playing field of a player to the rest of the players. Now it sends just modifications to the fields - this also reduces lots of lag 7. Increased size of playing fields 8. You can change how often each of the pieces/special blocks appear in the game tetrinet/tetris.c0000664000175000017500000006410010671277701013715 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Tetris core. */ #include #include #include #include #include "tetrinet.h" #include "tetris.h" #include "io.h" #include "sockets.h" /*************************************************************************/ int piecefreq[7]; /* Frequency (percentage) for each block type */ int specialfreq[9]; /* Frequency for each special type */ int old_mode; /* Old mode? (i.e. Gameboy-style) */ int initial_level; /* Initial level */ int lines_per_level; /* Number of lines per level-up */ int level_inc; /* Levels to increase at each level-up */ int level_average; /* Average all players' levels */ int special_lines; /* Number of lines needed for a special block */ int special_count; /* Number of special blocks added each time */ int special_capacity; /* Capacity of special block inventory */ Field fields[6]; /* Current field states */ int levels[6]; /* Current levels */ int lines; /* Lines completed (by us) */ signed char specials[MAX_SPECIALS] = {-1}; /* Special block inventory */ int next_piece; /* Next piece to fall */ static struct timeval timeout; /* Time of next action */ int current_piece; /* Current piece number */ int current_rotation; /* Current rotation value */ int current_x; /* Current X position */ int current_y; /* Current Y position */ static int piece_waiting; /* Are we waiting for a new piece to start? */ static int last_special; /* Last line for which we added a special */ /*************************************************************************/ #ifndef SERVER_ONLY /*************************************************************************/ /* The array of piece shapes. It is organized as: * - 7 pieces * - 4 rows * - 4 rotations (ordered clockwise) * - 4 points * A . is an empty point, a # is a full one. An X (upper-case) represents * the "hot-spot" of the piece; this is where the coordinates are fastened * to, and is used to determine the piece's new position after rotation. * If the location for an X empty, use a lowercase letter instead. * * This is all parsed by init_shapes, which should be called at startup. */ static const char shapes[7][4][4][4] = { { { "##X#", "..X.", "##X#", "..X." }, { "....", "..#.", "....", "..#." }, { "....", "..#.", "....", "..#." }, { "....", "..#.", "....", "..#." } }, { { "....", "....", "....", "...." }, { ".X#.", ".X#.", ".X#.", ".X#." }, { ".##.", ".##.", ".##.", ".##." }, { "....", "....", "....", "...." } }, { { "....", ".#..", "#...", ".##." }, { "#X#.", ".X..", "#X#.", ".X.." }, { "..#.", "##..", "....", ".#.." }, { "....", "....", "....", "...." } }, { { "....", "##..", "..#.", ".#.." }, { "#X#.", ".X..", "#X#.", ".X.." }, { "#...", ".#..", "....", ".##." }, { "....", "....", "....", "...." } }, { { "....", ".#..", "....", ".#.." }, { "#X..", "#X..", "#X..", "#X.." }, { ".##.", "#...", ".##.", "#..." }, { "....", "....", "....", "...." } }, { { "....", "#...", "....", "#..." }, { ".X#.", "#X..", ".X#.", "#X.." }, { "##..", ".#..", "##..", ".#.." }, { "....", "....", "....", "...." } }, { { "....", ".#..", ".#..", ".#.." }, { "#X#.", "#X..", "#X#.", ".X#." }, { ".#..", ".#..", "....", ".#.." }, { "....", "....", "....", "...." } } }; /* piecedata[piece][rot]; filled in by init_shapes() */ PieceData piecedata[7][4]; /*************************************************************************/ /* Parse the shapes array and fill in the piece data. */ void init_shapes(void) { int i, x, y, r; for (i = 0; i < 7; i++) { for (r = 0; r < 4; r++) { piecedata[i][r].hot_x = -1; piecedata[i][r].hot_y = -1; piecedata[i][r].top = 3; piecedata[i][r].left = 3; piecedata[i][r].bottom = 0; piecedata[i][r].right = 0; for (y = 0; y < 4; y++) { for (x = 0; x < 4; x++) { switch (shapes[i][y][r][x]) { case '.': piecedata[i][r].shape[y][x] = 0; break; case '#': piecedata[i][r].shape[y][x] = 1; if (piecedata[i][r].top > y) piecedata[i][r].top = y; if (piecedata[i][r].left > x) piecedata[i][r].left = x; if (piecedata[i][r].bottom < y) piecedata[i][r].bottom = y; if (piecedata[i][r].right < x) piecedata[i][r].right = x; break; case 'x': piecedata[i][r].shape[y][x] = 0; piecedata[i][r].hot_x = x; piecedata[i][r].hot_y = y; break; case 'X': piecedata[i][r].shape[y][x] = 1; if (piecedata[i][r].top > y) piecedata[i][r].top = y; if (piecedata[i][r].left > x) piecedata[i][r].left = x; if (piecedata[i][r].bottom < y) piecedata[i][r].bottom = y; if (piecedata[i][r].right < x) piecedata[i][r].right = x; piecedata[i][r].hot_x = x; piecedata[i][r].hot_y = y; break; default : fprintf(stderr, "Piece %d rotation %d: " "weird character `%c' at (%d,%d)\n", i, r, shapes[i][y][r][x], x, y); exit(1); } } } if (piecedata[i][r].hot_x < 0 || piecedata[i][r].hot_y < 0) { fprintf(stderr, "Piece %d rotation %d missing hot spot!\n", i, r); exit(1); } } } } /*************************************************************************/ /* Retrieve the shape for the given piece and rotation. Return -1 if piece * or rotation is invalid, else 0. */ int get_shape(int piece, int rotation, char buf[4][4]) { int x, y; char *shape; if (piece < 0 || piece > 6 || rotation < 0 || rotation > 3) return -1; shape = (char *) piecedata[piece][rotation].shape; for (y = 0; y < 4; y++) { for (x = 0; x < 4; x++) { buf[y][x] = *shape++ ? piece%5 + 1 : 0; } } return 0; } /*************************************************************************/ /*************************************************************************/ /* Return the number of milliseconds of delay between piece drops for the * current level. */ static int level_delay() { int level = levels[my_playernum-1]; int delay = 1000; while (--level) delay = (delay*69+35)/70; /* multiply by 69/70 and round */ return delay; } /*************************************************************************/ /* Return whether the piece in the position given by the x, y, and rot * variables (interpreted the same way as current_*) would overlap any * other blocks in the field. A value of -1 means use the current_* value. */ static int piece_overlaps(int x, int y, int rot) { Field *f = &fields[my_playernum-1]; PieceData *pd; int i, j, ok; if (x < 0) x = current_x; if (y < 0) y = current_y; if (rot < 0) rot = current_rotation; pd = &piecedata[current_piece][rot]; x -= pd->hot_x; y -= pd->hot_y; ok = 1; for (j = 0; ok && j < 4; j++) { if (y+j < 0) continue; for (i = 0; ok && i < 4; i++) { if (pd->shape[j][i] && (y+j >= FIELD_HEIGHT || x+i < 0 || x+i >= FIELD_WIDTH || (*f)[y+j][x+i])) ok = 0; } } return !ok; } /*************************************************************************/ /* Draw the piece in its current position on the board. If draw == 0, then * erase the piece rather than drawing it. */ static void draw_piece(int draw) { Field *f = &fields[my_playernum-1]; char c = draw ? current_piece % 5 + 1 : 0; int x = current_x - piecedata[current_piece][current_rotation].hot_x; int y = current_y - piecedata[current_piece][current_rotation].hot_y; char *shape = (char *) piecedata[current_piece][current_rotation].shape; int i, j; for (j = 0; j < 4; j++) { if (y+j < 0) { shape += 4; continue; } for (i = 0; i < 4; i++) { if (*shape++) (*f)[y+j][x+i] = c; } } } /*************************************************************************/ /* Clear any full lines on the field; return the number of lines cleared. */ static int clear_lines(int add_specials) { Field *f = &fields[my_playernum-1]; int x, y, count = 0, i, j, k; int new_specials[9]; for (y = 0; y < FIELD_HEIGHT; y++) { int full = 1; for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] == 0) { full = 0; break; } } if (full) count++; } memset(new_specials, 0, sizeof(new_specials)); for (y = 0; y < FIELD_HEIGHT; y++) { int full = 1; for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] == 0) { full = 0; break; } } if (full) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] > 5) new_specials[(*f)[y][x]-6]++; } if (y > 0) memmove((*f)[1], (*f)[0], FIELD_WIDTH*y); memset((*f)[0], 0, FIELD_WIDTH); } } if (add_specials) { int pos = 0; while (pos < special_capacity && specials[pos] >= 0) pos++; for (i = 0; i < count && pos < special_capacity; i++) { for (j = 0; j < 9 && pos < special_capacity; j++) { for (k = 0; k < new_specials[j] && pos < special_capacity; k++){ if (windows_mode && rand()%2) { memmove(specials+1, specials, pos); specials[0] = j; pos++; } else specials[pos++] = j; } } } if (pos < special_capacity) specials[pos] = -1; io->draw_specials(); } return count; } /*************************************************************************/ /* Place the given number of specials on the field. If there aren't enough * blocks to replace, replace all of the blocks and drop the rest of the * specials. */ static void place_specials(int num) { Field *f = &fields[my_playernum-1]; int nblocks = 0, left; int x, y, tries; for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x]) nblocks++; } } if (num > nblocks) num = nblocks; left = num; tries = 10; while (left > 0 && tries > 0) { for (y = 0; left > 0 && y < FIELD_HEIGHT; y++) { for (x = 0; left > 0 && x < FIELD_WIDTH; x++) { if ((*f)[y][x] > 5 || (*f)[y][x] == 0) continue; if (rand() % nblocks < num) { int which = 0, n = rand() % 100; while (n >= specialfreq[which]) { n -= specialfreq[which]; which++; } (*f)[y][x] = 6 + which; left--; } } } tries--; } } /*************************************************************************/ /* Send the new field, either as differences from the given old field or * (if more efficient) as a complete field. If oldfield is NULL, always * send the complete field. */ static void send_field(Field *oldfield) { Field *f = &fields[my_playernum-1]; int i, x, y, diff = 0; char buf[512], *s; if (oldfield) { for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] != (*oldfield)[y][x]) diff++; } } } else { diff = FIELD_WIDTH * FIELD_HEIGHT; } if (diff < (FIELD_WIDTH*FIELD_HEIGHT)/2) { s = buf + sprintf(buf, "f %d ", my_playernum); for (i = 0; i < 15; i++) { int seen = 0; /* Have we seen a difference of this block? */ for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] == i && (*f)[y][x] != (*oldfield)[y][x]) { if (!seen) { *s++ = i + '!'; seen = 1; } *s++ = x + '3'; *s++ = y + '3'; } } /* for x */ } /* for y */ } /* for i (each tile type) */ } /* difference check */ /* -4 below is to adjust for "f %d " */ if (diff >= (FIELD_WIDTH*FIELD_HEIGHT)/2 || strlen(buf)-4 > FIELD_WIDTH*FIELD_HEIGHT) { static const char specials[] = "acnrsbgqo"; s = buf + sprintf(buf, "f %d ", my_playernum); for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] > 5) *s++ = specials[(*f)[y][x]-6]; else *s++ = (*f)[y][x] + '0'; } } } *s = 0; sputs(buf, server_sock); } /*************************************************************************/ /*************************************************************************/ /* Generate a new piece and set up the timer. */ void new_piece(void) { int n; PieceData *pd; current_piece = next_piece; n = rand() % 100; next_piece = 0; while (n >= piecefreq[next_piece] && next_piece < 6) { n -= piecefreq[next_piece]; next_piece++; } current_rotation = 0; pd = &piecedata[current_piece][current_rotation]; current_x = 6; current_y = pd->hot_y - pd->top; if (piece_overlaps(-1, -1, -1)) { current_x--; if (piece_overlaps(-1, -1, -1)) { current_x += 2; if (piece_overlaps(-1, -1, -1)) { Field *f = &fields[my_playernum-1]; int x, y; for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) (*f)[y][x] = rand()%5 + 1; } send_field(NULL); sockprintf(server_sock, "playerlost %d", my_playernum); playing_game = 0; not_playing_game = 1; } } } draw_piece(1); io->draw_status(); io->draw_own_field(); gettimeofday(&timeout, NULL); timeout.tv_usec += level_delay() * 1000; timeout.tv_sec += timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; piece_waiting = 0; } /*************************************************************************/ /* Step the current piece down one space. If it's already as far as it can * go, solidify it, check for completed lines, send the new field state, * and start a new piece. */ void step_down(void) { Field *f = &fields[my_playernum-1]; PieceData *pd = &piecedata[current_piece][current_rotation]; int y = current_y - pd->hot_y; int ynew; draw_piece(0); ynew = current_y+1; if (y+1 + pd->bottom < FIELD_HEIGHT && !piece_overlaps(-1, ynew, -1)) { current_y++; draw_piece(1); io->draw_own_field(); gettimeofday(&timeout, NULL); timeout.tv_usec += level_delay() * 1000; timeout.tv_sec += timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; } else { int completed, level, nspecials; Field oldfield; char buf[16]; memcpy(&oldfield, f, sizeof(oldfield)); draw_piece(1); if (last_special > lines) /* i.e. from a previous game */ last_special = 0; completed = clear_lines(1); lines += completed; if (old_mode && completed > 1) { if (completed < 4) completed--; sockprintf(server_sock, "sb 0 cs%d %d", completed, my_playernum); sprintf(buf, "cs%d", completed); io->draw_attdef(buf, my_playernum, 0); } level = initial_level + (lines / lines_per_level) * level_inc; if (level > 100) level = 100; levels[my_playernum] = level; if (completed > 0) { sockprintf(server_sock, "lvl %d %d", my_playernum, level); io->draw_status(); } nspecials = (lines - last_special) / special_lines; last_special += nspecials * special_lines; nspecials *= special_count; place_specials(nspecials); io->draw_own_field(); send_field(&oldfield); piece_waiting = 1; gettimeofday(&timeout, NULL); timeout.tv_usec += tetrifast ? 0 : 600000; timeout.tv_sec += timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; } } /*************************************************************************/ /* Do something for a special block. */ void do_special(const char *type, int from, int to) { Field *f = &fields[my_playernum-1]; Field oldfield; int x, y; io->draw_attdef(type, from, to); if (!playing_game) return; if (to != 0 && to != my_playernum && !(from==my_playernum && *type=='s')) return; if (!piece_waiting) draw_piece(0); memcpy(&oldfield, f, sizeof(Field)); if (strncmp(type, "cs", 2) == 0) { int nlines = atoi(type+2); /* Don't add lines from a team member */ if (!teams[my_playernum-1] || !teams[from-1] || strcmp(teams[my_playernum-1],teams[from-1]) != 0 ) { while (nlines--) { memmove((*f)[0], (*f)[1], FIELD_WIDTH*(FIELD_HEIGHT-1)); for (x = 0; x < FIELD_WIDTH; x++) (*f)[21][x] = 1 + rand()%5; (*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0; } } } else if (*type == 'a') { memmove((*f)[0], (*f)[1], FIELD_WIDTH*(FIELD_HEIGHT-1)); for (x = 0; x < FIELD_WIDTH; x++) (*f)[21][x] = 1 + rand()%5; (*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0; (*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0; (*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0; } else if (*type == 'b') { for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] > 5) (*f)[y][x] = rand()%5 + 1; } } } else if (*type == 'c') { memmove((*f)[1], (*f)[0], FIELD_WIDTH*(FIELD_HEIGHT-1)); memset((*f)[0], 0, FIELD_WIDTH); } else if (*type == 'g') { for (x = 0; x < FIELD_WIDTH; x++) { y = FIELD_HEIGHT-1; while (y > 0) { if ((*f)[y][x] == 0) { int y2, allclear = 1; for (y2 = y-1; allclear && y2 >= 0; y2--) { if ((*f)[y2][x]) allclear = 0; } if (allclear) break; for (y2 = y-1; y2 >= 0; y2--) (*f)[y2+1][x] = (*f)[y2][x]; (*f)[0][x] = 0; } else y--; } } clear_lines(0); } else if (*type == 'n') { memset(*f, 0, FIELD_WIDTH*FIELD_HEIGHT); } else if (*type == 'o') { int tries, x2, y2, xnew, ynew; for (y = 0; y < FIELD_HEIGHT; y++) { for (x = 0; x < FIELD_WIDTH; x++) { if ((*f)[y][x] != 6 + SPECIAL_O) continue; (*f)[y][x] = 0; for (y2 = y-1; y2 <= y+1; y2++) { if (y2 < 0 || y2 >= FIELD_HEIGHT) continue; for (x2 = x-1; x2 <= x+1; x2++) { if (x2 < 0 || x2 >= FIELD_WIDTH) continue; if (!windows_mode && !(*f)[y2][x2]) continue; tries = 10; while (tries--) { xnew = random() % FIELD_WIDTH; ynew = FIELD_HEIGHT-1 - random()%16; if (windows_mode || !(*f)[ynew][xnew]) { (*f)[ynew][xnew] = (*f)[y2][x2]; break; } } (*f)[y2][x2] = 0; } } } } clear_lines(0); } else if (*type == 'q') { for (y = 0; y < FIELD_HEIGHT; y++) { int r = rand()%3 - 1; if (r < 0) { int save = (*f)[y][0]; memmove((*f)[y], (*f)[y]+1, FIELD_WIDTH-1); if (windows_mode) (*f)[y][FIELD_WIDTH-1] = 0; else (*f)[y][FIELD_WIDTH-1] = save; } else if (r > 0) { int save = (*f)[y][FIELD_WIDTH-1]; memmove((*f)[y]+1, (*f)[y], FIELD_WIDTH-1); if (windows_mode) (*f)[y][0] = 0; else (*f)[y][0] = save; } } } else if (*type == 'r') { int i; for (i = 0; i < 10; i++) { x = rand() % FIELD_WIDTH; y = rand() % FIELD_HEIGHT; if ((*f)[y][x] != 0) { (*f)[y][x] = 0; break; } } } else if (*type == 's') { Field temp; memcpy(temp, fields[from-1], sizeof(Field)); memcpy(fields[from-1], fields[to-1], sizeof(Field)); memcpy(fields[to-1], temp, sizeof(Field)); if (from == my_playernum || to == my_playernum) memset(fields[my_playernum-1], 0, 6*FIELD_WIDTH); if (from != my_playernum) io->draw_other_field(from); if (to != my_playernum) io->draw_other_field(to); } send_field(&oldfield); if (!piece_waiting) { while (piece_overlaps(-1, -1, -1)) current_y--; draw_piece(1); } io->draw_own_field(); } /*************************************************************************/ /*************************************************************************/ /* Deal with the in-game message input buffer. */ static char gmsg_buffer[512]; static int gmsg_pos; #define curpos (gmsg_buffer+gmsg_pos) /*************************************************************************/ static void gmsg_input(int c) { if (gmsg_pos < sizeof(gmsg_buffer) - 1) { memmove(curpos+1, curpos, strlen(curpos)+1); gmsg_buffer[gmsg_pos++] = c; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } } /*************************************************************************/ static void gmsg_delete(void) { if (gmsg_buffer[gmsg_pos]) { memmove(curpos, curpos+1, strlen(curpos)-1+1); io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } } /*************************************************************************/ static void gmsg_backspace(void) { if (gmsg_pos > 0) { gmsg_pos--; gmsg_delete(); } } /*************************************************************************/ static void gmsg_kill(void) { gmsg_pos = 0; *gmsg_buffer = 0; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } /*************************************************************************/ static void gmsg_move(int how) { if (how == -2) { gmsg_pos = 0; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } else if (how == -1 && gmsg_pos > 0) { gmsg_pos--; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } else if (how == 1 && gmsg_buffer[gmsg_pos]) { gmsg_pos++; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } else if (how == 2) { gmsg_pos = strlen(gmsg_buffer); io->draw_gmsg_input(gmsg_buffer, gmsg_pos); } } /*************************************************************************/ static void gmsg_enter(void) { if (*gmsg_buffer) { if (strncasecmp(gmsg_buffer, "/me ", 4) == 0) sockprintf(server_sock, "gmsg * %s %s", players[my_playernum-1], gmsg_buffer+4); else sockprintf(server_sock, "gmsg <%s> %s", players[my_playernum-1], gmsg_buffer); gmsg_pos = 0; *gmsg_buffer = 0; } io->clear_gmsg_input(); } #undef curpos /*************************************************************************/ /*************************************************************************/ /* Set up for a new game. */ void new_game(void) { int n; gettimeofday(&timeout, NULL); timeout.tv_usec += 1200000; timeout.tv_sec += timeout.tv_usec / 1000000; timeout.tv_usec %= 1000000; piece_waiting = 1; n = rand() % 100; next_piece = 0; while (n >= piecefreq[next_piece] && next_piece < 6) { n -= piecefreq[next_piece]; next_piece++; } } /*************************************************************************/ /* Return the number of milliseconds until we want to do something. */ int tetris_timeout(void) { struct timeval tv; int t; gettimeofday(&tv, NULL); t = (timeout.tv_sec - tv.tv_sec) * 1000 + (timeout.tv_usec-tv.tv_usec) / 1000; return t<0 ? 0 : t; } /*************************************************************************/ /* Do something when we hit a timeout. */ void tetris_timeout_action(void) { if (piece_waiting) new_piece(); else step_down(); } /*************************************************************************/ /* Do something with a character of input. */ static const char special_chars[] = "acnrsbgqo"; void tetris_input(int c) { PieceData *pd = &piecedata[current_piece][current_rotation]; int x = current_x - pd->hot_x; int y = current_y - pd->hot_y; int rnew, ynew; static int gmsg_active = 0; if (gmsg_active) { if (c == 8 || c == 127) /* Backspace or Delete */ gmsg_backspace(); else if (c == 4) /* Ctrl-D */ gmsg_delete(); else if (c == 21) /* Ctrl-U */ gmsg_kill(); else if (c == K_LEFT) gmsg_move(-1); else if (c == K_RIGHT) gmsg_move(1); else if (c == 1) /* Ctrl-A */ gmsg_move(-2); else if (c == 5) /* Ctrl-E */ gmsg_move(2); else if (c == '\r' || c == '\n') { gmsg_enter(); gmsg_active = 0; } else if (c == 27) { /* Escape */ io->clear_gmsg_input(); gmsg_active = 0; } else if (c >= 1 && c <= 0xFF) gmsg_input(c); return; } if (c != 't' && (!playing_game || game_paused)) return; switch (c) { case K_UP: /* Rotate clockwise */ case 'x': if (piece_waiting) break; rnew = (current_rotation+1) % 4; pd = &piecedata[current_piece][current_rotation]; x = current_x - pd->hot_x; y = current_y - pd->hot_y; if (x + pd->left < 0 || x + pd->right >= FIELD_WIDTH || y + pd->bottom >= FIELD_HEIGHT) break; draw_piece(0); if (!piece_overlaps(-1, -1, rnew)) { current_rotation = rnew; draw_piece(1); io->draw_own_field(); } else { draw_piece(1); } break; case 'z': /* Rotate counterclockwise */ if (piece_waiting) break; rnew = (current_rotation+3) % 4; pd = &piecedata[current_piece][current_rotation]; x = current_x - pd->hot_x; y = current_y - pd->hot_y; if (x + pd->left < 0 || x + pd->right >= FIELD_WIDTH || y + pd->bottom >= FIELD_HEIGHT) break; draw_piece(0); if (!piece_overlaps(-1, -1, rnew)) { current_rotation = rnew; draw_piece(1); io->draw_own_field(); } else { draw_piece(1); } break; case K_LEFT: /* Move left */ if (piece_waiting) break; if (x + pd->left > 0) { draw_piece(0); if (!piece_overlaps(current_x-1, -1, -1)) { current_x--; draw_piece(1); io->draw_own_field(); } else { draw_piece(1); } } break; case K_RIGHT: /* Move right */ if (piece_waiting) break; if (x + pd->right < FIELD_WIDTH-1) { draw_piece(0); if (!piece_overlaps(current_x+1, -1, -1)) { current_x++; draw_piece(1); io->draw_own_field(); } else { draw_piece(1); } } break; case K_DOWN: /* Down one space */ if (piece_waiting) break; step_down(); break; case ' ': /* Down until the piece hits something */ if (piece_waiting) break; draw_piece(0); ynew = current_y+1; while (y + pd->bottom < FIELD_HEIGHT && !piece_overlaps(-1,ynew,-1)) { ynew++; y++; } ynew--; if (ynew != current_y) { current_y = ynew-1; if (noslide) current_y++; /* Don't allow sliding */ step_down(); } else { draw_piece(1); } break; case 'd': if (specials[0] == -1) break; if (special_capacity > 1) memmove(specials, specials+1, special_capacity-1); specials[special_capacity-1] = -1; io->draw_specials(); break; case '1': case '2': case '3': case '4': case '5': case '6': { char buf[2]; c -= '0'; if (!players[c-1]) break; if (specials[0] == -1) break; sockprintf(server_sock, "sb %d %c %d", c, special_chars[(int) specials[0]], my_playernum); buf[0] = special_chars[(int) specials[0]]; buf[1] = 0; do_special(buf, my_playernum, c); if (special_capacity > 1) memmove(specials, specials+1, special_capacity-1); specials[special_capacity-1] = -1; io->draw_specials(); break; } case 't': gmsg_active = 1; io->draw_gmsg_input(gmsg_buffer, gmsg_pos); break; } /* switch (c) */ } /*************************************************************************/ #endif /* !SERVER_ONLY */ /*************************************************************************/ tetrinet/tetris.h0000664000175000017500000000412710671277701013725 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Tetris constants and routine declarations. */ #ifndef TETRIS_H #define TETRIS_H /*************************************************************************/ #define PIECE_BAR 0 /* Straight bar */ #define PIECE_SQUARE 1 /* Square block */ #define PIECE_L_REVERSE 2 /* Reversed L block */ #define PIECE_L 3 /* L block */ #define PIECE_Z 4 /* Z block */ #define PIECE_S 5 /* S block */ #define PIECE_T 6 /* T block */ #define SPECIAL_A 0 /* Add line */ #define SPECIAL_C 1 /* Clear line */ #define SPECIAL_N 2 /* Nuke field */ #define SPECIAL_R 3 /* Clear random blocks */ #define SPECIAL_S 4 /* Switch fields */ #define SPECIAL_B 5 /* Clear special blocks */ #define SPECIAL_G 6 /* Block gravity */ #define SPECIAL_Q 7 /* Blockquake */ #define SPECIAL_O 8 /* Block bomb */ /*************************************************************************/ #define MAX_SPECIALS 64 extern int piecefreq[7], specialfreq[9]; extern int old_mode; extern int initial_level, lines_per_level, level_inc, level_average; extern int special_lines, special_count, special_capacity; extern Field fields[6]; extern int levels[6]; extern int lines; extern signed char specials[MAX_SPECIALS]; extern int next_piece; extern int current_x, current_y; typedef struct { int hot_x, hot_y; /* Hotspot coordinates */ int top, left; /* Top-left coordinates relative to hotspot */ int bottom, right; /* Bottom-right coordinates relative to hotspot */ char shape[4][4]; /* Shape data for the piece */ } PieceData; PieceData piecedata[7][4]; extern int current_piece, current_rotation; extern void init_shapes(void); extern int get_shape(int piece, int rotation, char buf[4][4]); extern void new_game(void); extern void new_piece(void); extern void step_down(void); extern void do_special(const char *type, int from, int to); extern int tetris_timeout(void); extern void tetris_timeout_action(void); extern void tetris_input(int c); /*************************************************************************/ #endif /* TETRIS_H */ tetrinet/tty.c0000664000175000017500000007177010671442127013232 0ustar rhondarhonda/* Tetrinet for Linux, by Andrew Church * This program is public domain. * * Text terminal I/O routines. */ #include #include #include #include #include #include #include #include #include #include #include "tetrinet.h" #include "tetris.h" #include "io.h" /*************************************************************************/ #define MY_HLINE (fancy ? ACS_HLINE : '-') #define MY_VLINE (fancy ? ACS_VLINE : '|') #define MY_ULCORNER (fancy ? ACS_ULCORNER : '+') #define MY_URCORNER (fancy ? ACS_URCORNER : '+') #define MY_LLCORNER (fancy ? ACS_LLCORNER : '+') #define MY_LRCORNER (fancy ? ACS_LRCORNER : '+') #define MY_HLINE2 (fancy ? (ACS_HLINE | A_BOLD) : '=') #define MY_BOLD (fancy ? A_BOLD : 0) /*************************************************************************/ /******************************* Input stuff *****************************/ /*************************************************************************/ /* Return either an ASCII code 0-255, a K_* value, or -1 if server input is * waiting. Return -2 if we run out of time with no input. */ static int wait_for_input(int msec) { fd_set fds; struct timeval tv; int c; static int escape = 0; FD_ZERO(&fds); FD_SET(0, &fds); FD_SET(server_sock, &fds); tv.tv_sec = msec/1000; tv.tv_usec = (msec*1000) % 1000000; while (select(server_sock+1, &fds, NULL, NULL, msec<0 ? NULL : &tv) < 0) { if (errno != EINTR) perror("Warning: select() failed"); } if (FD_ISSET(0, &fds)) { c = getch(); if (!escape && c == 27) { /* Escape */ escape = 1; c = wait_for_input(1000); escape = 0; if (c < 0) return 27; else return c; } if (c == KEY_UP) return K_UP; else if (c == KEY_DOWN) return K_DOWN; else if (c == KEY_LEFT) return K_LEFT; else if (c == KEY_RIGHT) return K_RIGHT; else if (c == KEY_F(1) || c == ('1'|0x80) || (escape && c == '1')) return K_F1; else if (c == KEY_F(2) || c == ('2'|0x80) || (escape && c == '2')) return K_F2; else if (c == KEY_F(3) || c == ('3'|0x80) || (escape && c == '3')) return K_F3; else if (c == KEY_F(4) || c == ('4'|0x80) || (escape && c == '4')) return K_F4; else if (c == KEY_F(5) || c == ('5'|0x80) || (escape && c == '5')) return K_F5; else if (c == KEY_F(6) || c == ('6'|0x80) || (escape && c == '6')) return K_F6; else if (c == KEY_F(7) || c == ('7'|0x80) || (escape && c == '7')) return K_F7; else if (c == KEY_F(8) || c == ('8'|0x80) || (escape && c == '8')) return K_F8; else if (c == KEY_F(9) || c == ('9'|0x80) || (escape && c == '9')) return K_F9; else if (c == KEY_F(10) || c == ('0'|0x80) || (escape && c == '0')) return K_F10; else if (c == KEY_F(11)) return K_F11; else if (c == KEY_F(12)) return K_F12; else if (c == KEY_BACKSPACE) return 8; else if (c >= 0x0100) return K_INVALID; else if (c == 7) /* ^G */ return 27; /* Escape */ else return c; } /* if (FD_ISSET(0, &fds)) */ else if (FD_ISSET(server_sock, &fds)) return -1; else return -2; /* out of time */ } /*************************************************************************/ /****************************** Output stuff *****************************/ /*************************************************************************/ /* Size of the screen */ static int scrwidth, scrheight; /* Is color available? */ static int has_color; /*************************************************************************/ /* Text buffers: */ typedef struct { int x, y, width, height; int line; WINDOW *win; /* NULL if not currently displayed */ char **text; } TextBuffer; static TextBuffer plinebuf, gmsgbuf, attdefbuf; /*************************************************************************/ /* Window for typing in-game text, and its coordinates: */ static WINDOW *gmsg_inputwin; static int gmsg_inputpos, gmsg_inputheight; /*************************************************************************/ /*************************************************************************/ /* Clean up the screen on exit. */ static void screen_cleanup() { wmove(stdscr, scrheight-1, 0); wrefresh(stdscr); endwin(); printf("\n"); } /*************************************************************************/ /* Little signal handler that just does an exit(1) (thereby getting our * cleanup routine called), except for TSTP, which does a clean suspend. */ static void (*old_tstp)(int sig); static void sighandler(int sig) { if (sig != SIGTSTP) { endwin(); psignal(sig, "tetrinet"); exit(1); } endwin(); signal(SIGTSTP, old_tstp); raise(SIGTSTP); doupdate(); signal(SIGTSTP, sighandler); } /*************************************************************************/ /*************************************************************************/ #define MAXCOLORS 256 static int colors[MAXCOLORS][2] = { {-1,-1} }; /* Return a color attribute value. */ static long getcolor(int fg, int bg) { int i; if (colors[0][0] < 0) { start_color(); memset(colors, -1, sizeof(colors)); colors[0][0] = COLOR_WHITE; colors[0][1] = COLOR_BLACK; } if (fg == COLOR_WHITE && bg == COLOR_BLACK) return COLOR_PAIR(0); for (i = 1; i < MAXCOLORS; i++) { if (colors[i][0] == fg && colors[i][1] == bg) return COLOR_PAIR(i); } for (i = 1; i < MAXCOLORS; i++) { if (colors[i][0] < 0) { if (init_pair(i, fg, bg) == ERR) continue; colors[i][0] = fg; colors[i][1] = bg; return COLOR_PAIR(i); } } return -1; } /*************************************************************************/ /*************************************************************************/ /* Set up the screen stuff. */ static void screen_setup(void) { /* Avoid messy keyfield signals while we're setting up */ signal(SIGINT, SIG_IGN); signal(SIGTSTP, SIG_IGN); initscr(); cbreak(); noecho(); nodelay(stdscr, TRUE); keypad(stdscr, TRUE); leaveok(stdscr, TRUE); if ((has_color = has_colors())) start_color(); getmaxyx(stdscr, scrheight, scrwidth); scrwidth--; /* Don't draw in last column--this can cause scroll */ /* don't start with fewer lines */ if (scrheight < 50) { nocbreak(); endwin(); fprintf(stderr, "You need at least 50 lines to play tetrinet.\n"); exit(1); } /* Cancel all this when we exit. */ atexit(screen_cleanup); /* Catch signals so we can exit cleanly. */ signal(SIGINT, sighandler); signal(SIGTERM, sighandler); signal(SIGHUP, sighandler); signal(SIGUSR1, sighandler); signal(SIGUSR2, sighandler); signal(SIGALRM, sighandler); signal(SIGTSTP, sighandler); #ifdef SIGXCPU signal(SIGXCPU, sighandler); #endif #ifdef SIGXFSZ signal(SIGXFSZ, sighandler); #endif /* Broken pipes don't want to bother us at all. */ signal(SIGPIPE, SIG_IGN); } /*************************************************************************/ /* Redraw everything on the screen. */ static void screen_refresh(void) { if (gmsg_inputwin) touchline(stdscr, gmsg_inputpos, gmsg_inputheight); if (plinebuf.win) touchline(stdscr, plinebuf.y, plinebuf.height); if (gmsgbuf.win) touchline(stdscr, gmsgbuf.y, gmsgbuf.height); if (attdefbuf.win) touchline(stdscr, attdefbuf.y, attdefbuf.height); wnoutrefresh(stdscr); doupdate(); } /*************************************************************************/ /* Like screen_refresh(), but clear the screen first. */ static void screen_redraw(void) { clearok(stdscr, TRUE); screen_refresh(); } /*************************************************************************/ /************************* Text buffer routines **************************/ /*************************************************************************/ /* Put a line of text in a text buffer. */ static void outline(TextBuffer *buf, const unsigned char *s) { if (buf->line == buf->height) { if (buf->win) scroll(buf->win); memmove(buf->text, buf->text+1, (buf->height-1) * sizeof(char *)); buf->line--; } if (buf->win) { int i, x = 0, l = strlen(s); for (i = 0; i < l; i++) { unsigned char c = s[i] - 1; if (c < TATTR_MAX) { static const int cmap[8] = { COLOR_BLACK, COLOR_RED, COLOR_GREEN, COLOR_YELLOW, COLOR_BLUE, COLOR_MAGENTA, COLOR_CYAN, COLOR_WHITE, }; switch (c) { case TATTR_RESET: wattrset(buf->win, A_NORMAL); break; case TATTR_BOLD: wattron(buf->win, A_BOLD); break; case TATTR_ITALIC: wattron(buf->win, A_STANDOUT); break; case TATTR_UNDERLINE: wattron(buf->win, A_UNDERLINE); break; default: assert(c < TATTR_CMAX); wattron(buf->win, getcolor(c == 0 ? COLOR_WHITE : cmap[c % 8], COLOR_BLACK) | (A_BOLD * (c / 8))); break; } } else { mvwaddch(buf->win, buf->line, x++, c + 1); } } wattrset(buf->win, A_NORMAL); } if (s != (const unsigned char *) buf->text[buf->line]) /* check for restoring display */ buf->text[buf->line] = strdup(s); buf->line++; } static void draw_text(int bufnum, const char *s) { char str[1024]; /* hopefully scrwidth < 1024 */ const char *t; int indent = 0; int x = 0, y = 0; TextBuffer *buf; switch (bufnum) { case BUFFER_PLINE: buf = &plinebuf; break; case BUFFER_GMSG: buf = &gmsgbuf; break; case BUFFER_ATTDEF: buf = &attdefbuf; break; default: return; } if (!buf->text) return; if (buf->win) { getyx(stdscr, y, x); attrset(getcolor(COLOR_WHITE, COLOR_BLACK)); } while (*s && isspace(*s)) s++; while (strlen(s) > buf->width - indent) { t = s + buf->width - indent; while (t >= s && !isspace(*t)) t--; while (t >= s && isspace(*t)) t--; t++; if (t < s) t = s + buf->width - indent; if (indent > 0) sprintf(str, "%*s", indent, ""); strncpy(str+indent, s, t-s); str[t-s+indent] = 0; outline(buf, str); indent = 2; while (isspace(*t)) t++; s = t; } if (indent > 0) sprintf(str, "%*s", indent, ""); strcpy(str+indent, s); outline(buf, str); if (buf->win) { move(y, x); screen_refresh(); } } /*************************************************************************/ /* Clear the contents of a text buffer. */ static void clear_text(int bufnum) { TextBuffer *buf; int i; switch (bufnum) { case BUFFER_PLINE: buf = &plinebuf; break; case BUFFER_GMSG: buf = &gmsgbuf; break; case BUFFER_ATTDEF: buf = &attdefbuf; break; default: return; } if (buf->text) { for (i = 0; i < buf->height; i++) { if (buf->text[i]) { free(buf->text[i]); buf->text[i] = NULL; } } buf->line = 0; } if (buf->win) { werase(buf->win); screen_refresh(); } } /*************************************************************************/ /* Restore the contents of the given text buffer. */ static void restore_text(TextBuffer *buf) { buf->line = 0; while (buf->line < buf->height && buf->text[buf->line]) outline(buf, buf->text[buf->line]); } /*************************************************************************/ /* Open a window for the given text buffer. */ static void open_textwin(TextBuffer *buf) { if (buf->height <= 0 || buf->width <= 0) { char str[256]; move(scrheight-1, 0); snprintf(str, sizeof(str), "ERROR: bad textwin size (%d,%d)", buf->width, buf->height); addstr(str); exit(1); } if (!buf->win) { buf->win = subwin(stdscr, buf->height, buf->width, buf->y, buf->x); scrollok(buf->win, TRUE); } if (!buf->text) buf->text = calloc(buf->height, sizeof(char *)); else restore_text(buf); } /*************************************************************************/ /* Close the window for the given text buffer, if it's open. */ static void close_textwin(TextBuffer *buf) { if (buf->win) { delwin(buf->win); buf->win = NULL; } } /*************************************************************************/ /************************ Field drawing routines *************************/ /*************************************************************************/ /* Are we on a wide screen (>=92 columns)? */ static int wide_screen = 0; /* Field display X/Y coordinates. */ static const int own_coord[2] = {1,0}; static int other_coord[5][2] = /* Recomputed based on screen width */ { {30,0}, {47,0}, {64,0}, {47,24}, {64,24} }; /* Position of the status window. */ static const int status_coord[2] = {29,25}; static const int next_coord[2] = {41,24}; static const int alt_status_coord[2] = {29,2}; static const int alt_next_coord[2] = {30,8}; /* Position of the attacks/defenses window. */ static const int attdef_coord[2] = {28,28}; static const int alt_attdef_coord[2] = {28,24}; /* Position of the text window. X coordinate is ignored. */ static const int field_text_coord[2] = {0,47}; /* Information for drawing blocks. Color attributes are added to blocks in * the setup_fields() routine. */ static int tile_chars[15] = { ' ','#','#','#','#','#','a','c','n','r','s','b','g','q','o' }; /* Are we redrawing the entire display? */ static int field_redraw = 0; /*************************************************************************/ /*************************************************************************/ /* Set up the field display. */ static void draw_own_field(void); static void draw_other_field(int player); static void draw_status(void); static void draw_specials(void); static void draw_gmsg_input(const char *s, int pos); static void setup_fields(void) { int i, j, x, y, base, delta, attdefbot; char buf[32]; if (!(tile_chars[0] & A_ATTRIBUTES)) { for (i = 1; i < 15; i++) tile_chars[i] |= A_BOLD; tile_chars[1] |= getcolor(COLOR_BLUE, COLOR_BLACK); tile_chars[2] |= getcolor(COLOR_YELLOW, COLOR_BLACK); tile_chars[3] |= getcolor(COLOR_GREEN, COLOR_BLACK); tile_chars[4] |= getcolor(COLOR_MAGENTA, COLOR_BLACK); tile_chars[5] |= getcolor(COLOR_RED, COLOR_BLACK); } field_redraw = 1; leaveok(stdscr, TRUE); close_textwin(&plinebuf); clear(); attrset(getcolor(COLOR_WHITE,COLOR_BLACK)); if (scrwidth >= 92) { wide_screen = 1; base = 41; } else { base = 28; } delta = (scrwidth - base) / 3; base += 2 + (delta - (FIELD_WIDTH+5)) / 2; other_coord[0][0] = base; other_coord[1][0] = base + delta; other_coord[2][0] = base + delta*2; other_coord[3][0] = base + delta; other_coord[4][0] = base + delta*2; attdefbot = field_text_coord[1] - 1; if (scrheight - field_text_coord[1] > 3) { move(field_text_coord[1], 0); hline(MY_HLINE2, scrwidth); attdefbot--; if (scrheight - field_text_coord[1] > 5) { move(scrheight-2, 0); hline(MY_HLINE2, scrwidth); attrset(MY_BOLD); move(scrheight-1, 0); addstr("F1=Show Fields F2=Partyline F3=Winlist"); move(scrheight-1, scrwidth-8); addstr("F10=Quit"); attrset(A_NORMAL); gmsgbuf.y = field_text_coord[1]+1; gmsgbuf.height = scrheight - field_text_coord[1] - 3; } else { gmsgbuf.y = field_text_coord[1]+1; gmsgbuf.height = scrheight - field_text_coord[1] - 1; } } else { gmsgbuf.y = field_text_coord[1]; gmsgbuf.height = scrheight - field_text_coord[1]; } gmsgbuf.x = field_text_coord[0]; gmsgbuf.width = scrwidth; open_textwin(&gmsgbuf); x = own_coord[0]; y = own_coord[1]; sprintf(buf, "%d", my_playernum); mvaddstr(y, x-1, buf); for (i = 2; i < FIELD_HEIGHT*2 && players[my_playernum-1][i-2]; i++) mvaddch(y+i, x-1, players[my_playernum-1][i-2]); if (teams[my_playernum-1] != '\0') { mvaddstr(y, x+FIELD_WIDTH*2+2, "T"); for (i = 2; i < FIELD_HEIGHT*2 && teams[my_playernum-1][i-2]; i++) mvaddch(y+i, x+FIELD_WIDTH*2+2, teams[my_playernum-1][i-2]); } move(y, x); vline(MY_VLINE, FIELD_HEIGHT*2); move(y, x+FIELD_WIDTH*2+1); vline(MY_VLINE, FIELD_HEIGHT*2); move(y+FIELD_HEIGHT*2, x); addch(MY_LLCORNER); hline(MY_HLINE, FIELD_WIDTH*2); move(y+FIELD_HEIGHT*2, x+FIELD_WIDTH*2+1); addch(MY_LRCORNER); mvaddstr(y+FIELD_HEIGHT*2+2, x, "Specials:"); draw_own_field(); draw_specials(); for (j = 0; j < 5; j++) { x = other_coord[j][0]; y = other_coord[j][1]; move(y, x); vline(MY_VLINE, FIELD_HEIGHT); move(y, x+FIELD_WIDTH+1); vline(MY_VLINE, FIELD_HEIGHT); move(y+FIELD_HEIGHT, x); addch(MY_LLCORNER); hline(MY_HLINE, FIELD_WIDTH); move(y+FIELD_HEIGHT, x+FIELD_WIDTH+1); addch(MY_LRCORNER); if (j+1 >= my_playernum) { sprintf(buf, "%d", j+2); mvaddstr(y, x-1, buf); if (players[j+1]) { for (i = 0; i < FIELD_HEIGHT-2 && players[j+1][i]; i++) mvaddch(y+i+2, x-1, players[j+1][i]); if (teams[j+1] != '\0') { mvaddstr(y, x+FIELD_WIDTH+2, "T"); for (i = 0; i < FIELD_HEIGHT-2 && teams[j+1][i]; i++) mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j+1][i]); } } draw_other_field(j+2); } else { sprintf(buf, "%d", j+1); mvaddstr(y, x-1, buf); if (players[j]) { for (i = 0; i < FIELD_HEIGHT-2 && players[j][i]; i++) mvaddch(y+i+2, x-1, players[j][i]); if (teams[j] != '\0') { mvaddstr(y, x+FIELD_WIDTH+2, "T"); for (i = 0; i < FIELD_HEIGHT-2 && teams[j][i]; i++) mvaddch(y+i+2, x+FIELD_WIDTH+2, teams[j][i]); } } draw_other_field(j+1); } } if (wide_screen) { x = alt_status_coord[0]; y = alt_status_coord[1]; mvaddstr(y, x, "Lines:"); mvaddstr(y+1, x, "Level:"); x = alt_next_coord[0]; y = alt_next_coord[1]; mvaddstr(y-2, x-1, "Next piece:"); move(y-1, x-1); addch(MY_ULCORNER); hline(MY_HLINE, 8); mvaddch(y-1, x+8, MY_URCORNER); move(y, x-1); vline(MY_VLINE, 8); move(y, x+8); vline(MY_VLINE, 8); move(y+8, x-1); addch(MY_LLCORNER); hline(MY_HLINE, 8); mvaddch(y+8, x+8, MY_LRCORNER); } else { x = status_coord[0]; y = status_coord[1]; mvaddstr(y-1, x, "Next piece:"); mvaddstr(y, x, "Lines:"); mvaddstr(y+1, x, "Level:"); } if (playing_game) draw_status(); attdefbuf.x = wide_screen ? alt_attdef_coord[0] : attdef_coord[0]; attdefbuf.y = wide_screen ? alt_attdef_coord[1] : attdef_coord[1]; attdefbuf.width = (other_coord[3][0]-1) - attdefbuf.x; attdefbuf.height = (attdefbot+1) - attdefbuf.y; open_textwin(&attdefbuf); if (gmsg_inputwin) { delwin(gmsg_inputwin); gmsg_inputwin = NULL; draw_gmsg_input(NULL, -1); } (void)curs_set(0); screen_refresh(); field_redraw = 0; } /*************************************************************************/ /* Display the player's own field. */ static void draw_own_field(void) { int x, y, x0, y0; Field *f = &fields[my_playernum-1]; int shadow[4] = { -1, -1, -1, -1 }; if (dispmode != MODE_FIELDS) return; /* XXX: Code duplication with tetris.c:draw_piece(). --pasky */ if (playing_game && cast_shadow) { int y = current_y - piecedata[current_piece][current_rotation].hot_y; char *shape = (char *) piecedata[current_piece][current_rotation].shape; int i, j; for (j = 0; j < 4; j++) { if (y+j < 0) { shape += 4; continue; } for (i = 0; i < 4; i++) { if (*shape++) shadow[i] = y + j; } } } x0 = own_coord[0]+1; y0 = own_coord[1]; for (y = 0; y < 22; y++) { for (x = 0; x < 12; x++) { int c = tile_chars[(int) (*f)[y][x]]; if (playing_game && cast_shadow) { PieceData *piece = &piecedata[current_piece][current_rotation]; int piece_x = current_x - piece->hot_x; if (x >= piece_x && x <= piece_x + 3 && shadow[(x - piece_x)] >= 0 && shadow[(x - piece_x)] < y && ((c & 0x7f) == ' ')) { c = (c & (~0x7f)) | '.' | getcolor(COLOR_BLACK, COLOR_BLACK) | A_BOLD; } } mvaddch(y0+y*2, x0+x*2, c); addch(c); mvaddch(y0+y*2+1, x0+x*2, c); addch(c); } } if (gmsg_inputwin) { delwin(gmsg_inputwin); gmsg_inputwin = NULL; draw_gmsg_input(NULL, -1); } if (!field_redraw) { (void)curs_set(0); screen_refresh(); } } /*************************************************************************/ /* Display another player's field. */ static void draw_other_field(int player) { int x, y, x0, y0; Field *f; if (dispmode != MODE_FIELDS) return; f = &fields[player-1]; if (player > my_playernum) player--; player--; x0 = other_coord[player][0]+1; y0 = other_coord[player][1]; for (y = 0; y < 22; y++) { move(y0+y, x0); for (x = 0; x < 12; x++) { addch(tile_chars[(int) (*f)[y][x]]); } } if (gmsg_inputwin) { delwin(gmsg_inputwin); gmsg_inputwin = NULL; draw_gmsg_input(NULL, -1); } if (!field_redraw) { (void)curs_set(0); screen_refresh(); } } /*************************************************************************/ /* Display the current game status (level, lines, next piece). */ static void draw_status(void) { int x, y, i, j; char buf[32], shape[4][4]; x = wide_screen ? alt_status_coord[0] : status_coord[0]; y = wide_screen ? alt_status_coord[1] : status_coord[1]; sprintf(buf, "%d", lines>99999 ? 99999 : lines); mvaddstr(y, x+7, buf); sprintf(buf, "%d", levels[my_playernum]); mvaddstr(y+1, x+7, buf); x = wide_screen ? alt_next_coord[0] : next_coord[0]; y = wide_screen ? alt_next_coord[1] : next_coord[1]; if (get_shape(next_piece, 0, shape) == 0) { for (j = 0; j < 4; j++) { if (!wide_screen) move(y+j, x); for (i = 0; i < 4; i++) { if (wide_screen) { move(y+j*2, x+i*2); addch(tile_chars[(int) shape[j][i]]); addch(tile_chars[(int) shape[j][i]]); move(y+j*2+1, x+i*2); addch(tile_chars[(int) shape[j][i]]); addch(tile_chars[(int) shape[j][i]]); } else addch(tile_chars[(int) shape[j][i]]); } } } } /*************************************************************************/ /* Display the special inventory and description of the current special. */ static const char *descs[] = { " ", "Add Line ", "Clear Line ", "Nuke Field ", "Clear Random Blocks ", "Switch Fields ", "Clear Special Blocks", "Block Gravity ", "Blockquake ", "Block Bomb " }; static void draw_specials(void) { int x, y, i; if (dispmode != MODE_FIELDS) return; x = own_coord[0]; y = own_coord[1]+45; mvaddstr(y, x, descs[specials[0]+1]); move(y+1, x+10); i = 0; while (i < special_capacity && specials[i] >= 0 && x < attdef_coord[0]-1) { addch(tile_chars[specials[i]+6]); i++; x++; } while (x < attdef_coord[0]-1) { addch(tile_chars[0]); x++; } if (!field_redraw) { (void)curs_set(0); screen_refresh(); } } /*************************************************************************/ /* Display an attack/defense message. */ static const char *msgs[][2] = { { "cs1", "1 Line Added to All" }, { "cs2", "2 Lines Added to All" }, { "cs4", "4 Lines Added to All" }, { "a", "Add Line" }, { "c", "Clear Line" }, { "n", "Nuke Field" }, { "r", "Clear Random Blocks" }, { "s", "Switch Fields" }, { "b", "Clear Special Blocks" }, { "g", "Block Gravity" }, { "q", "Blockquake" }, { "o", "Block Bomb" }, { NULL } }; static void draw_attdef(const char *type, int from, int to) { int i, width; char buf[512]; width = other_coord[4][0] - attdef_coord[0] - 1; for (i = 0; msgs[i][0]; i++) { if (strcmp(type, msgs[i][0]) == 0) break; } if (!msgs[i][0]) return; strcpy(buf, msgs[i][1]); if (to != 0) sprintf(buf+strlen(buf), " on %s", players[to-1]); if (from == 0) sprintf(buf+strlen(buf), " by Server"); else sprintf(buf+strlen(buf), " by %s", players[from-1]); draw_text(BUFFER_ATTDEF, buf); } /*************************************************************************/ /* Display the in-game text window. */ static void draw_gmsg_input(const char *s, int pos) { static int start = 0; /* Start of displayed part of input line */ static const char *last_s; static int last_pos; if (s) last_s = s; else s = last_s; if (pos >= 0) last_pos = pos; else pos = last_pos; attrset(getcolor(COLOR_WHITE,COLOR_BLACK)); if (!gmsg_inputwin) { gmsg_inputpos = scrheight/2 - 1; gmsg_inputheight = 3; gmsg_inputwin = subwin(stdscr, gmsg_inputheight, scrwidth, gmsg_inputpos, 0); werase(gmsg_inputwin); leaveok(gmsg_inputwin, FALSE); leaveok(stdscr, FALSE); mvwaddstr(gmsg_inputwin, 1, 0, "Text>"); } if (strlen(s) < scrwidth-7) { start = 0; mvwaddstr(gmsg_inputwin, 1, 6, s); wmove(gmsg_inputwin, 1, 6+strlen(s)); move(gmsg_inputpos+1, 6+strlen(s)); wclrtoeol(gmsg_inputwin); wmove(gmsg_inputwin, 1, 6+pos); move(gmsg_inputpos+1, 6+pos); } else { if (pos < start+8) { start = pos-8; if (start < 0) start = 0; } else if (pos > start + scrwidth-15) { start = pos - (scrwidth-15); if (start > strlen(s) - (scrwidth-7)) start = strlen(s) - (scrwidth-7); } mvwaddnstr(gmsg_inputwin, 1, 6, s+start, scrwidth-6); wmove(gmsg_inputwin, 1, 6 + (pos-start)); move(gmsg_inputpos+1, 6 + (pos-start)); } (void)curs_set(1); screen_refresh(); } /*************************************************************************/ /* Clear the in-game text window. */ static void clear_gmsg_input(void) { if (gmsg_inputwin) { delwin(gmsg_inputwin); gmsg_inputwin = NULL; leaveok(stdscr, TRUE); touchline(stdscr, gmsg_inputpos, gmsg_inputheight); setup_fields(); (void)curs_set(0); screen_refresh(); } } /*************************************************************************/ /*************************** Partyline display ***************************/ /*************************************************************************/ static void setup_partyline(void) { close_textwin(&gmsgbuf); close_textwin(&attdefbuf); clear(); attrset(getcolor(COLOR_WHITE,COLOR_BLACK)); plinebuf.x = plinebuf.y = 0; plinebuf.width = scrwidth; plinebuf.height = scrheight-4; open_textwin(&plinebuf); move(scrheight-4, 0); hline(MY_HLINE, scrwidth); move(scrheight-3, 0); addstr("> "); move(scrheight-2, 0); hline(MY_HLINE2, scrwidth); attrset(MY_BOLD); move(scrheight-1, 0); addstr("F1=Show Fields F2=Partyline F3=Winlist"); move(scrheight-1, scrwidth-8); addstr("F10=Quit"); attrset(A_NORMAL); move(scrheight-3, 2); leaveok(stdscr, FALSE); (void)curs_set(1); screen_refresh(); } /*************************************************************************/ static void draw_partyline_input(const char *s, int pos) { static int start = 0; /* Start of displayed part of input line */ attrset(getcolor(COLOR_WHITE,COLOR_BLACK)); if (strlen(s) < scrwidth-3) { start = 0; mvaddstr(scrheight-3, 2, s); move(scrheight-3, 2+strlen(s)); clrtoeol(); move(scrheight-3, 2+pos); } else { if (pos < start+8) { start = pos-8; if (start < 0) start = 0; } else if (pos > start + scrwidth-11) { start = pos - (scrwidth-11); if (start > strlen(s) - (scrwidth-3)) start = strlen(s) - (scrwidth-3); } mvaddnstr(scrheight-3, 2, s+start, scrwidth-2); move(scrheight-3, 2 + (pos-start)); } screen_refresh(); } /*************************************************************************/ /**************************** Winlist display ****************************/ /*************************************************************************/ static void setup_winlist(void) { int i, x; char buf[32]; leaveok(stdscr, TRUE); close_textwin(&plinebuf); clear(); attrset(getcolor(COLOR_WHITE,COLOR_BLACK)); for (i = 0; i < MAXWINLIST && *winlist[i].name; i++) { x = scrwidth/2 - strlen(winlist[i].name); if (x < 0) x = 0; if (winlist[i].team) { if (x < 4) x = 4; mvaddstr(i*2, x-4, ""); } mvaddstr(i*2, x, winlist[i].name); snprintf(buf, sizeof(buf), "%4d", winlist[i].points); if (winlist[i].games) { int avg100 = winlist[i].points*100 / winlist[i].games; snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), " %d.%02d",avg100/100, avg100%100); } x += strlen(winlist[i].name) + 2; if (x > scrwidth - strlen(buf)) x = scrwidth - strlen(buf); mvaddstr(i*2, x, buf); } move(scrheight-2, 0); hline(MY_HLINE2, scrwidth); attrset(MY_BOLD); move(scrheight-1, 0); addstr("F1=Show Fields F2=Partyline F3=Winlist"); move(scrheight-1, scrwidth-8); addstr("F10=Quit"); attrset(A_NORMAL); (void)curs_set(0); screen_refresh(); } /*************************************************************************/ /************************** Interface declaration ************************/ /*************************************************************************/ Interface tty_interface = { wait_for_input, screen_setup, screen_refresh, screen_redraw, draw_text, clear_text, setup_fields, draw_own_field, draw_other_field, draw_status, draw_specials, draw_attdef, draw_gmsg_input, clear_gmsg_input, setup_partyline, draw_partyline_input, setup_winlist }; /*************************************************************************/ tetrinet/version.h0000664000175000017500000000033507737051757014107 0ustar rhondarhonda/* Tetrinet for Linux, by Petr Baudis * This program is public domain. * * Tetrinet version information file. */ #ifndef TETRINET__VERSION_H #define TETRINET__VERSION_H #define VERSION "0.11" #endif