From 34a631ffe12fddf7cc820c3b9d7b7c0dd49a7e4d Mon Sep 17 00:00:00 2001 From: John Sennesael Date: Tue, 15 Feb 2022 18:17:36 -0600 Subject: [PATCH] initial commit --- .gitignore | 4 + AUTHORS | 28 ++ CMakeLists.txt | 30 ++ ChangeLog | 135 ++++++ Changes | 43 ++ Makefile | 41 ++ README | 293 +++++++++++++ README.md | 9 + TODO | 8 + io.h | 66 +++ server.c | 966 ++++++++++++++++++++++++++++++++++++++++++ server.h | 8 + sockets.c | 205 +++++++++ sockets.h | 18 + tetrinet.c | 752 +++++++++++++++++++++++++++++++++ tetrinet.h | 98 +++++ tetrinet.txt | 628 +++++++++++++++++++++++++++ tetris.c | 1045 +++++++++++++++++++++++++++++++++++++++++++++ tetris.h | 75 ++++ tty.c | 1100 ++++++++++++++++++++++++++++++++++++++++++++++++ version.h | 12 + xwin.c | 1038 +++++++++++++++++++++++++++++++++++++++++++++ 22 files changed, 6602 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 ChangeLog create mode 100644 Changes create mode 100644 Makefile create mode 100644 README create mode 100644 README.md create mode 100644 TODO create mode 100644 io.h create mode 100644 server.c create mode 100644 server.h create mode 100644 sockets.c create mode 100644 sockets.h create mode 100644 tetrinet.c create mode 100644 tetrinet.h create mode 100644 tetrinet.txt create mode 100644 tetris.c create mode 100644 tetris.h create mode 100644 tty.c create mode 100644 version.h create mode 100644 xwin.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..38861cd --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +[syntax=glob] +*.o +*.swp +*.*~ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..ad41e1a --- /dev/null +++ b/AUTHORS @@ -0,0 +1,28 @@ +People who contributed to tetrinet(-pb), in alphabetical order (if you are +missing here or some of your contributions is, tell pasky): + + +Andrew Church + Vanilla tetrinet maintainer + The original implementation ;-) + +Chris Clark + Counterclockwise rotation + +Christoph Weiss + Teamplay fix + +Cougar + IPv6 support + +Gadall + FreeBSD compilation fixes + +Gerfried Fuchs + Random hacking + +Petr Baudis + -pb branch maintainer + Multichannel support + Tetrifast support + Random hacking diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f9a0af5 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,30 @@ +cmake_minimum_required(VERSION 3.5) + +set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb -fno-omit-frame-pointer -fsanitize=address") + +if(NOT DEFINED CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE "DEBUG" CACHE STRING "Type of build: Debug|Release") +endif() + +project(tetrinet C) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") + +find_package(Curses REQUIRED) + +add_executable(tetrinet + "sockets.c" "tetrinet.c" "tetris.c" "tty.c" "xwin.c" +) + +add_compile_definitions(HAVE_IPV6) + +target_link_libraries(tetrinet + ${CURSES_LIBRARIES} +) + +target_include_directories(tetrinet + PRIVATE + include + PUBLIC + ${CURSES_INCLUDE_DIRS} +) diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..a4bbab7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,135 @@ +Thu Oct 2 16:35:59 2003 Petr Baudis : + + * Changes, README, version.h: + + tetrinet-0.11 + +Thu Sep 11 20:26:53 2003 Gerfried Fuchs : + + * tty.c: + + Add support to display team name in playfield. Required to move + player name to the left. + +Tue Sep 9 19:15:04 2003 Gerfried Fuchs : + + * sockets.c: + + #include to silence memcpy and memset warning. + +Sun Sep 7 16:30:02 2003 Petr Baudis : + + * README, version.h: + + 0.10-pb4 + +Sun Sep 7 16:29:29 2003 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. + +Sun Sep 7 16:12:51 2003 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 :-(. + +Fri Sep 5 14:32:37 2003 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. + +Fri Sep 5 11:39:39 2003 Petr Baudis : + + * README, version.h: + + 0.10-pb3 + +Fri Sep 5 11:37:01 2003 Petr Baudis : + + * tetrinet.c, version.h: + + Include the version information in the usage output. + +Fri Sep 5 11:33:03 2003 Petr Baudis : + + * .cvsignore: + + Ignore compiled binaries. + +Fri Sep 5 11:25:26 2003 Petr Baudis : + + * server.c: + + _Untested_ server-side tetrifast support. + +Fri Sep 5 11:15:54 2003 Petr Baudis : + + * README, tetrinet.c: + + Documented the -fast option. + +Fri Sep 5 11:09:09 2003 Petr Baudis : + + * tetrinet.c: + + Print verbose usage help, also when an unknown option is passed. + +Fri Sep 5 10:56:32 2003 Petr Baudis : + + * Changes: + + -pb branch changes are in ChangeLog. + +Fri Sep 5 10:54:00 2003 Petr Baudis : + + * AUTHORS: + + Put together some AUTHORS file. + +Fri Sep 5 10:46:45 2003 Petr Baudis : + + * README: + + 0.10-pb2 (pb1 was done outside of CVS yet) + +Fri Sep 5 10:39:20 2003 Petr Baudis : + + * tetrinet.c, tetrinet.h, tetris.c: + + Tetrifast support. + +Fri Sep 5 10:38:34 2003 Petr Baudis : + + * README, tetrinet.c: + + Introduced multichannel support (it was rather just a trivial fix). + +Fri Sep 5 10:37:13 2003 Petr Baudis : + + * tetrinet.c: + + Still send unknown commands to the server, patch by Gerfried Fuchs + and me. + +Fri Sep 5 10:30:53 2003 Petr Baudis : + + * README: + + Administrative commit - 0.10-pb0. + +Fri Sep 5 10:28:55 2003 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 + diff --git a/Changes b/Changes new file mode 100644 index 0000000..24c1275 --- /dev/null +++ b/Changes @@ -0,0 +1,43 @@ +Please 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c30bae9 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +CC = cc + +CFLAGS = -O2 -I/usr/include/ncurses -DHAVE_IPV6 -g -Wall +OBJS = sockets.o tetrinet.o tetris.o tty.o xwin.o + +### If you want to have -server tetrinet client option, comment the two lines +### above and uncomment this instead. + +# CFLAGS = -O2 -I/usr/include/ncurses -DHAVE_IPV6 -g -DBUILTIN_SERVER -Wall +# OBJS = server.o sockets.o tetrinet.o tetris.o tty.o xwin.o + +######## + +all: tetrinet tetrinet-server + +install: all + cp -p tetrinet tetrinet-server /usr/games + +clean: + rm -f tetrinet tetrinet-server *.o + +spotless: clean + +######## + +tetrinet: $(OBJS) + $(CC) -o $@ $(OBJS) $(LDFLAGS) -lncurses -ltinfo + +tetrinet-server: server.c sockets.c tetrinet.c tetris.c + $(CC) $(CFLAGS) -o $@ -DSERVER_ONLY server.c sockets.c tetrinet.c tetris.c + +.c.o: + $(CC) $(CFLAGS) -c $< + +server.o: server.c +sockets.o: sockets.c +tetrinet.o: tetrinet.c +tetris.o: tetris.c +tty.o: tty.c +xwin.o: xwin.c + diff --git a/README b/README new file mode 100644 index 0000000..5cae77d --- /dev/null +++ b/README @@ -0,0 +1,293 @@ + 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. + +NOTE: Xwindows graphics display really isn't supported, despite the +presence of the "xwin.c" file! Don't be fooled! (The file is there to +remind me to implement Xwindows support someday. Note how well it's +working.) + + +Compilation +----------- +Type "make". This will generate two programs: "tetrinet" and +"tetrinet-server". The former is the main program; the latter is a +standalone server. + + +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..391d68e --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +This is an old console tetrinet client for Linux written by Andrew Church. +I slightly modified it to allow it to compile with recent gcc versions. + +Just type `make` to compile - depends on ncurses. + +I only managed to get this to work in a playable way using xterm. +urxvt renders a blank screen - gnome-terminal renders a screen, +but causes the program to exit when you hit any of the function keys. + diff --git a/TODO b/TODO new file mode 100644 index 0000000..8fafce1 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +Teams 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 diff --git a/io.h b/io.h new file mode 100644 index 0000000..a5f9b88 --- /dev/null +++ b/io.h @@ -0,0 +1,66 @@ +/* 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. */ + 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; + +#endif /* IO_H */ diff --git a/server.c b/server.c new file mode 100644 index 0000000..7b3cdc9 --- /dev/null +++ b/server.c @@ -0,0 +1,966 @@ +/* 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 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) { + /* Messy decoding stuff */ + char iphashbuf[16], newbuf[1024]; + unsigned char *ip; + int j, c, d; + + 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); + c = xtoi(buf); + for (j = 2; buf[j] && buf[j+1]; j += 2) { + int temp; + temp = d = xtoi(buf+j); + d ^= iphashbuf[((j/2)-1) % strlen(iphashbuf)]; + d += 255 - c; + d %= 255; + newbuf[j/2-1] = d; + c = temp; + } + newbuf[j/2-1] = 0; + if (strncmp(newbuf, "tetrisstart ", 12) != 0) { + close(fd); + player_socks[i] = -1; + continue; + } + /* Buffers should be the same size, but let's be paranoid */ + strncpy(buf, newbuf, sizeof(buf)); + buf[sizeof(buf)-1] = 0; + 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; +} + +/*************************************************************************/ diff --git a/server.h b/server.h new file mode 100644 index 0000000..b0928f8 --- /dev/null +++ b/server.h @@ -0,0 +1,8 @@ +#ifndef SERVER_H +#define SERVER_H + +extern void read_config(void); +extern void write_config(void); +extern int server_main(void); + +#endif /* SERVER_H */ diff --git a/sockets.c b/sockets.c new file mode 100644 index 0000000..6a770ed --- /dev/null +++ b/sockets.c @@ -0,0 +1,205 @@ +/* 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); +} + +/*************************************************************************/ diff --git a/sockets.h b/sockets.h new file mode 100644 index 0000000..9f4d25f --- /dev/null +++ b/sockets.h @@ -0,0 +1,18 @@ +/* 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 */ diff --git a/tetrinet.c b/tetrinet.c new file mode 100644 index 0000000..5a9a0fc --- /dev/null +++ b/tetrinet.c @@ -0,0 +1,752 @@ +/* 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 + +/*************************************************************************/ + +/* 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); + io->draw_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]); + io->draw_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]); + io->draw_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); + io->draw_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); + io->draw_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; + io->draw_text(BUFFER_PLINE, "*** The Game Has Started"); + + } 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) { + io->draw_text(BUFFER_PLINE, "*** The Game Has Been Paused"); + io->draw_text(BUFFER_GMSG, "*** The Game Has Been Paused"); + } else { + io->draw_text(BUFFER_PLINE, "*** The Game Has Been Unpaused"); + io->draw_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); + io->draw_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); + } + } + + } 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; + io->draw_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); + io->draw_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); + io->draw_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]); + io->draw_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); + io->draw_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 { + io->draw_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 */ + +/*************************************************************************/ diff --git a/tetrinet.h b/tetrinet.h new file mode 100644 index 0000000..b1e9c34 --- /dev/null +++ b/tetrinet.h @@ -0,0 +1,98 @@ +/* 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 diff --git a/tetrinet.txt b/tetrinet.txt new file mode 100644 index 0000000..21d5ecd --- /dev/null +++ b/tetrinet.txt @@ -0,0 +1,628 @@ +TetriNET 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 diff --git a/tetris.c b/tetris.c new file mode 100644 index 0000000..815d12a --- /dev/null +++ b/tetris.c @@ -0,0 +1,1045 @@ +/* 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) */ +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 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 */ + +/*************************************************************************/ diff --git a/tetris.h b/tetris.h new file mode 100644 index 0000000..61c286c --- /dev/null +++ b/tetris.h @@ -0,0 +1,75 @@ +/* Tetrinet for Linux, by Andrew Church + * This program is public domain. + * + * Tetris constants and routine declarations. + */ + +#pragma once + +#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 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; + +extern 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 */ diff --git a/tty.c b/tty.c new file mode 100644 index 0000000..830f0fc --- /dev/null +++ b/tty.c @@ -0,0 +1,1100 @@ +/* Tetrinet for Linux, by Andrew Church + * This program is public domain. + * + * Text terminal I/O routines. + */ + +#define _GNU_SOURCE /* strsignal() - FIXME!!! --pasky */ + +#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. + */ + +extern PieceData piecedata[7][4]; + +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(); + if (sig != SIGINT) + fprintf(stderr, "%s\n", strsignal(sig)); + 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(SIGQUIT, 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 */ + + /* Cancel all this when we exit. */ + atexit(screen_cleanup); + + /* Catch signals so we can exit cleanly. */ + signal(SIGINT, sighandler); + signal(SIGQUIT, sighandler); + signal(SIGTERM, sighandler); + signal(SIGHUP, sighandler); + signal(SIGSEGV, sighandler); + signal(SIGABRT, sighandler); + signal(SIGIOT, sighandler); + signal(SIGTRAP, sighandler); + signal(SIGBUS, sighandler); + signal(SIGFPE, sighandler); + signal(SIGUSR1, sighandler); + signal(SIGUSR2, sighandler); + signal(SIGALRM, sighandler); +#ifdef SIGSTKFLT + signal(SIGSTKFLT, sighandler); +#endif + signal(SIGTSTP, sighandler); + signal(SIGXCPU, sighandler); + signal(SIGXFSZ, sighandler); + signal(SIGVTALRM, sighandler); + + /* 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 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) + mvwaddstr(buf->win, buf->line, 0, s); + if (s != 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); + } + + 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) + 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) + 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) + 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)); + } + 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(); + 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); + 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); + + 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 +}; + +/*************************************************************************/ diff --git a/version.h b/version.h new file mode 100644 index 0000000..0767031 --- /dev/null +++ b/version.h @@ -0,0 +1,12 @@ +/* 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 diff --git a/xwin.c b/xwin.c new file mode 100644 index 0000000..0cd4e84 --- /dev/null +++ b/xwin.c @@ -0,0 +1,1038 @@ +/* Tetrinet for Linux, by Andrew Church + * This program is public domain. + * + * Text terminal I/O routines. + */ + +#define _GNU_SOURCE /* strsignal() - FIXME!!! --pasky */ + +#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; + + 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 (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)) + return K_F1; + else if (c == KEY_F(2)) + return K_F2; + else if (c == KEY_F(3)) + return K_F3; + else if (c == KEY_F(4)) + return K_F4; + else if (c == KEY_F(5)) + return K_F5; + else if (c == KEY_F(6)) + return K_F6; + else if (c == KEY_F(7)) + return K_F7; + else if (c == KEY_F(8)) + return K_F8; + else if (c == KEY_F(9)) + return K_F9; + else if (c == KEY_F(10)) + 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(); + if (sig != SIGINT) + fprintf(stderr, "%s\n", strsignal(sig)); + 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(SIGQUIT, 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 */ + + /* Cancel all this when we exit. */ + atexit(screen_cleanup); + + /* Catch signals so we can exit cleanly. */ + signal(SIGINT, sighandler); + signal(SIGQUIT, sighandler); + signal(SIGTERM, sighandler); + signal(SIGHUP, sighandler); +/* signal(SIGSEGV, sighandler); */ + signal(SIGABRT, sighandler); + signal(SIGIOT, sighandler); + signal(SIGTRAP, sighandler); + signal(SIGBUS, sighandler); + signal(SIGFPE, sighandler); + signal(SIGUSR1, sighandler); + signal(SIGUSR2, sighandler); + signal(SIGALRM, sighandler); +#ifdef linux + signal(SIGSTKFLT, sighandler); +#endif + signal(SIGTSTP, sighandler); + signal(SIGXCPU, sighandler); + signal(SIGXFSZ, sighandler); + signal(SIGVTALRM, sighandler); + + /* 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 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) + mvwaddstr(buf->win, buf->line, 0, s); + if (s != 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] = {0,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] = {28,25}; +static const int next_coord[2] = {40,24}; +static const int alt_status_coord[2] = {28,2}; +static const int alt_next_coord[2] = {29,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+FIELD_WIDTH*2+2, buf); + for (i = 2; i < FIELD_HEIGHT*2 && players[my_playernum-1][i-2]; i++) + mvaddch(y+i, x+FIELD_WIDTH*2+2, players[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+FIELD_WIDTH+2, buf); + if (players[j+1]) { + for (i = 0; i < FIELD_HEIGHT-2 && players[j+1][i]; i++) + mvaddch(y+i+2, x+FIELD_WIDTH+2, players[j+1][i]); + } + draw_other_field(j+2); + } else { + sprintf(buf, "%d", j+1); + mvaddstr(y, x+FIELD_WIDTH+2, buf); + if (players[j]) { + for (i = 0; i < FIELD_HEIGHT-2 && players[j][i]; i++) + mvaddch(y+i+2, x+FIELD_WIDTH+2, players[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); + } + + 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]; + + if (dispmode != MODE_FIELDS) + return; + 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]]; + 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) + 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) + 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+1, x+7, buf); + sprintf(buf, "%d", levels[my_playernum]); + mvaddstr(y+2, 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) + 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)); + } + 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(); + 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); + 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); + + screen_refresh(); +} + +/*************************************************************************/ +/************************** Interface declaration ************************/ +/*************************************************************************/ + +Interface xwin_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 +}; + +/*************************************************************************/