From 89d91b6cfd98e514b3e418526ba854d00beb876c Mon Sep 17 00:00:00 2001 From: Gerfried Fuchs Date: Wed, 14 Jul 2010 21:37:25 +0200 Subject: [PATCH] Imported Upstream version 0.4 --- COPYING | 339 ++++++++++++++++++++++++++++++++++ Configure | 259 ++++++++++++++++++++++++++ FAQ | 112 +++++++++++ README | 132 +++++++++++++ VERSION | 1 + board.c | 247 +++++++++++++++++++++++++ curses.c | 304 ++++++++++++++++++++++++++++++ game.c | 534 +++++++++++++++++++++++++++++++++++++++++++++++++++++ inet.c | 216 ++++++++++++++++++++++ netris.h | 180 ++++++++++++++++++ robot.c | 174 +++++++++++++++++ robot_desc | 225 ++++++++++++++++++++++ shapes.c | 191 +++++++++++++++++++ sr.c | 474 +++++++++++++++++++++++++++++++++++++++++++++++ util.c | 385 ++++++++++++++++++++++++++++++++++++++ 15 files changed, 3773 insertions(+) create mode 100644 COPYING create mode 100755 Configure create mode 100644 FAQ create mode 100644 README create mode 100644 VERSION create mode 100644 board.c create mode 100644 curses.c create mode 100644 game.c create mode 100644 inet.c create mode 100644 netris.h create mode 100644 robot.c create mode 100644 robot_desc create mode 100644 shapes.c create mode 100644 sr.c create mode 100644 util.c diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..e77696a --- /dev/null +++ b/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/Configure b/Configure new file mode 100755 index 0000000..7fe293e --- /dev/null +++ b/Configure @@ -0,0 +1,259 @@ +: +# +# Netris -- A free networked version of T*tris +# Copyright (C) 1994,1995,1996 Mark H. Weaver +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id: Configure,v 1.17 1996/02/09 08:22:03 mhw Exp $ +# + +CC="gcc" +COPT="-g -O" +CEXTRA="" +LEXTRA="" + +while [ $# -ge 1 ]; do + opt="$1" + shift + case "$opt" in + -g) + COPT="-g" + CEXTRA="-Wall -Wstrict-prototypes" + ;; + -O*) + COPT="$opt" + CEXTRA="-DNDEBUG" + ;; + --cc) + CC="$1" + shift + ;; + --copt) + COPT="$1" + shift + ;; + --cextra) + CEXTRA="$1" + shift + ;; + --lextra) + LEXTRA="$1" + shift + ;; + *) + cat << "END" +Usage: ./Configure [options...] + -g: Full debugging, no optimization, and full warnings + -O?: Optimization, no debugging or warnings + --cc : Set the C compiler to use (default "gcc") + --copt : Set C optimization flags + --cextra : Set extra C flags + --lextra : Set extra linker flags +END + exit 1 + ;; + esac +done + +CFLAGS="$COPT $CEXTRA" + +echo "Checking for libraries" +echo 'main(){}' > test.c +LFLAGS="" +for lib in -lsocket -lnsl -lcurses -ltermcap +do + if $CC $CFLAGS $LEXTRA test.c $lib > /dev/null 2>&1; then + LFLAGS="$LFLAGS $lib" + fi +done + +echo "Checking for on_exit()" +cat << END > test.c +void handler(void) {} +main() { on_exit(handler, (void *)0); } +END +if $CC $CFLAGS $LEXTRA test.c > /dev/null 2>&1; then + HAS_ON_EXIT=true +else + HAS_ON_EXIT=false +fi + +echo "Checking for sigprocmask()" +cat << END > test.c +#include +main() { sigset_t set; sigprocmask(SIG_BLOCK, &set, &set); } +END +if $CC $CFLAGS $LEXTRA test.c > /dev/null 2>&1; then + HAS_SIGPROCMASK=true +else + HAS_SIGPROCMASK=false +fi + +echo "Checking for getopt.h" +cat << END > test.c +#include +main(){} +END + +if $CC $CFLAGS $LEXTRA test.c > /dev/null 2>&1; then + HAS_GETOPT_H=true +else + HAS_GETOPT_H=false +fi + +echo "Checking for memory.h" +cat << END > test.c +#include +main(){} +END + +if $CC $CFLAGS $LEXTRA test.c > /dev/null 2>&1; then + HAS_MEMORY_H=true +else + HAS_MEMORY_H=false +fi + +rm -f test.c test.o a.out + +ORIG_SOURCES="game- curses- shapes- board- util- inet- robot-" +GEN_SOURCES="version-" +SOURCES="$ORIG_SOURCES $GEN_SOURCES" + +SRCS="`echo $SOURCES | sed -e s/-/.c/g`" +OBJS="`echo $SOURCES | sed -e s/-/.o/g`" + +DISTFILES="README FAQ COPYING VERSION Configure netris.h sr.c robot_desc" +DISTFILES="$DISTFILES `echo $ORIG_SOURCES | sed -e s/-/.c/g`" + +echo > .depend + +echo "Creating Makefile" +sed -e "s/-LFLAGS-/$LFLAGS/g" -e "s/-SRCS-/$SRCS/g" \ + -e "s/-OBJS-/$OBJS/g" -e "s/-DISTFILES-/$DISTFILES/g" \ + -e "s/-COPT-/$COPT/g" -e "s/-CEXTRA-/$CEXTRA/g" \ + -e "s/-LEXTRA-/$LEXTRA/g" -e "s/-CC-/$CC/g" << "END" > Makefile +# +# Automatically generated by ./Configure -- DO NOT EDIT! +# + +CC = -CC- +COPT = -COPT- +CEXTRA = -CEXTRA- +LEXTRA = -LEXTRA- +LFLAGS = -LEXTRA- -LFLAGS- +CFLAGS = $(CEXTRA) $(COPT) + +PROG = netris +HEADERS = netris.h + +SRCS = -SRCS- +OBJS = -OBJS- +DISTFILES = -DISTFILES- + +all: Makefile config.h proto.h $(PROG) sr + +$(PROG): $(OBJS) + $(CC) -o $(PROG) $(OBJS) $(LFLAGS) + +sr: sr.o + $(CC) -o sr sr.o $(LFLAGS) + +.c.o: + $(CC) $(CFLAGS) -c $< + +Makefile config.h: Configure + @echo "Makefile and/or config.h is out of date" + @echo "Run ./Configure now" + @false + +version.c: VERSION + @echo "Creating version.c" + @sed -e 's/^\(.*\)$$/char *version_string = "\1";/' VERSION > $@ + +proto.h: $(SRCS) + @touch $@ + @mv $@ $@.old + @cat $(SRCS) | grep '^ExtFunc[ ]' | sed -e 's/)$$/);/' > $@ + @if diff $@.old $@ > /dev/null 2>&1; then :; else \ + echo "proto.h changed"; \ + touch proto.chg; \ + fi + @rm -f $@.old + +depend: proto.h $(SRCS) + @echo "Checking dependencies" + @sed -n -e '1,/make depend #####$$/p' Makefile > Makefile.new + @$(CC) -M $(SRCS) | sed -e 's/proto\.h/proto.chg/g' >> Makefile.new + @mv -f Makefile.new Makefile + +dist: $(DISTFILES) + @vers=`cat VERSION`; \ + dir="netris-$$vers"; \ + echo "Creating $$dir directory"; \ + rm -rf $$dir; \ + mkdir $$dir; \ + cp $(DISTFILES) $$dir; \ + chmod 755 $$dir; \ + chmod 644 $$dir/*; \ + chmod 755 $$dir/Configure; \ + echo "Creating $$dir.tar.gz"; \ + tar -cvzof $$dir.tar.gz $$dir + +clean: + rm -f proto.h proto.chg $(PROG) $(OBJS) version.c test.c a.out sr sr.o + +cleandir: clean + rm -f .depend Makefile config.h + +##### DO NOT EDIT OR DELETE THIS LINE, it's needed by make depend ##### +END + +echo "Creating config.h" +cat << END > config.h +/* + * Automatically generated by ./Configure -- DO NOT EDIT! + */ + +END + +if [ "$HAS_GETOPT_H" = "true" ]; then + echo "#include " >> config.h +else + echo "extern char *optarg;" >> config.h + echo "extern int optind;" >> config.h +fi +if [ "$HAS_MEMORY_H" = "true" ]; then + echo "#include " >> config.h +fi +if [ "$HAS_ON_EXIT" = "true" ]; then + echo "#define HAS_ON_EXIT" >> config.h +fi +if [ "$HAS_SIGPROCMASK" = "true" ]; then + echo "#define HAS_SIGPROCMASK" >> config.h +fi + +echo "Running 'make depend'" +if make depend; then :; else cat << END; fi + +make depend failed, but that's OK unless you're doing development +END +cat << END + +Now do a 'make' + +END + +# vi: ts=4 ai diff --git a/FAQ b/FAQ new file mode 100644 index 0000000..18a203c --- /dev/null +++ b/FAQ @@ -0,0 +1,112 @@ +# +# Netris +# Frequently asked questions +# +# $Id: FAQ,v 1.3 1996/02/09 08:47:23 mhw Exp $ +# + +Questions +========= +[1] Where can I find the latest version? +[2] The pieces look bizarre in my xterm window, and don't erase + properly. What's up? +[3] If I drop a piece and then slide it off a cliff, shouldn't + it automatically drop again? +[4] When I try to play a networked game, it just hangs. +[5] Is the game fair? Is there an advantage to being the -w or the + -c player? +[6] I'm using a slow terminal, and the game response is sluggish. + What can I do? +[7] Why can't my terminal hide the cursor? + +Answers +======= +[1] Where can I find the latest version? + + ftp://ftp.netris.org/pub/netris/ + + The latest version is available via anonymous ftp from + ftp.netris.org in /pub/netris. + + Unfortunately this machine is currently on the far end of a + 14.4kbps modem connection, and may go away at any time without + notice, but I'll do my best to keep it up. If you have trouble + contacting the ftp server, try mailing me at . + +[2] The pieces look bizarre in my xterm window, and don't erase + properly. What's up? + + Try disabling standout mode with the -S option. If this fixes it, + it's probably because the bold font on your xterm is set wrong. + It's a good idea to fix it, since the blocks look much nicer when + they're inverse. + + If the blocks don't erase correctly and are drawn larger than normal + characters, check your .Xdefaults file. If you set the "font", make + sure you also set the "boldFont" to something of the same size. For + example, I use 6x10 font for xterms, and here are the relevant lines + from my xterm. + + txterm*font: 6x10 + txterm*boldFont: 6x10 + + Make sure you capitalize the F in "boldFont" + +[3] If I drop a piece and then slide it off a cliff, shouldn't + it automatically drop again? + + Try the -D option. + +[4] When I try to play a networked game, it just hangs. + + There are two possibilities. First, versions 0.1d? are incompatible + with current versions. This is unfortunate, but remember, those were + very developmental versions. I needed to fix up the protocol, and + I didn't want a whole bunch of messy compatibility code (at least + not yet :-) Ask your opponent to get the latest version. + + The other possibility is that you've typed in the wrong hostname, or + there's a port number mismatch, if either of you used the -p option. + +[5] Is the game fair? Is there an advantage to being the -w or the + -c player? + + The game is fair. The game is completely symmetric once the + connection is established. Furthermore, a random number seed is + exchanged at the start of the game, so both players will get the + same pieces. + + There is a built-in random number generator, so even if the C + library on your system has a non-standard generator, both + players will still get the same sequence. + + There is no attempt to synchronize the start of the game + accurately for networks with high latency. This should be fixed + at some point, but I doubt such a small head start makes much + difference for a game lasting several minutes. + +[6] I'm using a slow terminal, and the game response is sluggish. + What can I do? + + Try the -S option. This disables use of standout mode (bold/inverse), + which require control sequences to be sent twice (or more) per line. + Standout mode makes the pieces look much more like blocks, but can + make the game unplayable on a slow terminal. + + Also, you can type 's' to toggle spying (updating your view of the + opponent's board). + +[7] Why can't my terminal hide the cursor? + + Netris uses the termcap library to look up the "vi" and "ve" + capabilities, which make the cursor invisible/visible + respectively. These capabilities aren't very consistently + reported among un*xes, so I use compiled-in vt220 codes for + several vt100-like terminals. Most emulators probably won't + support the codes, but they'll probably ignore them quietly. + + Try setting the TERM environment variable to "vt100" or "vt220" + before running Netris. If that doesn't work, your terminal + probably doesn't support cursor invisibility. + +# vi: tw=70 ai diff --git a/README b/README new file mode 100644 index 0000000..fc7d155 --- /dev/null +++ b/README @@ -0,0 +1,132 @@ +# +# Netris -- A free networked version of T*tris +# Copyright (C) 1994,1995,1996 Mark H. Weaver +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# $Id: README,v 1.20 1996/02/09 08:22:06 mhw Exp $ +# + +This is an unfinished developmental version of Netris, a free +networked version of T*tris. It is distributed under the terms +of the GNU General Public License, which is described in the +file "COPYING" included with this distribution. + +In order to compile Netris you will need gcc. You may be able to +compile it with another ANSI C compiler, but if you attempt this +you are on your own. + +It's been built and tested on at least the following systems: + + NetBSD 1.0, 1.1 + Linux + SunOS 4.1.1, 4.1.3 + Solaris 2.3, 2.4 + HP-UX + +If Netris doesn't build on your favorite system "out-of-the-box", +I encourage you to mail me context diffs to fix the problem so I +can fold it into the next version. + +Netris should build cleanly on 64-bit systems such as the Alpha, +although you might need to edit the definitions for netint{2,4}, +hton{2,4}, and ntoh{2,4} in netris.h. Alpha users, please let me know +how it goes, and send me diffs if needed! + +See the FAQ in this directory if you have any problems. + + +NEW IN VERSION 0.4 +================== +- Netris now attempts to make the cursor invisible for terminals that + support it. Xterm no, vt220 yes, vt100 maybe? +- Ctrl-L (by default) will now redraw the screen. +- Various cleanup and documentation changes. + + +INSTALLATION +============ +1. Run "./Configure" to create a Makefile and config.h appropriate + for your system. If you have problems running Configure with + your /bin/sh, try "bash Configure". +2. Try "make" +3. Make sure "./netris" works properly +4. Copy "./netris" to the appropriate public directory + +Try "./Configure -h" for more options + + +RUNNING +======= +To start a two-player game, do the following: + 1. Player 1 types "netris -w". This means "wait for challenge". + 2. Player 2 types "netris -c " where is the hostname + of Player 1. This means "challenge". + +To start a one-player game, run netris with no parameters. +One-player mode is a tad boring at the moment, because it never +gets any faster, and there's no scoring. This will be rectified +at some point. For now, use the "f" key (by default) to make the +game go faster. Speedups cannot be reversed for the remainder of +the game. + +Unlike standard T*tris, Netris gives you a little extra time after +dropping a piece before it solidifies. This allows you to slide the +piece into a notch without waiting for it to fall the whole way down. +In fact, if you can even slide it off a cliff and it'll start falling +again. If you think it should automatically drop again in this case, +use the -D option. + +The keys are: + 'j' left + 'k' rotate + 'l' right + Space drop + 'm' down faster + 's' toggle spying on the other player + 'p' pause + 'f' make game faster (irreversable) + Ctrl-L redraw the screen + +To see usage information, type "netris -h". +To see distribution/warranty information, type "netris -H". +To see the rules, type "netris -R". +To use a port number other than the default, use the -p option. + +You can remap the keys with "-k ", where is a string +containing the keys in the order listed above. The default is: + netris -k "jkl mspf^l" + +You needn't specify all of the keys, for example -k "asd" will only +change the main three keys. "^x" notation can be used for control +characters. + +The "m" key moves the falling piece down one block, in addition to the +usual step-down timer. Use this in repetition when "drop" would go +too far but you don't want to wait for the piece of fall. + + +RUMORS +====== +At some point I may implement a server that Netris players can connect +to to find other players with similar skill across the globe. + +This version at least partially supports robots. A rough description +of the protocol is in "robot_desc", and a sample robot is in sr.c. + +The source code should be viewed with tab stops set every 4 columns, +eg, "less -x4 game.c". + +# vi: tw=70 ai diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..bd73f47 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.4 diff --git a/board.c b/board.c new file mode 100644 index 0000000..125cdd0 --- /dev/null +++ b/board.c @@ -0,0 +1,247 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: board.c,v 1.14 1996/02/09 08:22:08 mhw Exp $ + */ + +#include "netris.h" +#include + +#ifdef DEBUG_FALLING +# define B_OLD +#else +# define B_OLD abs +#endif + +static BlockType board[MAX_SCREENS][MAX_BOARD_HEIGHT][MAX_BOARD_WIDTH]; +static BlockType oldBoard[MAX_SCREENS][MAX_BOARD_HEIGHT][MAX_BOARD_WIDTH]; +static unsigned int changed[MAX_SCREENS][MAX_BOARD_HEIGHT]; +static int falling[MAX_SCREENS][MAX_BOARD_WIDTH]; +static int oldFalling[MAX_SCREENS][MAX_BOARD_WIDTH]; + +ExtFunc void InitBoard(int scr) +{ + boardHeight[scr] = MAX_BOARD_HEIGHT; + boardVisible[scr] = 20; + boardWidth[scr] = 10; + InitScreen(scr); +} + +ExtFunc void CleanupBoard(int scr) +{ + CleanupScreen(scr); +} + +ExtFunc BlockType GetBlock(int scr, int y, int x) +{ + if (y < 0 || x < 0 || x >= boardWidth[scr]) + return BT_wall; + else if (y >= boardHeight[scr]) + return BT_none; + else + return abs(board[scr][y][x]); +} + +ExtFunc void SetBlock(int scr, int y, int x, BlockType type) +{ + if (y >= 0 && y < boardHeight[scr] && x >= 0 && x < boardWidth[scr]) { + if (y < boardVisible[scr]) + falling[scr][x] += (type < 0) - (board[scr][y][x] < 0); + board[scr][y][x] = type; + changed[scr][y] |= 1 << x; + } +} + +ExtFunc int RefreshBoard(int scr) +{ + int y, x, any = 0; + unsigned int c; + BlockType b; + + for (y = boardVisible[scr] - 1; y >= 0; --y) + if ((c = changed[scr][y])) { + if (robotEnable) { + RobotCmd(0, "RowUpdate %d %d", scr, y); + for (x = 0; x < boardWidth[scr]; ++x) { + b = board[scr][y][x]; + if (fairRobot) + b = abs(b); + RobotCmd(0, " %d", b); + } + RobotCmd(0, "\n"); + } + changed[scr][y] = 0; + any = 1; + for (x = 0; c; (c >>= 1), (++x)) + if ((c & 1) && B_OLD(board[scr][y][x])!=oldBoard[scr][y][x]) { + PlotBlock(scr, y, x, B_OLD(board[scr][y][x])); + oldBoard[scr][y][x] = B_OLD(board[scr][y][x]); + } + } + if (robotEnable) + RobotTimeStamp(); + for (x = 0; x < boardWidth[scr]; ++x) + if (oldFalling[scr][x] != !!falling[scr][x]) { + oldFalling[scr][x] = !!falling[scr][x]; + PlotUnderline(scr, x, oldFalling[scr][x]); + any = 1; + } + return any; +} + +ExtFunc int PlotFunc(int scr, int y, int x, BlockType type, void *data) +{ + SetBlock(scr, y, x, type); + return 0; +} + +ExtFunc int EraseFunc(int scr, int y, int x, BlockType type, void *data) +{ + SetBlock(scr, y, x, BT_none); + return 0; +} + +ExtFunc int CollisionFunc(int scr, int y, int x, BlockType type, void *data) +{ + return GetBlock(scr, y, x) != BT_none; +} + +ExtFunc int VisibleFunc(int scr, int y, int x, BlockType type, void *data) +{ + return (y >= 0 && y < boardVisible[scr] && x >= 0 && x < boardWidth[scr]); +} + +ExtFunc void PlotShape(Shape *shape, int scr, int y, int x, int falling) +{ + ShapeIterate(shape, scr, y, x, falling, PlotFunc, NULL); +} + +ExtFunc void EraseShape(Shape *shape, int scr, int y, int x) +{ + ShapeIterate(shape, scr, y, x, 0, EraseFunc, NULL); +} + +ExtFunc int ShapeFits(Shape *shape, int scr, int y, int x) +{ + return !ShapeIterate(shape, scr, y, x, 0, CollisionFunc, NULL); +} + +ExtFunc int ShapeVisible(Shape *shape, int scr, int y, int x) +{ + return ShapeIterate(shape, scr, y, x, 0, VisibleFunc, NULL); +} + +ExtFunc int MovePiece(int scr, int deltaY, int deltaX) +{ + int result; + + EraseShape(curShape[scr], scr, curY[scr], curX[scr]); + result = ShapeFits(curShape[scr], scr, curY[scr] + deltaY, + curX[scr] + deltaX); + if (result) { + curY[scr] += deltaY; + curX[scr] += deltaX; + } + PlotShape(curShape[scr], scr, curY[scr], curX[scr], 1); + return result; +} + +ExtFunc int RotatePiece(int scr) +{ + int result; + + EraseShape(curShape[scr], scr, curY[scr], curX[scr]); + result = ShapeFits(curShape[scr]->rotateTo, scr, curY[scr], curX[scr]); + if (result) + curShape[scr] = curShape[scr]->rotateTo; + PlotShape(curShape[scr], scr, curY[scr], curX[scr], 1); + return result; +} + +ExtFunc int DropPiece(int scr) +{ + int count = 0; + + EraseShape(curShape[scr], scr, curY[scr], curX[scr]); + while (ShapeFits(curShape[scr], scr, curY[scr] - 1, curX[scr])) { + --curY[scr]; + ++count; + } + PlotShape(curShape[scr], scr, curY[scr], curX[scr], 1); + return count; +} + +ExtFunc int LineIsFull(int scr, int y) +{ + int x; + + for (x = 0; x < boardWidth[scr]; ++x) + if (GetBlock(scr, y, x) == BT_none) + return 0; + return 1; +} + +ExtFunc void CopyLine(int scr, int from, int to) +{ + int x; + + if (from != to) + for (x = 0; x < boardWidth[scr]; ++x) + SetBlock(scr, to, x, GetBlock(scr, from, x)); +} + +ExtFunc int ClearFullLines(int scr) +{ + int from, to; + + from = to = 0; + while (to < boardHeight[scr]) { + while (LineIsFull(scr, from)) + ++from; + CopyLine(scr, from++, to++); + } + return from - to; +} + +ExtFunc void FreezePiece(int scr) +{ + int y, x; + BlockType type; + + for (y = 0; y < boardHeight[scr]; ++y) + for (x = 0; x < boardWidth[scr]; ++x) + if ((type = board[scr][y][x]) < 0) + SetBlock(scr, y, x, -type); +} + +ExtFunc void InsertJunk(int scr, int count, int column) +{ + int y, x; + + for (y = boardHeight[scr] - count - 1; y >= 0; --y) + CopyLine(scr, y, y + count); + for (y = 0; y < count; ++y) + for (x = 0; x < boardWidth[scr]; ++x) + SetBlock(scr, y, x, (x == column) ? BT_none : BT_piece1); + curY[scr] += count; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/curses.c b/curses.c new file mode 100644 index 0000000..047efa4 --- /dev/null +++ b/curses.c @@ -0,0 +1,304 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: curses.c,v 1.32 1996/02/09 08:47:25 mhw Exp $ + */ + +#include "netris.h" +#include +#include +#include +#include +#include + +static void PlotBlock1(int scr, int y, int x, BlockType type); +static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event); + +static EventGenRec keyGen = + { NULL, 0, FT_read, STDIN_FILENO, KeyGenFunc, EM_key }; + +static int boardYPos[MAX_SCREENS], boardXPos[MAX_SCREENS]; +static int statusYPos, statusXPos; + +static char *term_vi; /* String to make cursor invisible */ +static char *term_ve; /* String to make cursor visible */ + +ExtFunc void InitScreens(void) +{ + MySigSet oldMask; + + GetTermcapInfo(); + + /* + * Do this atomically. Otherwise a badly timed Ctrl-C during + * initialization will leave your terminal in a bad state. + */ + BlockSignals(&oldMask, SIGINT, 0); + initscr(); + AtExit(CleanupScreens); + RestoreSignals(NULL, &oldMask); + + cbreak(); + noecho(); + OutputTermStr(term_vi, 0); + AddEventGen(&keyGen); + + move(0, 0); + addstr("Netris "); + addstr(version_string); + addstr(" (C) 1994,1995,1996 Mark H. Weaver " + "\"netris -h\" for more info"); + statusYPos = 22; + statusXPos = 0; +} + +ExtFunc void CleanupScreens(void) +{ + RemoveEventGen(&keyGen); + endwin(); + OutputTermStr(term_ve, 1); +} + +ExtFunc void GetTermcapInfo(void) +{ + char *term, *buf, *data; + int bufSize = 10240; + + if (!(term = getenv("TERM"))) + return; + if (tgetent(scratch, term) == 1) { + /* + * Make the buffer HUGE, since tgetstr is unsafe. + * Allocate it on the heap too. + */ + data = buf = malloc(bufSize); + + /* + * There is no standard include file for tgetstr, no prototype + * definitions. I like casting better than using my own prototypes + * because if I guess the prototype, I might be wrong, especially + * with regards to "const". + */ + term_vi = (char *)tgetstr("vi", &data); + term_ve = (char *)tgetstr("ve", &data); + + /* Okay, so I'm paranoid; I just don't like unsafe routines */ + if (data > buf + bufSize) + fatal("tgetstr overflow, you must have a very sick termcap"); + + /* Trim off the unused portion of buffer */ + buf = realloc(buf, data - buf); + } + + /* + * If that fails, use hardcoded vt220 codes. + * They don't seem to do anything bad on vt100's, so + * we'll try them just in case they work. + */ + if (!term_vi || !term_ve) { + static char *vts[] = { + "vt100", "vt101", "vt102", + "vt200", "vt220", "vt300", + "vt320", "vt400", "vt420", + "screen", "xterm", NULL }; + int i; + + for (i = 0; vts[i]; i++) + if (!strcmp(term, vts[i])) + { + term_vi = "\033[?25l"; + term_ve = "\033[?25h"; + break; + } + } + if (!term_vi || !term_ve) + term_vi = term_ve = NULL; +} + +ExtFunc void OutputTermStr(char *str, int flush) +{ + if (str) { + fputs(str, stdout); + if (flush) + fflush(stdout); + } +} + +ExtFunc void InitScreen(int scr) +{ + int y, x; + + if (scr == 0) + boardXPos[scr] = 1; + else + boardXPos[scr] = boardXPos[scr - 1] + + 2 * boardWidth[scr - 1] + 3; + boardYPos[scr] = 22; + if (statusXPos < boardXPos[scr] + 2 * boardWidth[scr] + 3) + statusXPos = boardXPos[scr] + 2 * boardWidth[scr] + 3; + for (y = boardVisible[scr] - 1; y >= 0; --y) { + move(boardYPos[scr] - y, boardXPos[scr] - 1); + addch('|'); + move(boardYPos[scr] - y, boardXPos[scr] + 2 * boardWidth[scr]); + addch('|'); + } + for (y = boardVisible[scr]; y >= -1; y -= boardVisible[scr] + 1) { + move(boardYPos[scr] - y, boardXPos[scr] - 1); + addch('+'); + for (x = boardWidth[scr] - 1; x >= 0; --x) + addstr("--"); + addch('+'); + } +} + +ExtFunc void CleanupScreen(int scr) +{ +} + +static void PlotBlock1(int scr, int y, int x, BlockType type) +{ + move(boardYPos[scr] - y, boardXPos[scr] + 2 * x); + switch (type) { + case BT_none: + addstr(" "); + break; + case -BT_piece1: + if (standoutEnable) + standout(); + addstr("$$"); + if (standoutEnable) + standend(); + break; + case BT_piece1: + if (standoutEnable) + standout(); + addstr("[]"); + if (standoutEnable) + standend(); + break; + default: + assert(0); + } +} + +ExtFunc void PlotBlock(int scr, int y, int x, BlockType type) +{ + if (y >= 0 && y < boardVisible[scr] && x >= 0 && x < boardWidth[scr]) + PlotBlock1(scr, y, x, type); +} + +ExtFunc void PlotUnderline(int scr, int x, int flag) +{ + move(boardYPos[scr] + 1, boardXPos[scr] + 2 * x); + addstr(flag ? "==" : "--"); +} + +ExtFunc void ShowDisplayInfo(void) +{ + move(statusYPos - 9, statusXPos); + printw("Seed: %d", initSeed); + clrtoeol(); + move(statusYPos - 8, statusXPos); + printw("Speed: %dms", speed / 1000); + clrtoeol(); + if (robotEnable) { + move(statusYPos - 6, statusXPos); + if (fairRobot) + addstr("Controlled by a fair robot"); + else + addstr("Controlled by a robot"); + clrtoeol(); + } + if (opponentFlags & SCF_usingRobot) { + move(statusYPos - 5, statusXPos); + if (opponentFlags & SCF_fairRobot) + addstr("The opponent is a fair robot"); + else + addstr("The opponent is a robot"); + clrtoeol(); + } +} + +ExtFunc void UpdateOpponentDisplay(void) +{ + move(1, 0); + printw("Playing %s@%s", opponentName, opponentHost); + clrtoeol(); +} + +ExtFunc void ShowPause(int pausedByMe, int pausedByThem) +{ + move(statusYPos - 3, statusXPos); + if (pausedByThem) + addstr("Game paused by opponent"); + else + clrtoeol(); + move(statusYPos - 2, statusXPos); + if (pausedByMe) + addstr("Game paused by you"); + else + clrtoeol(); +} + +ExtFunc void Message(char *s) +{ + static int line = 0; + + move(statusYPos - 20 + line, statusXPos); + addstr(s); /* XXX Should truncate long lines */ + clrtoeol(); + line = (line + 1) % 10; + move(statusYPos - 20 + line, statusXPos); + clrtoeol(); +} + +ExtFunc void RefreshScreen(void) +{ + static char timeStr[2][32]; + time_t theTime; + + time(&theTime); + strftime(timeStr[0], 30, "%I:%M %p", localtime(&theTime)); + /* Just in case the local curses library sucks */ + if (strcmp(timeStr[0], timeStr[1])) + { + move(statusYPos, statusXPos); + addstr(timeStr[0]); + strcpy(timeStr[1], timeStr[0]); + } + move(boardYPos[0] + 1, boardXPos[0] + 2 * boardWidth[0] + 1); + refresh(); +} + +ExtFunc void ScheduleFullRedraw(void) +{ + touchwin(stdscr); +} + +static MyEventType KeyGenFunc(EventGenRec *gen, MyEvent *event) +{ + if (MyRead(gen->fd, &event->u.key, 1)) + return E_key; + else + return E_none; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/game.c b/game.c new file mode 100644 index 0000000..26ab6a8 --- /dev/null +++ b/game.c @@ -0,0 +1,534 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: game.c,v 1.38 1996/02/09 08:22:11 mhw Exp $ + */ + +#define NOEXT +#include "netris.h" +#include +#include +#include +#include +#include + +enum { KT_left, KT_rotate, KT_right, KT_drop, KT_down, + KT_toggleSpy, KT_pause, KT_faster, KT_redraw, KT_numKeys }; + +static char *keyNames[KT_numKeys+1] = { + "Left", "Rotate", "Right", "Drop", "Down", "ToggleSpy", "Pause", + "Faster", "Redraw", NULL }; + +static char *gameNames[GT_len] = { "OnePlayer", "ClassicTwo" }; + +static char keyTable[KT_numKeys+1]; +static int dropModeEnable = 0; +static char *robotProg; + +ExtFunc void MapKeys(char *newKeys) +{ + int i, k, ch; + char used[256]; + int errs = 0; + + /* XXX assumptions about ASCII encoding here */ + for (i = k = 0; newKeys[i] && k < KT_numKeys; i++,k++) { + if (newKeys[i] == '^' && newKeys[i+1]) + keyTable[k] = toupper(newKeys[++i]) - ('A' - 1); + else + keyTable[k] = newKeys[i]; + } + memset(used, 0, sizeof(used)); + for (k = 0; k < KT_numKeys; k++) { + ch = (unsigned char) keyTable[k]; + if (used[ch]) { + if (iscntrl(ch) && ch < ' ') + sprintf(scratch, "Ctrl-%c", ch + ('A' - 1)); + else if (isprint(ch)) + sprintf(scratch, "\"%c\"", ch); + else + sprintf(scratch, "0x%X", ch); + if (!errs) + fprintf(stderr, "Duplicate key mappings:\n"); + errs++; + fprintf(stderr, " %s mapped to both %s and %s\n", + scratch, keyNames[used[ch]-1], keyNames[k]); + } + used[ch] = k + 1; + } + if (errs) + exit(1); +} + +ExtFunc int StartNewPiece(int scr, Shape *shape) +{ + curShape[scr] = shape; + curY[scr] = boardVisible[scr] + 4; + curX[scr] = boardWidth[scr] / 2; + while (!ShapeVisible(shape, scr, curY[scr], curX[scr])) + --curY[scr]; + if (!ShapeFits(shape, scr, curY[scr], curX[scr])) + return 0; + PlotShape(shape, scr, curY[scr], curX[scr], 1); + return 1; +} + +ExtFunc void OneGame(int scr, int scr2) +{ + MyEvent event; + int linesCleared, changed = 0; + int spied = 0, spying = 0, dropMode = 0; + int oldPaused = 0, paused = 0, pausedByMe = 0, pausedByThem = 0; + long pauseTimeLeft; + int pieceCount = 0; + int key; + char *p, *cmd; + + speed = stepDownInterval; + ResetBaseTime(); + InitBoard(scr); + if (scr2 >= 0) { + spied = 1; + spying = 1; + InitBoard(scr2); + UpdateOpponentDisplay(); + } + ShowDisplayInfo(); + SetITimer(speed, speed); + if (robotEnable) { + RobotCmd(0, "GameType %s\n", gameNames[game]); + RobotCmd(0, "BoardSize 0 %d %d\n", + boardVisible[scr], boardWidth[scr]); + if (scr2 >= 0) { + RobotCmd(0, "BoardSize 1 %d %d\n", + boardVisible[scr2], boardWidth[scr2]); + RobotCmd(0, "Opponent 1 %s %s\n", opponentName, opponentHost); + if (opponentFlags & SCF_usingRobot) + RobotCmd(0, "OpponentFlag 1 robot\n"); + if (opponentFlags & SCF_fairRobot) + RobotCmd(0, "OpponentFlag 1 fairRobot\n"); + } + RobotCmd(0, "TickLength %.3f\n", speed / 1.0e6); + RobotCmd(0, "BeginGame\n"); + RobotTimeStamp(); + } + while (StartNewPiece(scr, ChooseOption(stdOptions))) { + if (robotEnable && !fairRobot) + RobotCmd(1, "NewPiece %d\n", ++pieceCount); + if (spied) { + short shapeNum; + netint2 data[1]; + + shapeNum = ShapeToNetNum(curShape[scr]); + data[0] = hton2(shapeNum); + SendPacket(NP_newPiece, sizeof(data), data); + } + for (;;) { + changed = RefreshBoard(scr) || changed; + if (spying) + changed = RefreshBoard(scr2) || changed; + if (changed) { + RefreshScreen(); + changed = 0; + } + CheckNetConn(); + switch (WaitMyEvent(&event, EM_any)) { + case E_alarm: + if (!MovePiece(scr, -1, 0)) + goto nextPiece; + else if (spied) + SendPacket(NP_down, 0, NULL); + break; + case E_key: + p = strchr(keyTable, tolower(event.u.key)); + key = p - keyTable; + if (robotEnable) { + RobotCmd(1, "UserKey %d %s\n", + (int)(unsigned char)event.u.key, + p ? keyNames[key] : "?"); + break; + } + if (!p) + break; + keyEvent: + if (paused && (key != KT_pause) && (key != KT_redraw)) + break; + switch(key) { + case KT_left: + if (MovePiece(scr, 0, -1) && spied) + SendPacket(NP_left, 0, NULL); + break; + case KT_right: + if (MovePiece(scr, 0, 1) && spied) + SendPacket(NP_right, 0, NULL); + break; + case KT_rotate: + if (RotatePiece(scr) && spied) + SendPacket(NP_rotate, 0, NULL); + break; + case KT_down: + if (MovePiece(scr, -1, 0) && spied) + SendPacket(NP_down, 0, NULL); + break; + case KT_toggleSpy: + spying = (!spying) && (scr2 >= 0); + break; + case KT_drop: + if (DropPiece(scr) > 0) { + if (spied) + SendPacket(NP_drop, 0, NULL); + SetITimer(speed, speed); + } + dropMode = dropModeEnable; + break; + case KT_pause: + pausedByMe = !pausedByMe; + if (game == GT_classicTwo) { + netint2 data[1]; + + data[0] = hton2(pausedByMe); + SendPacket(NP_pause, sizeof(data), data); + } + paused = pausedByMe || pausedByThem; + if (robotEnable) + RobotCmd(1, "Pause %d %d\n", pausedByMe, + pausedByThem); + ShowPause(pausedByMe, pausedByThem); + changed = 1; + break; + case KT_faster: + if (game != GT_onePlayer) + break; + speed = speed * 0.8; + SetITimer(speed, SetITimer(0, 0)); + ShowDisplayInfo(); + changed = 1; + break; + case KT_redraw: + ScheduleFullRedraw(); + if (paused) + RefreshScreen(); + break; + } + if (dropMode && DropPiece(scr) > 0) { + if (spied) + SendPacket(NP_drop, 0, NULL); + SetITimer(speed, speed); + } + break; + case E_robot: + { + int num; + + cmd = event.u.robot.data; + if ((p = strchr(cmd, ' '))) + *p++ = 0; + else + p = cmd + strlen(cmd); + for (key = 0; keyNames[key]; ++key) + if (!strcmp(keyNames[key], cmd) && + (fairRobot || (1 == sscanf(p, "%d", &num) && + num == pieceCount))) + goto keyEvent; + if (!strcmp(cmd, "Message")) { + Message(p); + changed = 1; + } + break; + } + case E_net: + switch(event.u.net.type) { + case NP_giveJunk: + { + netint2 data[2]; + short column; + + memcpy(data, event.u.net.data, sizeof(data[0])); + column = Random(0, boardWidth[scr]); + data[1] = hton2(column); + InsertJunk(scr, ntoh2(data[0]), column); + if (spied) + SendPacket(NP_insertJunk, sizeof(data), data); + break; + } + case NP_newPiece: + { + short shapeNum; + netint2 data[1]; + + FreezePiece(scr2); + memcpy(data, event.u.net.data, sizeof(data)); + shapeNum = ntoh2(data[0]); + StartNewPiece(scr2, NetNumToShape(shapeNum)); + break; + } + case NP_down: + MovePiece(scr2, -1, 0); + break; + case NP_left: + MovePiece(scr2, 0, -1); + break; + case NP_right: + MovePiece(scr2, 0, 1); + break; + case NP_rotate: + RotatePiece(scr2); + break; + case NP_drop: + DropPiece(scr2); + break; + case NP_clear: + ClearFullLines(scr2); + break; + case NP_insertJunk: + { + netint2 data[2]; + + memcpy(data, event.u.net.data, sizeof(data)); + InsertJunk(scr2, ntoh2(data[0]), ntoh2(data[1])); + break; + } + case NP_pause: + { + netint2 data[1]; + + memcpy(data, event.u.net.data, sizeof(data)); + pausedByThem = ntoh2(data[0]); + paused = pausedByMe || pausedByThem; + if (robotEnable) + RobotCmd(1, "Pause %d %d\n", pausedByMe, + pausedByThem); + ShowPause(pausedByMe, pausedByThem); + changed = 1; + break; + } + default: + break; + } + break; + case E_lostRobot: + case E_lostConn: + goto gameOver; + default: + break; + } + if (paused != oldPaused) { + if (paused) + pauseTimeLeft = SetITimer(0, 0); + else + SetITimer(speed, pauseTimeLeft); + oldPaused = paused; + } + } + nextPiece: + dropMode = 0; + FreezePiece(scr); + linesCleared = ClearFullLines(scr); + if (linesCleared > 0 && spied) + SendPacket(NP_clear, 0, NULL); + if (game == GT_classicTwo && linesCleared > 1) { + short junkLines; + netint2 data[1]; + + junkLines = linesCleared - (linesCleared < 4); + data[0] = hton2(junkLines); + SendPacket(NP_giveJunk, sizeof(data), data); + } + } +gameOver: + SetITimer(0, 0); +} + +ExtFunc int main(int argc, char **argv) +{ + int initConn = 0, waitConn = 0, ch; + char *hostStr = NULL, *portStr = NULL; + + standoutEnable = 1; + stepDownInterval = DEFAULT_INTERVAL; + MapKeys(DEFAULT_KEYS); + while ((ch = getopt(argc, argv, "hHRs:r:Fk:c:woDSp:i:")) != -1) + switch (ch) { + case 'c': + initConn = 1; + hostStr = optarg; + break; + case 'w': + waitConn = 1; + break; + case 'p': + portStr = optarg; + break; + case 'i': + stepDownInterval = atof(optarg) * 1e6; + break; + case 's': + initSeed = atoi(optarg); + myFlags |= SCF_setSeed; + break; + case 'r': + robotEnable = 1; + robotProg = optarg; + myFlags |= SCF_usingRobot; + break; + case 'F': + fairRobot = 1; + myFlags |= SCF_fairRobot; + break; + case 'D': + dropModeEnable = 1; + break; + case 'S': + standoutEnable = 0; + break; + case 'k': + MapKeys(optarg); + break; + case 'H': + DistInfo(); + exit(0); + case 'R': + Rules(); + exit(0); + case 'h': + Usage(); + exit(0); + default: + Usage(); + exit(1); + } + if (optind < argc || (initConn && waitConn)) { + Usage(); + exit(1); + } + if (fairRobot && !robotEnable) + fatal("You can't use the -F option without the -r option"); + InitUtil(); + if (robotEnable) + InitRobot(robotProg); + InitNet(); + InitScreens(); + if (initConn || waitConn) { + MyEvent event; + + game = GT_classicTwo; + if (initConn) + InitiateConnection(hostStr, portStr); + else if (waitConn) + WaitForConnection(portStr); + { + netint4 data[2]; + int major; + + data[0] = hton4(MAJOR_VERSION); + data[1] = hton4(PROTOCOL_VERSION); + SendPacket(NP_version, sizeof(data), data); + if (WaitMyEvent(&event, EM_net) != E_net) + fatal("Network negotiation failed"); + memcpy(data, event.u.net.data, sizeof(data)); + major = ntoh4(data[0]); + protocolVersion = ntoh4(data[1]); + if (event.u.net.type != NP_version || major < MAJOR_VERSION) + fatal("Your opponent is using an old, incompatible version\n" + "of Netris. They should get the latest version."); + if (major > MAJOR_VERSION) + fatal("Your opponent is using an newer, incompatible version\n" + "of Netris. Get the latest version."); + if (protocolVersion > PROTOCOL_VERSION) + protocolVersion = PROTOCOL_VERSION; + } + if (protocolVersion < 3 && stepDownInterval != DEFAULT_INTERVAL) + fatal("Your opponent's version of Netris predates the -i option.\n" + "For fairness, you shouldn't use the -i option either."); + { + netint4 data[3]; + int len; + int seed; + + if (protocolVersion >= 3) + len = sizeof(data); + else + len = sizeof(netint4[2]); + if ((myFlags & SCF_setSeed)) + seed = initSeed; + else + seed = time(0); + if (waitConn) + SRandom(seed); + data[0] = hton4(myFlags); + data[1] = hton4(seed); + data[2] = hton4(stepDownInterval); + SendPacket(NP_startConn, len, data); + if (WaitMyEvent(&event, EM_net) != E_net || + event.u.net.type != NP_startConn) + fatal("Network negotiation failed"); + memcpy(data, event.u.net.data, len); + opponentFlags = ntoh4(data[0]); + seed = ntoh4(data[1]); + if (initConn) { + if ((opponentFlags & SCF_setSeed) != (myFlags & SCF_setSeed)) + fatal("If one player sets the random number seed, " + "both must."); + if ((myFlags & SCF_setSeed) && seed != initSeed) + fatal("Both players have set the random number seed, " + "and they are unequal."); + if (protocolVersion >= 3 && stepDownInterval != ntoh4(data[2])) + fatal("Your opponent is using a different step-down " + "interval (-i).\nYou must both use the same one."); + SRandom(seed); + } + } + { + char *userName; + int len, i; + + userName = getenv("LOGNAME"); + if (!userName || !userName[0]) + userName = getenv("USER"); + if (!userName || !userName[0]) + strcpy(userName, "???"); + len = strlen(userName)+1; + if (len > sizeof(opponentName)) + len = sizeof(opponentName); + SendPacket(NP_userName, len, userName); + if (WaitMyEvent(&event, EM_net) != E_net || + event.u.net.type != NP_userName) + fatal("Network negotiation failed"); + strncpy(opponentName, event.u.net.data, + sizeof(opponentName)-1); + opponentName[sizeof(opponentName)-1] = 0; + for (i = 0; opponentName[i]; ++i) + if (!isprint(opponentName[i])) + opponentName[i] = '?'; + for (i = 0; opponentHost[i]; ++i) + if (!isprint(opponentHost[i])) + opponentHost[i] = '?'; + } + OneGame(0, 1); + } + else { + game = GT_onePlayer; + OneGame(0, -1); + } + return 0; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/inet.c b/inet.c new file mode 100644 index 0000000..dbfe748 --- /dev/null +++ b/inet.c @@ -0,0 +1,216 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: inet.c,v 1.18 1996/02/09 08:22:13 mhw Exp $ + */ + +#include "netris.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define HEADER_SIZE sizeof(netint2[2]) + +static MyEventType NetGenFunc(EventGenRec *gen, MyEvent *event); + +static int sock = -1; +static EventGenRec netGen = { NULL, 0, FT_read, -1, NetGenFunc, EM_net }; + +static char netBuf[64]; +static int netBufSize, netBufGoal = HEADER_SIZE; +static int isServer, lostConn, gotEndConn; + +ExtFunc void InitNet(void) +{ + AtExit(CloseNet); +} + +ExtFunc int WaitForConnection(char *portStr) +{ + struct sockaddr_in addr; + struct hostent *host; + int sockListen; + int addrLen; + short port; + int val1; + struct linger val2; + + if (portStr) + port = atoi(portStr); /* XXX Error checking */ + else + port = DEFAULT_PORT; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + sockListen = socket(AF_INET, SOCK_STREAM, 0); + if (sockListen < 0) + die("socket"); + val1 = 1; + setsockopt(sockListen, SOL_SOCKET, SO_REUSEADDR, + (void *)&val1, sizeof(val1)); + if (bind(sockListen, (struct sockaddr *)&addr, sizeof(addr)) < 0) + die("bind"); + if (listen(sockListen, 1) < 0) + die("listen"); + addrLen = sizeof(addr); + sock = accept(sockListen, (struct sockaddr *)&addr, &addrLen); + if (sock < 0) + die("accept"); + close(sockListen); + val2.l_onoff = 1; + val2.l_linger = 0; + setsockopt(sock, SOL_SOCKET, SO_LINGER, + (void *)&val2, sizeof(val2)); + netGen.fd = sock; + strcpy(opponentHost, "???"); + if (addr.sin_family == AF_INET) { + host = gethostbyaddr((void *)&addr.sin_addr, + sizeof(struct in_addr), AF_INET); + if (host) { + strncpy(opponentHost, host->h_name, sizeof(opponentHost)-1); + opponentHost[sizeof(opponentHost)-1] = 0; + } + } + AddEventGen(&netGen); + isServer = 1; + return 0; +} + +ExtFunc int InitiateConnection(char *hostStr, char *portStr) +{ + struct sockaddr_in addr; + struct hostent *host; + short port; + int mySock; + + if (portStr) + port = atoi(portStr); /* XXX Error checking */ + else + port = DEFAULT_PORT; + host = gethostbyname(hostStr); + if (!host) + die("gethostbyname"); + assert(host->h_addrtype == AF_INET); + strncpy(opponentHost, host->h_name, sizeof(opponentHost)-1); + opponentHost[sizeof(opponentHost)-1] = 0; + again: + memset(&addr, 0, sizeof(addr)); + addr.sin_family = host->h_addrtype; + memcpy(&addr.sin_addr, host->h_addr, host->h_length); + addr.sin_port = htons(port); + mySock = socket(AF_INET, SOCK_STREAM, 0); + if (mySock < 0) + die("socket"); + if (connect(mySock, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + if (errno != ECONNREFUSED) + die("connect"); + close(mySock); + sleep(1); + goto again; + } + netGen.fd = sock = mySock; + AddEventGen(&netGen); + return 0; +} + +static MyEventType NetGenFunc(EventGenRec *gen, MyEvent *event) +{ + int result; + short type, size; + netint2 data[2]; + + result = MyRead(sock, netBuf + netBufSize, netBufGoal - netBufSize); + if (result < 0) { + lostConn = 1; + return E_lostConn; + } + netBufSize += result; + if (netBufSize < netBufGoal) + return E_none; + memcpy(data, netBuf, sizeof(data)); + type = ntoh2(data[0]); + size = ntoh2(data[1]); + netBufGoal = size; + if (netBufSize < netBufGoal) + return E_none; + netBufSize = 0; + netBufGoal = HEADER_SIZE; + event->u.net.type = type; + event->u.net.size = size - HEADER_SIZE; + event->u.net.data = netBuf + HEADER_SIZE; + if (type == NP_endConn) { + gotEndConn = 1; + return E_lostConn; + } + else if (type == NP_byeBye) { + lostConn = 1; + return E_lostConn; + } + return E_net; +} + +ExtFunc void CheckNetConn(void) +{ +} + +ExtFunc void SendPacket(NetPacketType type, int size, void *data) +{ + netint2 header[2]; + + header[0] = hton2(type); + header[1] = hton2(size + HEADER_SIZE); + if (MyWrite(sock, header, HEADER_SIZE) != HEADER_SIZE) + die("write"); + if (size > 0 && data && MyWrite(sock, data, size) != size) + die("write"); +} + +ExtFunc void CloseNet(void) +{ + MyEvent event; + + if (sock >= 0) { + if (!lostConn) { + SendPacket(NP_endConn, 0, NULL); + if (isServer) { + while (!lostConn) + WaitMyEvent(&event, EM_net); + } + else { + while (!gotEndConn) + WaitMyEvent(&event, EM_net); + SendPacket(NP_byeBye, 0, NULL); + } + } + close(sock); + sock = -1; + } + if (netGen.next) + RemoveEventGen(&netGen); +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/netris.h b/netris.h new file mode 100644 index 0000000..5864cf7 --- /dev/null +++ b/netris.h @@ -0,0 +1,180 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: netris.h,v 1.27 1996/02/09 08:22:14 mhw Exp $ + */ + +#ifndef NETRIS_H +#define NETRIS_H + +#include "config.h" +#include +#include +#include +#include + +#define ExtFunc /* Marks functions that need prototypes */ + +#ifdef NOEXT +# define EXT +# define IN(a) a +#else +# define EXT extern +# define IN(a) +#endif + +#ifndef NULL +# define NULL ((void *)0) +#endif + +#ifdef HAS_SIGPROCMASK +typedef sigset_t MySigSet; +#else +typedef int MySigSet; +#endif + +/* + * The following definitions are to ensure network compatibility even if + * the sizes of ints and shorts are different. I'm not sure exactly how + * to deal with this problem, so I've added an abstraction layer. + */ + +typedef short netint2; +typedef long netint4; + +#define hton2(x) htons(x) +#define hton4(x) htonl(x) + +#define ntoh2(x) ntohs(x) +#define ntoh4(x) ntohl(x) + +#define DEFAULT_PORT 9284 /* Very arbitrary */ + +#define DEFAULT_KEYS "jkl mspf^l" + +/* Protocol versions */ +#define MAJOR_VERSION 1 +#define PROTOCOL_VERSION 3 +#define ROBOT_VERSION 1 + +#define MAX_BOARD_WIDTH 32 +#define MAX_BOARD_HEIGHT 64 +#define MAX_SCREENS 2 + +#define DEFAULT_INTERVAL 300000 /* Step-down interval in microseconds */ + +/* NP_startConn flags */ +#define SCF_usingRobot 000001 +#define SCF_fairRobot 000002 +#define SCF_setSeed 000004 + +/* Event masks */ +#define EM_alarm 000001 +#define EM_key 000002 +#define EM_net 000004 +#define EM_robot 000010 +#define EM_any 000777 + +typedef enum _GameType { GT_onePlayer, GT_classicTwo, GT_len } GameType; +typedef enum _BlockTypeA { BT_none, BT_piece1, BT_wall, BT_len } BlockTypeA; +typedef enum _Dir { D_down, D_right, D_up, D_left } Dir; +typedef enum _Cmd { C_end, C_forw, C_back, C_left, C_right, C_plot } Cmd; +typedef enum _FDType { FT_read, FT_write, FT_except, FT_len } FDType; +typedef enum _MyEventType { E_none, E_alarm, E_key, E_net, + E_lostConn, E_robot, E_lostRobot } MyEventType; +typedef enum _NetPacketType { NP_endConn, NP_giveJunk, NP_newPiece, + NP_down, NP_left, NP_right, + NP_rotate, NP_drop, NP_clear, + NP_insertJunk, NP_startConn, + NP_userName, NP_pause, NP_version, + NP_byeBye } NetPacketType; + +typedef signed char BlockType; + +typedef struct _MyEvent { + MyEventType type; + union { + char key; + struct { + NetPacketType type; + int size; + void *data; + } net; + struct { + int size; + char *data; + } robot; + } u; +} MyEvent; + +struct _EventGenRec; +typedef MyEventType (*EventGenFunc)(struct _EventGenRec *gen, MyEvent *event); + +typedef struct _EventGenRec { + struct _EventGenRec *next; + int ready; + FDType fdType; + int fd; + EventGenFunc func; + int mask; +} EventGenRec; + +typedef struct _Shape { + struct _Shape *rotateTo; + int initY, initX, mirrored; + Dir initDir; + BlockType type; + Cmd *cmds; +} Shape; + +typedef struct _ShapeOption { + float weight; + Shape *shape; +} ShapeOption; + +typedef int (*ShapeDrawFunc)(int scr, int y, int x, + BlockType type, void *data); + +EXT GameType game; +EXT int boardHeight[MAX_SCREENS]; +EXT int boardVisible[MAX_SCREENS], boardWidth[MAX_SCREENS]; +EXT Shape *curShape[MAX_SCREENS]; +EXT int curY[MAX_SCREENS], curX[MAX_SCREENS]; +EXT char opponentName[16], opponentHost[256]; +EXT int standoutEnable; +EXT int robotEnable, robotVersion, fairRobot; +EXT int protocolVersion; + +EXT long initSeed; +EXT long stepDownInterval, speed; + +EXT int myFlags, opponentFlags; + +EXT char scratch[1024]; + +extern ShapeOption stdOptions[]; +extern char *version_string; + +#include "proto.h" + +#endif /* NETRIS_H */ + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/robot.c b/robot.c new file mode 100644 index 0000000..23875a3 --- /dev/null +++ b/robot.c @@ -0,0 +1,174 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: robot.c,v 1.8 1996/02/09 08:22:15 mhw Exp $ + */ + +#include "netris.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static MyEventType RobotGenFunc(EventGenRec *gen, MyEvent *event); + +static EventGenRec robotGen = + { NULL, 0, FT_read, -1, RobotGenFunc, EM_robot }; + +static int robotProcess; +static FILE *toRobot; +static int toRobotFd, fromRobotFd; + +static char robotBuf[128]; +static int robotBufSize, robotBufMsg; + +static int gotSigPipe; + +ExtFunc void InitRobot(char *robotProg) +{ + int to[2], from[2]; + int status; + MyEvent event; + + signal(SIGPIPE, CatchPipe); + AtExit(CloseRobot); + if (pipe(to) || pipe(from)) + die("pipe"); + robotProcess = fork(); + if (robotProcess < 0) + die("fork"); + if (robotProcess == 0) { + dup2(to[0], STDIN_FILENO); + dup2(from[1], STDOUT_FILENO); + close(to[0]); + close(to[1]); + close(from[0]); + close(from[1]); + execl("/bin/sh", "sh", "-c", robotProg, NULL); + die("execl failed"); + } + close(to[0]); + close(from[1]); + toRobotFd = to[1]; + robotGen.fd = fromRobotFd = from[0]; + if (!(toRobot = fdopen(toRobotFd, "w"))) + die("fdopen"); + if ((status = fcntl(fromRobotFd, F_GETFL, 0)) < 0) + die("fcntl/F_GETFL"); + status |= O_NONBLOCK; + if (fcntl(fromRobotFd, F_SETFL, status) < 0) + die("fcntl/F_SETFL"); + AddEventGen(&robotGen); + RobotCmd(1, "Version %d\n", ROBOT_VERSION); + if (WaitMyEvent(&event, EM_robot) != E_robot) + fatal("Robot didn't start successfully"); + if (1 > sscanf(event.u.robot.data, "Version %d", &robotVersion) + || robotVersion < 1) + fatal("Invalid Version line from robot"); + if (robotVersion > ROBOT_VERSION) + robotVersion = ROBOT_VERSION; +} + +ExtFunc void CatchPipe(int sig) +{ + robotGen.ready = gotSigPipe = 1; +} + +ExtFunc void RobotCmd(int flush, char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + vfprintf(toRobot, fmt, args); + va_end(args); + if (flush) + fflush(toRobot); +} + +ExtFunc void RobotTimeStamp(void) +{ + RobotCmd(1, "TimeStamp %.3f\n", CurTimeval() / 1.0e6); +} + +ExtFunc void CloseRobot(void) +{ + RemoveEventGen(&robotGen); + if (robotProcess > 0) + RobotCmd(1, "Exit\n"); + fclose(toRobot); + close(fromRobotFd); +} + +static MyEventType RobotGenFunc(EventGenRec *gen, MyEvent *event) +{ + static int more; + int result, i; + char *p; + + if (gotSigPipe) { + gotSigPipe = 0; + robotGen.ready = more; + return E_lostRobot; + } + if (robotBufMsg > 0) { + /* + * Grrrrrr! SunOS 4.1 doesn't have memmove (or atexit) + * I'm told some others have a broken memmove + * + * memmove(robotBuf, robotBuf + robotBufMsg, + * robotBufSize - robotBufMsg); + */ + for (i = robotBufMsg; i < robotBufSize; ++i) + robotBuf[i - robotBufMsg] = robotBuf[i]; + + robotBufSize -= robotBufMsg; + robotBufMsg = 0; + } + if (more) + more = 0; + else { + do { + result = read(fromRobotFd, robotBuf + robotBufSize, + sizeof(robotBuf) - robotBufSize); + } while (result < 0 && errno == EINTR); + if (result <= 0) + return E_lostRobot; + robotBufSize += result; + } + if (!(p = memchr(robotBuf, '\n', robotBufSize))) { + if (robotBufSize >= sizeof(robotBuf)) + fatal("Line from robot is too long"); + return E_none; + } + *p = 0; + robotBufMsg = p - robotBuf + 1; + robotGen.ready = more = (memchr(robotBuf + robotBufMsg, '\n', + robotBufSize - robotBufMsg) != NULL); + event->u.robot.size = p - robotBuf; + event->u.robot.data = robotBuf; + return E_robot; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/robot_desc b/robot_desc new file mode 100644 index 0000000..01c6970 --- /dev/null +++ b/robot_desc @@ -0,0 +1,225 @@ +$Id: robot_desc,v 1.3 1996/02/09 08:22:16 mhw Exp $ + + +GENERAL PROTOCOL +================ +When you pass the "-r " option to Netris, it launches +"/bin/sh -c " as a subprocess and communicates to it via pipes. + +The robot reads events from Netris through stdin, and writes commands to +stdout. All exchanges are ASCII lines composed of tokens separated by +spaces. The first token in a line is the name of the command. + +The robot should ignore commands which it doesn't recognize. The protocol +may be extended by adding extra commands without incrementing the version +number. The version number will only be increased when the command cannot +be safely ignored. + +Netris will also ignore commands which it doesn't recognize, for the same +reason. + + +INITIALIZATION +============== +The initial exchange between Netris and the robot is Version negotiation. +Each sends a "Version " line to the other, and the lowest version is +used. Currently, the robot protocol version is 1. + +Next, Netris sends "GameType ", there is either OnePlayer +or ClassicTwo. There may be other games in the future. + +Then Netris sends "BoardSize " for each player. + is 0 for the computer player and 1 for the opponent, if any. +Other numbers may be used in the future. + +For each opponent, Netris sends "Opponent ". + is not necessarily a fully qualified host name, unfortunately. +It might even be something like "localhost". + +For each opponent, Netris may send 0 or more flags associated with the +opponent. For each flag which is true, Netris sends +"OpponentFlag ". Currently, the flags are "robot" and +"fairRobot". + +Next, Netris sends "TickLength ", where is the number +of seconds between pieces stepping down. + +Finally, a "BeginGame" command is sent to the robot. + + +NORMAL GAME (Netris --> robot) +============================== +Here's a list of commands sent from Netris to the robot, and a brief +description of each one. + +Exit +---- +This is always sent to the robot when the game is over, for any reason. +The robot should exit immediately. + +NewPiece +-------------- +This command is never sent in "fair" robot mode. + +Everytime a new piece is created, this command is sent to the robot. + is a positive integer uniquely identifying the piece. should +be sent as a parameter to each movement command (sent to Netris) in +order to prevent accidental movement of the wrong piece. + +TimeStamp +------------------- +This command may be sent to the robot at any time. is +a floating point number of seconds since the beginning of the game. +One is always sent immediately after "BeginGame" and "RowUpdate" commands. +One will be sent at least every tick (see "TickLength") unless the game +is paused. + +RowUpdate ... +-------------------------------------------------------- +Whenever the screen changes, a group of these commands is sent to the +robot followed by a "TimeStamp". After a "RowUpdate" command, the robot's +view of the board is incorrect until the "TimeStamp". + + is 0 for the computer player and positive numbers for opponents. + is an integer from 0 to boardHeight-1. 0 is the bottom row. + + ... are integers separated by spaces, one for each column. +"0" indicates an empty square. Positive integers indicates blocks. +Currently only "1" is used, but in the future there may be special kinds of +blocks indicated by higher numbers. Negative integers indicate part of +the currently falling piece. For each block, the absolute value of the +number indicates the type of piece, and the sign indicates whether it is +currently falling. + +If Netris is in "fair robot" (-F) mode, Netris will not highlight the +falling piece with negative numbers. In this case, all numbers will +be non-negative. + +UserKey +----------------------- +Whenever the user presses a key, this command is sent to the robot. The +key is not automatically interpreted by Netris. + + is the ascii value of the key, from 0 to 255. is one +of the following strings: "Left", "Rotate", "Right", "Drop", "Down", +"ToggleSpy", "Pause", or "?". When possible, you should use , +since it will pay attention to keyboard remappings. + +For each possible (other than "?"), there's an equivalent +command recognized by Netris from the robot which performs the equivalent +of the key. Therefore, if you want give the user "manual" control, you +can send Netris " ". + +Pause +--------------------------------- + is 0 or 1, indicating whether the game is currently paused by +our side. is 0 or 1, indicating whether any player other +than our side has paused the game. If either is 1, the game is paused. + +You may actually receive this command even when none of the flags change. + + +NORMAL GAME (robot --> Netris) +============================== +Here's a list of commands recognized by Netris from the robot: + +Left +Rotate +Right +Drop +Down +ToggleSpy +Pause +--------------- +These commands perform the equivalent of typing the corresponding command +on the keyboard, if you weren't in robot mode. + + is checked against the current piece number (the value sent along +with "NewPiece" commands). If it doesn't match, the command is ignored. +However, in "fair robot" mode, the is ignored. + +These commands (except "Pause") are also ignored if the game is paused. + +Message +----------------- + is printed on the messages part of the display. Messages too +long to fit on the screen may be truncated. + + may contain spaces and printable characters only. + + +EXAMPLE +======= +Here's a portion of an example log generated by the sample robot. The +sample robot generates a log file in "log" if the "-l" is given to sr +(eg "netris -r 'sr -l'"). + +In this log file, every line is preceeded by two characters. Lines +sent from Netris to the robot are preceeded by two spaces " ", and +lines sent to Netris are preceeded by "> ". + +> Version 1 + Version 1 + GameType OnePlayer + BoardSize 0 20 10 + TickLength 0.300 + BeginGame + TimeStamp 0.009 + NewPiece 1 + RowUpdate 0 19 0 0 0 0 0 -1 -1 0 0 0 + TimeStamp 0.010 + RowUpdate 0 19 0 0 0 0 -1 -1 0 0 0 0 + RowUpdate 0 18 0 0 0 0 0 -1 -1 0 0 0 + TimeStamp 0.314 +> Message Goal 0 : ** :** : : : +> Left 1 + TimeStamp 0.352 + RowUpdate 0 19 0 0 0 -1 -1 0 0 0 0 0 + RowUpdate 0 18 0 0 0 0 -1 -1 0 0 0 0 + TimeStamp 0.362 +> Left 1 + RowUpdate 0 19 0 0 -1 -1 0 0 0 0 0 0 + RowUpdate 0 18 0 0 0 -1 -1 0 0 0 0 0 + TimeStamp 0.375 +> Left 1 + RowUpdate 0 19 0 -1 -1 0 0 0 0 0 0 0 + RowUpdate 0 18 0 0 -1 -1 0 0 0 0 0 0 + TimeStamp 0.387 +> Left 1 + RowUpdate 0 19 -1 -1 0 0 0 0 0 0 0 0 + RowUpdate 0 18 0 -1 -1 0 0 0 0 0 0 0 + TimeStamp 0.399 +> Drop 1 + RowUpdate 0 19 0 0 0 0 0 0 0 0 0 0 + RowUpdate 0 18 0 0 0 0 0 0 0 0 0 0 + RowUpdate 0 1 -1 -1 0 0 0 0 0 0 0 0 + RowUpdate 0 0 0 -1 -1 0 0 0 0 0 0 0 + TimeStamp 0.413 + NewPiece 2 + RowUpdate 0 19 0 0 0 0 0 -1 0 0 0 0 + RowUpdate 0 1 1 1 0 0 0 0 0 0 0 0 + RowUpdate 0 0 0 1 1 0 0 0 0 0 0 0 + TimeStamp 0.715 + RowUpdate 0 19 0 0 0 0 -1 -1 -1 0 0 0 + RowUpdate 0 18 0 0 0 0 0 -1 0 0 0 0 + TimeStamp 1.014 +> Message Goal 3 :*** : * : : : +> Rotate 2 + TimeStamp 1.053 + RowUpdate 0 19 0 0 0 0 0 -1 -1 0 0 0 + RowUpdate 0 18 0 0 0 0 0 -1 0 0 0 0 + TimeStamp 1.062 + RowUpdate 0 19 0 0 0 0 0 -1 0 0 0 0 + RowUpdate 0 18 0 0 0 0 0 -1 -1 0 0 0 + RowUpdate 0 17 0 0 0 0 0 -1 0 0 0 0 + TimeStamp 1.314 +> Rotate 2 + RowUpdate 0 19 0 0 0 0 0 -1 0 0 0 0 + RowUpdate 0 18 0 0 0 0 -1 -1 -1 0 0 0 + RowUpdate 0 17 0 0 0 0 0 0 0 0 0 0 + TimeStamp 1.326 +[...] + Exit + + +# vi: tw=70 ai diff --git a/shapes.c b/shapes.c new file mode 100644 index 0000000..c62f69b --- /dev/null +++ b/shapes.c @@ -0,0 +1,191 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: shapes.c,v 1.15 1996/02/09 08:22:17 mhw Exp $ + */ + +#include "netris.h" +#include + +#define ShapeName(name, dir) \ + shape_ ## name ## _ ## dir + +#define PreDecl(name, dir) \ + static Shape ShapeName(name, dir) + +#define StdShape(name, cmdName, mirror, type, realDir, dir, nextDir) \ + static Shape ShapeName(name, dir) = { &ShapeName(name, nextDir), \ + 0, 0, mirror, D_ ## realDir, type, cmds_ ## cmdName } + +#define FourWayDecl(name, cmdName, mirror, type) \ + PreDecl(name, down); \ + StdShape(name, cmdName, mirror, type, left, left, down); \ + StdShape(name, cmdName, mirror, type, up, up, left); \ + StdShape(name, cmdName, mirror, type, right, right, up); \ + StdShape(name, cmdName, mirror, type, down, down, right) + +#define TwoWayDecl(name, cmdName, mirror, type) \ + PreDecl(name, vert); \ + StdShape(name, cmdName, mirror, type, right, horiz, vert); \ + StdShape(name, cmdName, mirror, type, down, vert, horiz) + +static Cmd cmds_long[] = { C_back, C_plot, C_forw, C_plot, C_forw, C_plot, + C_forw, C_plot, C_end }; +TwoWayDecl(long, long, 0, BT_piece1); + +static Cmd cmds_square[] = { C_plot, C_forw, C_left, C_plot, C_forw, C_left, + C_plot, C_forw, C_left, C_plot, C_end }; +static Shape shape_square = { &shape_square, 0, 0, D_up, 0, BT_piece1, + cmds_square }; + +static Cmd cmds_l[] = { C_right, C_back, C_plot, C_forw, C_plot, C_forw, + C_plot, C_left, C_forw, C_plot, C_end }; +FourWayDecl(l, l, 0, BT_piece1); +FourWayDecl(l1, l, 1, BT_piece1); + +static Cmd cmds_t[] = { C_plot, C_forw, C_plot, C_back, C_right, C_forw, + C_plot, C_back, C_back, C_plot, C_end }; +FourWayDecl(t, t, 0, BT_piece1); + +static Cmd cmds_s[] = { C_back, C_plot, C_forw, C_plot, C_left, C_forw, + C_plot, C_right, C_forw, C_plot, C_end }; +TwoWayDecl(s, s, 0, BT_piece1); +TwoWayDecl(s1, s, 1, BT_piece1); + +ShapeOption stdOptions[] = { + {1, &shape_long_horiz}, + {1, &shape_square}, + {1, &shape_l_down}, + {1, &shape_l1_down}, + {1, &shape_t_down}, + {1, &shape_s_horiz}, + {1, &shape_s1_horiz}, + {0, NULL}}; + +Shape *netMapping[] = { + &shape_long_horiz, + &shape_long_vert, + &shape_square, + &shape_l_down, + &shape_l_right, + &shape_l_up, + &shape_l_left, + &shape_l1_down, + &shape_l1_right, + &shape_l1_up, + &shape_l1_left, + &shape_t_down, + &shape_t_right, + &shape_t_up, + &shape_t_left, + &shape_s_horiz, + &shape_s_vert, + &shape_s1_horiz, + &shape_s1_vert, + NULL}; + +ExtFunc void MoveInDir(Dir dir, int dist, int *y, int *x) +{ + switch (dir) { + case D_down: *y -= dist; break; + case D_right: *x += dist; break; + case D_up: *y += dist; break; + case D_left: *x -= dist; break; + default: + assert(0); + } +} + +ExtFunc Dir RotateDir(Dir dir, int delta) +{ + return 3 & (dir + delta); +} + +ExtFunc int ShapeIterate(Shape *s, int scr, int y, int x, int falling, +ExtFunc ShapeDrawFunc func, void *data) +{ + int i, mirror, result; + Dir dir; + BlockType type; + + y += s->initY; + x += s->initX; + dir = s->initDir; + type = falling ? -s->type : s->type; + mirror = s->mirrored ? -1 : 1; + for (i = 0; s->cmds[i] != C_end; ++i) + switch (s->cmds[i]) { + case C_forw: + MoveInDir(dir, 1, &y, &x); + break; + case C_back: + MoveInDir(dir, -1, &y, &x); + break; + case C_left: + dir = RotateDir(dir, mirror); + break; + case C_right: + dir = RotateDir(dir, -mirror); + break; + case C_plot: + if ((result = func(scr, y, x, type, data))) + return result; + break; + default: + assert(0); + } + return 0; +} + +ExtFunc Shape *ChooseOption(ShapeOption *options) +{ + int i; + float total = 0, val; + + for (i = 0; options[i].shape; ++i) + total += options[i].weight; + val = Random(0, 32767) / 32768.0 * total; + for (i = 0; options[i].shape; ++i) { + val -= options[i].weight; + if (val < 0) + return options[i].shape; + } + return options[0].shape; +} + +ExtFunc short ShapeToNetNum(Shape *shape) +{ + int num; + + for (num = 0; netMapping[num]; ++num) + if (netMapping[num] == shape) + return num; + assert(0); + return 0; +} + +ExtFunc Shape *NetNumToShape(short num) +{ + assert(num >= 0 && num < sizeof(netMapping) / sizeof(netMapping[0]) - 1); + return netMapping[num]; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/sr.c b/sr.c new file mode 100644 index 0000000..a5974fd --- /dev/null +++ b/sr.c @@ -0,0 +1,474 @@ +/* + * sr -- A sample robot for Netris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: sr.c,v 1.10 1996/02/09 08:22:20 mhw Exp $ + */ + +#include +#include +#include +#include +#include +#include + +/* Both of these should be at least twice the actual max */ +#define MAX_BOARD_WIDTH 32 +#define MAX_BOARD_HEIGHT 64 + +char b[1024]; +FILE *logFile; + +int twoPlayer; +int boardHeight, boardWidth; +int board[MAX_BOARD_HEIGHT][MAX_BOARD_WIDTH]; +int piece[4][4]; +int pieceLast[4][4]; + +int board1[MAX_BOARD_HEIGHT][MAX_BOARD_WIDTH]; +int piece1[4][4]; +int piece2[4][4]; + +int pieceCount; /* Serial number of current piece, for sending commands */ +int pieceVisible; /* How many blocks of the current piece are visible */ +int pieceBottom, pieceLeft; /* Position of bottom-left square */ +int pieceBottomLast, pieceLeftLast; + +/* + * 0 = Not decided yet + * 1 = decided + * 2 = move in progress + * 3 = drop in progress + */ +int pieceState; + +int leftDest; +int pieceDest[4][4]; + +int masterEnable = 1, dropEnable = 1; + +float curTime, moveTimeout; + +int min(int a, int b) +{ + return a < b ? a : b; +} + +char *ReadLine(char *buf, int size) +{ + int len; + + if (!fgets(buf, size, stdin)) + return NULL; + len = strlen(buf); + if (len > 0 && buf[len-1] == '\n') + buf[len-1] = 0; + if (logFile) + fprintf(logFile, " %s\n", buf); + return buf; +} + +int WriteLine(char *fmt, ...) +{ + int result; + va_list args; + + va_start(args, fmt); + result = vfprintf(stdout, fmt, args); + if (logFile) { + fprintf(logFile, "> "); + vfprintf(logFile, fmt, args); + } + va_end(args); + return result; +} + +void FindPiece(void) +{ + int row, col; + + pieceVisible = 0; + pieceBottom = MAX_BOARD_HEIGHT; + pieceLeft = MAX_BOARD_WIDTH; + for (row = boardHeight - 1; row >= 0; --row) + for (col = boardWidth - 1; col >= 0; --col) + if (board[row][col] < 0) { + pieceBottom = row; + if (pieceLeft > col) + pieceLeft = col; + pieceVisible++; + } + for (row = 0; row < 4; ++row) + for (col = 0; col < 4; ++col) + piece[row][col] = board[pieceBottom + row][pieceLeft + col] < 0; +} + +void RotatePiece1(void) +{ + int row, col, height = 0; + + for (row = 0; row < 4; ++row) + for (col = 0; col < 4; ++col) + { + piece2[row][col] = piece1[row][col]; + piece1[row][col] = 0; + if (piece2[row][col]) + height = row + 1; + } + for (row = 0; row < 4; ++row) + for (col = 0; col < height; ++col) + piece1[row][col] = piece2[height - col - 1][row]; +} + +int PieceFits(int row, int col) +{ + int i, j; + + if (row < 0) + return 0; + for (i = 0; i < 4; ++i) + for (j = 0; j < 4; ++j) + if (piece1[i][j]) + if (col+j >= boardWidth || board[row+i][col+j] > 0) + return 0; + return 1; +} + +int SimPlacement(int row, int col) +{ + int i, j; + int from, to, count; + + for (i = 0; i < boardHeight; ++i) + for (j = 0; j < boardWidth; ++j) { + board1[i][j] = board[i][j] > 0; + if (i >= row && i < row+4 && j >= col && j < col+4) + if (piece1[i - row][j - col]) + board1[i][j] = 1; + } + for (from = to = 0; to < boardHeight; ++from) { + count = boardWidth; + for (j = 0; j < boardWidth; ++j) + count -= board1[to][j] = board1[from][j]; + to += (count > 0); + } + return from - to; +} + +double BoardScore(int linesCleared, int pRow, int verbose) +{ + double score = 0; + double avgHeight2 = 0, avgHolesTimesDepth = 0; + int maxMidHeight = 0, maxHeight = 0; + int height[MAX_BOARD_WIDTH]; + int holesTimesDepth[MAX_BOARD_WIDTH]; + int holes[MAX_BOARD_WIDTH]; + double hardFit[MAX_BOARD_HEIGHT]; + int depend[MAX_BOARD_HEIGHT]; + int cover[MAX_BOARD_WIDTH]; + int row, col, count, i; + int deltaLeft, deltaRight; + double closeToTop, topShape = 0, fitProbs = 0, space = 0; + double maxHard; + + for (col = 0; col < boardWidth; ++col) { + cover[col] = 0; + height[col] = 0; + for (row = 0; row < boardHeight; ++row) + if (board1[row][col]) + height[col] = row + 1; + avgHeight2 += height[col] * height[col]; + if (maxHeight < height[col]) + maxHeight = height[col]; + if (col >= 2 && col < boardWidth - 2 && maxMidHeight < height[col]) + maxMidHeight = height[col]; + holes[col] = 0; + holesTimesDepth[col] = 0; + for (row = 0; row < height[col]; ++row) { + if (board1[row][col]) + holesTimesDepth[col] += holes[col]; + else + holes[col]++; + } + avgHolesTimesDepth += holesTimesDepth[col]; + } + avgHeight2 /= boardWidth; + avgHolesTimesDepth /= boardWidth; + + /* Calculate dependencies */ + for (row = maxHeight - 1; row >= 0; --row) { + depend[row] = 0; + for (col = 0; col < boardWidth; ++col) { + if (board1[row][col]) + cover[col] |= 1 << row; + else + depend[row] |= cover[col]; + } + for (i = row + 1; i < maxHeight; ++i) + if (depend[row] & (1 << i)) + depend[row] |= depend[i]; + } + + /* Calculate hardness of fit */ + for (row = maxHeight - 1; row >= 0; --row) { + hardFit[row] = 5; + count = 0; + for (col = 0; col < boardWidth; ++col) { + if (board1[row][col]) { + space += 0.5; + } + else if (!board1[row][col]) { + count++; + space += 1; + hardFit[row]++; + if (height[col] < row) + hardFit[row] += row - height[col]; + if (col > 0) + deltaLeft = height[col - 1] - row; + else + deltaLeft = MAX_BOARD_HEIGHT; + if (col < boardWidth - 1) + deltaRight = height[col + 1] - row; + else + deltaRight = MAX_BOARD_HEIGHT; + if (deltaLeft > 2 && deltaRight > 2) + hardFit[row] += 7; + else if (deltaLeft > 2 || deltaRight > 2) + hardFit[row] += 2; + else if (abs(deltaLeft) == 2 && abs(deltaRight) == 2) + hardFit[row] += 2; + else if (abs(deltaLeft) == 2 || abs(deltaRight) == 2) + hardFit[row] += 3; + } + } + maxHard = 0; + for (i = row + 1; i < row + 5 && i < maxHeight; ++i) + if (depend[row] & (1 << i)) + if (maxHard < hardFit[i]) + maxHard = hardFit[i]; + fitProbs += maxHard * count; + } + + /* Calculate score based on top shape */ + for (col = 0; col < boardWidth; ++col) { + if (col > 0) + deltaLeft = height[col - 1] - height[col]; + else + deltaLeft = MAX_BOARD_HEIGHT; + if (col < boardWidth - 1) + deltaRight = height[col + 1] - height[col]; + else + deltaRight = MAX_BOARD_HEIGHT; + if (deltaLeft > 2 && deltaRight > 2) + topShape += 15 + 15 * (min(deltaLeft, deltaRight) / 4); + else if (deltaLeft > 2 || deltaRight > 2) + topShape += 2; + else if (abs(deltaLeft) == 2 && abs(deltaRight) == 2) + topShape += 2; + else if (abs(deltaLeft) == 2 || abs(deltaRight) == 2) + topShape += 3; + } + + closeToTop = (pRow / (double)boardHeight); + closeToTop *= closeToTop; + + closeToTop *= 200; + space /= 2; + score = space + closeToTop + topShape + fitProbs - linesCleared * 10; + + if (verbose) { + WriteLine("Message space=%g, close=%g, shape=%g\n", + space, closeToTop, topShape); + WriteLine("Message fitProbs=%g, cleared=%d\n", + fitProbs, -linesCleared * 10); + } + + return score; +} + +void PrintGoal(void) +{ + char b[32]; + int i, j, c; + + c = 0; + for (i = 0; i < 4; ++i) { + b[c++] = ':'; + for (j = 0; j < 4; ++j) + b[c++] = pieceDest[i][j] ? '*' : ' '; + } + b[c++]=':'; + b[c++]=0; + WriteLine("Message Goal %d %s\n", leftDest, b); +} + +double MakeDecision(void) +{ + int row, col, rot; + int linesCleared; + int first = 1; + double minScore = 0, score; + + memcpy(piece1, piece, sizeof(piece)); + for (rot = 0; rot < 4; ++rot) { + RotatePiece1(); + for (col = 0; col < boardWidth; ++col) { + if (!PieceFits(pieceBottom, col)) + continue; + for (row = pieceBottom; PieceFits(row-1, col); --row) + ; + linesCleared = SimPlacement(row, col); + score = BoardScore(linesCleared, row, 0); + if (first || minScore > score) { + first = 0; + minScore = score; + memcpy(pieceDest, piece1, sizeof(piece)); + leftDest = col; + } + } + } + PrintGoal(); + return minScore; +} + +double PeekScore(int verbose) +{ + int row, col, linesCleared; + + memcpy(piece1, piece, sizeof(piece)); + col = pieceLeft; + for (row = pieceBottom; PieceFits(row-1, col); --row) + ; + linesCleared = SimPlacement(row, col); + return BoardScore(linesCleared, row, verbose); +} + +int main(int argc, char **argv) +{ + int ac; + char *av[32]; + + if (argc == 2 && !strcmp(argv[1], "-l")) { + logFile = fopen("log", "w"); + if (!logFile) { + perror("fopen log"); + exit(1); + } + } + setvbuf(stdout, NULL, _IOLBF, 0); + WriteLine("Version 1\n"); + while(ReadLine(b, sizeof b)) { + av[0] = strtok(b, " "); + if (!av[0]) + continue; + ac = 1; + while ((av[ac] = strtok(NULL, " "))) + ac++; + if (!strcmp(av[0], "Exit")) + return 0; + else if (!strcmp(av[0], "NewPiece") && ac >= 2) { + pieceCount = atoi(av[1]); + pieceState = 0; + } + else if (!strcmp(av[0], "BoardSize") && ac >= 4) { + if (atoi(av[1]) != 0) + continue; + boardHeight = atoi(av[2]); + boardWidth = atoi(av[3]); + } + else if (!strcmp(av[0], "RowUpdate") && ac >= 3 + boardWidth) { + int scr, row, col; + + scr = atoi(av[1]); + if (scr != 0) + continue; + row = atoi(av[2]); + for (col = 0; col < boardWidth; col++) + board[row][col] = atoi(av[3 + col]); + } + else if (!strcmp(av[0], "UserKey") && ac >= 3) { + char key; + + key = atoi(av[1]); + switch (key) { + case 'v': + case 's': + FindPiece(); + WriteLine("Message Score = %g\n", PeekScore(key == 'v')); + break; + case 'e': + masterEnable = !masterEnable; + WriteLine("Message Enable = %d\n", masterEnable); + break; + case 'd': + dropEnable = !dropEnable; + WriteLine("Message Drop Enable = %d\n", dropEnable); + break; + default: + if (strcmp(av[2], "?")) + WriteLine("%s %d\n", av[2], pieceCount); + break; + } + } + else if (!strcmp(av[0], "TimeStamp") && ac >= 2 && masterEnable) { + curTime = atof(av[1]); + FindPiece(); + if (pieceVisible < 4) + continue; + if (memcmp(piece, pieceLast, sizeof(piece)) || + pieceLeft != pieceLeftLast) { + if (pieceState == 2) + pieceState = 1; + memcpy(pieceLast, piece, sizeof(piece)); + pieceLeftLast = pieceLeft; + } + if (pieceState == 0) { /* Undecided */ + MakeDecision(); + pieceState = 1; + } + if (pieceState >= 2) { /* Move or drop in progress */ + if (curTime >= moveTimeout) + pieceState = 1; + } + if (pieceState == 1) { /* Decided */ + if (memcmp(piece, pieceDest, sizeof(piece))) { + WriteLine("Rotate %d\n", pieceCount); + pieceState = 2; + } + else if (pieceLeft != leftDest) { + if (pieceLeft < leftDest) + WriteLine("Right %d\n", pieceCount); + else + WriteLine("Left %d\n", pieceCount); + pieceState = 2; + } + else if (dropEnable) { + WriteLine("Drop %d\n", pieceCount); + pieceState = 3; + } + if (pieceState == 2) + moveTimeout = curTime + 0.5; + } + } + } + return 0; +} + +/* + * vi: ts=4 ai + * vim: noai si + */ diff --git a/util.c b/util.c new file mode 100644 index 0000000..98248ed --- /dev/null +++ b/util.c @@ -0,0 +1,385 @@ +/* + * Netris -- A free networked version of T*tris + * Copyright (C) 1994,1995,1996 Mark H. Weaver + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * $Id: util.c,v 1.27 1996/02/09 08:22:23 mhw Exp $ + */ + +#include "netris.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static MyEventType AlarmGenFunc(EventGenRec *gen, MyEvent *event); + +static EventGenRec alarmGen = + { &alarmGen, 0, FT_read, -1, AlarmGenFunc, EM_alarm }; +static EventGenRec *nextGen = &alarmGen; + +static myRandSeed = 1; + +static struct timeval baseTimeval; + +ExtFunc void InitUtil(void) +{ + if (initSeed) + SRandom(initSeed); + else + SRandom(time(0)); + signal(SIGINT, CatchInt); + ResetBaseTime(); +} + +ExtFunc void ResetBaseTime(void) +{ + gettimeofday(&baseTimeval, NULL); +} + +ExtFunc void AtExit(void (*handler)(void)) +{ +#ifdef HAS_ON_EXIT + on_exit((void *)handler, NULL); +#else + atexit(handler); +#endif +} + +ExtFunc void Usage(void) +{ + fprintf(stderr, + "Netris version %s (C) 1994,1995,1996 Mark H. Weaver \n" + "Usage: netris \n" + " -h Print usage information\n" + " -w Wait for connection\n" + " -c Initiate connection\n" + " -p Set port number (default is %d)\n" + " -k Remap keys. The argument is a prefix of the string\n" + " containing the keys in order: left, rotate, right, drop,\n" + " down-faster, toggle-spying, pause, faster, redraw.\n" + " \"^\" prefixes controls. (default is \"%s\")\n" + " -i Set the step-down interval, in seconds\n" + " -r Execute (a command) as a robot controlling\n" + " the game instead of the keyboard\n" + " -F Use fair robot interface\n" + " -s Start with given random seed\n" + " -D Drops go into drop mode\n" + " This means that sliding off a cliff after a drop causes\n" + " another drop automatically\n" + " -S Disable standout mode (inverse/bold) for slow terminals\n" + " -H Show distribution and warranty information\n" + " -R Show rules\n", + version_string, DEFAULT_PORT, DEFAULT_KEYS); +} + +ExtFunc void DistInfo(void) +{ + fprintf(stderr, + "Netris version %s (C) 1994,1995,1996 Mark H. Weaver \n" + "\n" + "This program is free software; you can redistribute it and/or modify\n" + "it under the terms of the GNU General Public License as published by\n" + "the Free Software Foundation; either version 2 of the License, or\n" + "(at your option) any later version.\n" + "\n" + "This program is distributed in the hope that it will be useful,\n" + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" + "GNU General Public License for more details.\n" + "\n" + "You should have received a copy of the GNU General Public License\n" + "along with this program; if not, write to the Free Software\n" + "Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.\n", + version_string); +} + +ExtFunc void Rules(void) +{ + fprintf(stderr, + "Netris version %s rules\n" + "\n" + "Two player mode\n" + "---------------\n" + "It's just like normal T*tris except that when you clear more than\n" + "one row with a single piece, the other player's board is moved up\n" + "and junk rows are added to the bottom. If you clear 2, 3 or 4\n" + "rows, 1, 2 or 4 junk rows are added to your opponent's board,\n" + "respectively. The junk rows have exactly one empty column.\n" + "For each group of junk rows given, the empty columns will line\n" + "up. This is intentional.\n" + "\n" + "The longest surviving player wins the game.\n" + "\n" + "One player mode\n" + "---------------\n" + "This mode is currently very boring, because there's no scoring\n" + "and it never gets any faster. This will be rectified at some point.\n" + "I'm not very motivated to do it right now because I'm sick of one\n" + "player T*tris. For now, use the \"f\" key (by default) to make the\n" + "game go faster. Speedups cannot be reversed for the remainder of\n" + "the game.\n", + version_string); +} + +/* + * My really crappy random number generator follows + * Should be more than sufficient for our purposes though + */ +ExtFunc void SRandom(int seed) +{ + initSeed = seed; + myRandSeed = seed % 31751 + 1; +} + +ExtFunc int Random(int min, int max1) +{ + myRandSeed = (myRandSeed * 31751 + 15437) % 32767; + return myRandSeed % (max1 - min) + min; +} + +ExtFunc int MyRead(int fd, void *data, int len) +{ + int result, left; + + left = len; + while (left > 0) { + result = read(fd, data, left); + if (result > 0) { + data = ((char *)data) + result; + left -= result; + } + else if (errno != EINTR) + return result; + } + return len; +} + +ExtFunc int MyWrite(int fd, void *data, int len) +{ + int result, left; + + left = len; + while (left > 0) { + result = write(fd, data, left); + if (result > 0) { + data = ((char *)data) + result; + left -= result; + } + else if (errno != EINTR) + return result; + } + return len; +} + +ExtFunc void NormalizeTime(struct timeval *tv) +{ + tv->tv_sec += tv->tv_usec / 1000000; + tv->tv_usec %= 1000000; + if (tv->tv_usec < 0) { + tv->tv_usec += 1000000; + --tv->tv_sec; + } +} + +ExtFunc void CatchInt(int sig) +{ + exit(0); +} + +ExtFunc void CatchAlarm(int sig) +{ + alarmGen.ready = 1; + signal(SIGALRM, CatchAlarm); +} + +static MyEventType AlarmGenFunc(EventGenRec *gen, MyEvent *event) +{ + return E_alarm; +} + +ExtFunc long CurTimeval(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + tv.tv_sec -= baseTimeval.tv_sec; + tv.tv_usec -= baseTimeval.tv_usec; + return GetTimeval(&tv); +} + +ExtFunc void SetTimeval(struct timeval *tv, long usec) +{ + tv->tv_sec = usec / 1000000; + tv->tv_usec = usec % 1000000; +} + +ExtFunc long GetTimeval(struct timeval *tv) +{ + return tv->tv_sec * 1000000 + tv->tv_usec; +} + +static long SetITimer1(long interval, long value) +{ + struct itimerval it, old; + + SetTimeval(&it.it_interval, interval); + SetTimeval(&it.it_value, value); + if (setitimer(ITIMER_REAL, &it, &old) < 0) + die("setitimer"); + signal(SIGALRM, CatchAlarm); + return GetTimeval(&old.it_value); +} + +ExtFunc long SetITimer(long interval, long value) +{ + long old; + + old = SetITimer1(0, 0); + alarmGen.ready = 0; + SetITimer1(interval, value); + return old; +} + +ExtFunc volatile void die(char *msg) +{ + perror(msg); + exit(1); +} + +ExtFunc volatile void fatal(char *msg) +{ + fprintf(stderr, "%s\n", msg); + exit(1); +} + +ExtFunc void BlockSignals(MySigSet *saved, ...) +{ + MySigSet set; + va_list args; + int sig; + + va_start(args, saved); +#ifdef HAS_SIGPROCMASK + sigemptyset(&set); +#else + set = 0; +#endif + while ((sig = va_arg(args, int))) { +#ifdef HAS_SIGPROCMASK + sigaddset(&set, sig); +#else + sig |= sigmask(sig); +#endif + } +#ifdef HAS_SIGPROCMASK + sigprocmask(SIG_BLOCK, &set, saved); +#else + *saved = sigblock(set); +#endif + va_end(args); +} + +ExtFunc void RestoreSignals(MySigSet *saved, MySigSet *set) +{ +#ifdef HAS_SIGPROCMASK + sigprocmask(SIG_SETMASK, set, saved); +#else + if (saved) + *saved = sigsetmask(*set); + else + sigsetmask(*set); +#endif +} + +ExtFunc void AddEventGen(EventGenRec *gen) +{ + assert(gen->next == NULL); + gen->next = nextGen->next; + nextGen->next = gen; +} + +ExtFunc void RemoveEventGen(EventGenRec *gen) +{ + assert(gen->next != NULL); + while (nextGen->next != gen) + nextGen = nextGen->next; + nextGen->next = gen->next; + gen->next = NULL; +} + +ExtFunc MyEventType WaitMyEvent(MyEvent *event, int mask) +{ + int i, retry = 0; + fd_set fds[FT_len]; + EventGenRec *gen; + int result, anyReady, anySet; + struct timeval tv; + + /* XXX In certain circumstances, this routine does polling */ + for (;;) { + for (i = 0; i < FT_len; ++i) + FD_ZERO(&fds[i]); + anyReady = anySet = 0; + gen = nextGen; + do { + if (gen->mask & mask) { + if (gen->ready) + anyReady = 1; + if (gen->fd >= 0) { + FD_SET(gen->fd, &fds[gen->fdType]); + anySet = 1; + } + } + gen = gen->next; + } while (gen != nextGen); + if (anySet) { + tv.tv_sec = 0; + tv.tv_usec = (retry && !anyReady) ? 500000 : 0; + result = select(FD_SETSIZE, &fds[FT_read], &fds[FT_write], + &fds[FT_except], anyReady ? &tv : NULL); + } + else { + if (retry && !anyReady) + sleep(1); + result = 0; + } + gen = nextGen; + do { + if ((gen->mask & mask) + && (gen->ready || (result > 0 && gen->fd >= 0 + && FD_ISSET(gen->fd, &fds[gen->fdType])))) { + gen->ready = 0; + event->type = gen->func(gen, event); + if (event->type != E_none) { + nextGen = gen->next; + return event->type; + } + } + gen = gen->next; + } while (gen != nextGen); + retry = 1; + } +} + +/* + * vi: ts=4 ai + * vim: noai si + */ -- 2.39.2