From: Gerfried Fuchs Date: Wed, 14 Jul 2010 19:37:25 +0000 (+0200) Subject: Imported Upstream version 0.4 X-Git-Tag: upstream/0.4^0 X-Git-Url: https://git.deb.at/?p=pkg%2Fnetris.git;a=commitdiff_plain;h=89d91b6cfd98e514b3e418526ba854d00beb876c Imported Upstream version 0.4 --- 89d91b6cfd98e514b3e418526ba854d00beb876c 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 + */