initial commit
This commit is contained in:
commit
34a631ffe1
|
@ -0,0 +1,4 @@
|
|||
[syntax=glob]
|
||||
*.o
|
||||
*.swp
|
||||
*.*~
|
|
@ -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
|
|
@ -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}
|
||||
)
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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
|
||||
|
|
@ -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.
|
|
@ -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.
|
||||
|
|
@ -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
|
|
@ -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 */
|
|
@ -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;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
|
@ -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 */
|
|
@ -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);
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
|
@ -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 */
|
|
@ -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 */
|
||||
|
||||
/*************************************************************************/
|
|
@ -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
|
|
@ -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
|
|
@ -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 */
|
|
@ -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
|
Loading…
Reference in New Issue