initial commit

This commit is contained in:
John Sennesael 2022-02-15 18:17:36 -06:00
commit 34a631ffe1
22 changed files with 6602 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
[syntax=glob]
*.o
*.swp
*.*~

28
AUTHORS Normal file
View File

@ -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 <achurch@achurch.org>
Vanilla tetrinet maintainer
The original implementation ;-)
Chris Clark <clach04@yahoo.com>
Counterclockwise rotation
Christoph Weiss <weissch@informatik.tu-muenchen.de>
Teamplay fix
Cougar <cougar@random.ee>
IPv6 support
Gadall <gadall@hotmail.com>
FreeBSD compilation fixes
Gerfried Fuchs <alfie@ist.org>
Random hacking
Petr Baudis <pasky@ucw.cz>
-pb branch maintainer
Multichannel support
Tetrifast support
Random hacking

30
CMakeLists.txt Normal file
View File

@ -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}
)

135
ChangeLog Normal file
View File

@ -0,0 +1,135 @@
Thu Oct 2 16:35:59 2003 Petr Baudis <pasky@ucw.cz>:
* Changes, README, version.h:
tetrinet-0.11
Thu Sep 11 20:26:53 2003 Gerfried Fuchs <alfie@ist.org>:
* 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 <alfie@ist.org>:
* sockets.c:
#include <string.c> to silence memcpy and memset warning.
Sun Sep 7 16:30:02 2003 Petr Baudis <pasky@ucw.cz>:
* README, version.h:
0.10-pb4
Sun Sep 7 16:29:29 2003 Petr Baudis <pasky@ucw.cz>:
* 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 <pasky@ucw.cz>:
* 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 <pasky@ucw.cz>:
* 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 <pasky@ucw.cz>:
* README, version.h:
0.10-pb3
Fri Sep 5 11:37:01 2003 Petr Baudis <pasky@ucw.cz>:
* tetrinet.c, version.h:
Include the version information in the usage output.
Fri Sep 5 11:33:03 2003 Petr Baudis <pasky@ucw.cz>:
* .cvsignore:
Ignore compiled binaries.
Fri Sep 5 11:25:26 2003 Petr Baudis <pasky@ucw.cz>:
* server.c:
_Untested_ server-side tetrifast support.
Fri Sep 5 11:15:54 2003 Petr Baudis <pasky@ucw.cz>:
* README, tetrinet.c:
Documented the -fast option.
Fri Sep 5 11:09:09 2003 Petr Baudis <pasky@ucw.cz>:
* tetrinet.c:
Print verbose usage help, also when an unknown option is passed.
Fri Sep 5 10:56:32 2003 Petr Baudis <pasky@ucw.cz>:
* Changes:
-pb branch changes are in ChangeLog.
Fri Sep 5 10:54:00 2003 Petr Baudis <pasky@ucw.cz>:
* AUTHORS:
Put together some AUTHORS file.
Fri Sep 5 10:46:45 2003 Petr Baudis <pasky@ucw.cz>:
* README:
0.10-pb2 (pb1 was done outside of CVS yet)
Fri Sep 5 10:39:20 2003 Petr Baudis <pasky@ucw.cz>:
* tetrinet.c, tetrinet.h, tetris.c:
Tetrifast support.
Fri Sep 5 10:38:34 2003 Petr Baudis <pasky@ucw.cz>:
* README, tetrinet.c:
Introduced multichannel support (it was rather just a trivial fix).
Fri Sep 5 10:37:13 2003 Petr Baudis <pasky@ucw.cz>:
* tetrinet.c:
Still send unknown commands to the server, patch by Gerfried Fuchs
<alfie@ist.org> and me.
Fri Sep 5 10:30:53 2003 Petr Baudis <pasky@ucw.cz>:
* README:
Administrative commit - 0.10-pb0.
Fri Sep 5 10:28:55 2003 Petr Baudis <pasky@ucw.cz>:
* 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

43
Changes Normal file
View File

@ -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 <pasky@ucw.cz>
2003/04/01 .8 Junk lines are no longer added for double/triple/Tetris
clears by team members. Patch provided by Christoph
Weiss <weissch@informatik.tu-muenchen.de>
2002/08/13 Added IPv6 support. Patch for client provided by Cougar
<cougar@random.ee>
2002/08/10 Added clean and spotless targets to Makefile.
2002/01/28 .7a Fixed compilation error on FreeBSD. Reported by Gadall
<gadall@hotmail.com>
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
<clach04@yahoo.com>
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.

41
Makefile Normal file
View File

@ -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

293
README Normal file
View File

@ -0,0 +1,293 @@
Tetrinet for Linux
------------------
by Andrew Church <achurch@achurch.org>
and Petr Baudis <pasky@ucw.cz>
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 <file> Log network traffic to the given file. All lines
start with an absolute time (seconds) in brackets.
Lines sent from the client to the server are prefixed
with ">>>", and lines from the server to the client
are prefixed with "<<<". This could be used with a
utility program to replay a game later on (though such
a program is not currently included in the Tetrinet
distribution.)
-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 <pid-of-server>
where <pid-of-server> 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.

9
README.md Normal file
View File

@ -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.

8
TODO Normal file
View File

@ -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

66
io.h Normal file
View File

@ -0,0 +1,66 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* 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 */

966
server.c Normal file
View File

@ -0,0 +1,966 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* This program is public domain.
*
* Tetrinet server code
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <netinet/in.h>
/* Due to glibc brokenness, we can't blindly include this. Yet another
* reason to not use glibc. */
/* #include <netinet/protocols.h> */
#include <signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#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;
}
/*************************************************************************/

8
server.h Normal file
View File

@ -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 */

205
sockets.c Normal file
View File

@ -0,0 +1,205 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* This program is public domain.
*
* Socket routines.
*/
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <unistd.h>
#include <string.h>
#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);
}
/*************************************************************************/

18
sockets.h Normal file
View File

@ -0,0 +1,18 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* 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 */

752
tetrinet.c Normal file
View File

@ -0,0 +1,752 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* This program is public domain.
*
* Tetrinet main program.
*/
/*************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#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 <file> 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 */
/*************************************************************************/

98
tetrinet.h Normal file
View File

@ -0,0 +1,98 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* 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

628
tetrinet.txt Normal file
View File

@ -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 <T> 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 <message>
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 <T> 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

1045
tetris.c Normal file

File diff suppressed because it is too large Load Diff

75
tetris.h Normal file
View File

@ -0,0 +1,75 @@
/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
* 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 */

1100
tty.c Normal file

File diff suppressed because it is too large Load Diff

12
version.h Normal file
View File

@ -0,0 +1,12 @@
/* Tetrinet for Linux, by Petr Baudis <pasky@ucw.cz>
* This program is public domain.
*
* Tetrinet version information file.
*/
#ifndef TETRINET__VERSION_H
#define TETRINET__VERSION_H
#define VERSION "0.11"
#endif

1038
xwin.c Normal file

File diff suppressed because it is too large Load Diff