]> Zhao Yanbai Git Server - minix.git/commitdiff
Importing games/tetris
authorThomas Cort <linuxgeek@gmail.com>
Sat, 22 Mar 2014 10:14:14 +0000 (06:14 -0400)
committerLionel Sambuc <lionel@minix3.org>
Mon, 28 Jul 2014 15:05:20 +0000 (17:05 +0200)
No Minix specific changes needed.

Change-Id: Ia6a2d9d2192ae0cee29e395aa42271005c6179c2

17 files changed:
distrib/sets/lists/minix/mi
etc/Makefile
etc/mtree/NetBSD.dist.base
games/Makefile
games/tetris/Makefile [new file with mode: 0644]
games/tetris/input.c [new file with mode: 0644]
games/tetris/input.h [new file with mode: 0644]
games/tetris/pathnames.h [new file with mode: 0644]
games/tetris/scores.c [new file with mode: 0644]
games/tetris/scores.h [new file with mode: 0644]
games/tetris/screen.c [new file with mode: 0644]
games/tetris/screen.h [new file with mode: 0644]
games/tetris/shapes.c [new file with mode: 0644]
games/tetris/tetris.6 [new file with mode: 0644]
games/tetris/tetris.c [new file with mode: 0644]
games/tetris/tetris.h [new file with mode: 0644]
releasetools/nbsd_ports

index 2efb34c594935a3598a39a64a7dc8159f7f864c1..33a4204b82480c35ecf159d54899a312138aea38 100644 (file)
 ./usr/games/primes                     minix-sys
 ./usr/games/random                     minix-sys
 ./usr/games/strfile                    minix-sys
+./usr/games/tetris                     minix-sys
 ./usr/games/unstr                      minix-sys
 ./usr/games/wargames                   minix-sys
 ./usr/include                          minix-sys
 ./usr/man/man6/ppt.6                           minix-sys
 ./usr/man/man6/primes.6                                minix-sys
 ./usr/man/man6/random.6                                minix-sys
+./usr/man/man6/tetris.6
 ./usr/man/man6/wargames.6                      minix-sys
 ./usr/man/man7                                 minix-sys
 ./usr/man/man7/ascii.7                         minix-sys
 ./var/db/obsolete                      minix-sys
 ./var/db/obsolete/minix                        minix-sys
 ./var/db/obsolete/tests                        minix-sys       kyua,atf
+./var/games                            minix-sys
+./var/games/tetris.scores              minix-sys
 ./var/log                              minix-sys
 ./var/mail                             minix-sys
 ./var/run                              minix-sys
index 1b36115c107a71c3e8a5f9bbf3833f59468672aa..1af1c740aa69e2fb48bffe0932c7a9f6fe3fb29d 100644 (file)
@@ -350,6 +350,19 @@ install-etc-files-safe: .PHONY .MAKE check_DESTDIR MAKEDEV
        ${_MKMSG_INSTALL} ${DESTDIR}/dev/MAKEDEV
        ${ETC_INSTALL_OBJ_FILE} -o ${BINOWN} -g ${BINGRP} -m 555 \
            MAKEDEV ${DESTDIR}/dev
+.else
+.for owner group mode file in \
+               games games             664     /var/games/tetris.scores
+       ${_MKMSG_INSTALL} ${DESTDIR}${file}
+       if [ ! -e ${DESTDIR}${file} -o -s ${DESTDIR}${file} ]; then \
+               ${ETC_INSTALL_FILE} -o ${owner} -g ${group} -m ${mode} \
+                       /dev/null ${DESTDIR}${file}; \
+       else true; fi
+.endfor
+.endif # !defined(__MINIX)
+# TAC as software is imported, move the files from the .for block below
+# TAC to the .for block above.
+.if !defined(__MINIX)
 .for owner group mode file in \
                ${BINOWN} operator      664     /etc/dumpdates  \
                ${BINOWN} operator      600     /etc/skeykeys \
@@ -383,7 +396,6 @@ install-etc-files-safe: .PHONY .MAKE check_DESTDIR MAKEDEV
                games games             664     /var/games/saillog \
                games games             664     /var/games/snakerawscores \
                games games             664     /var/games/snake.log \
-               games games             664     /var/games/tetris.scores
        ${_MKMSG_INSTALL} ${DESTDIR}${file}
        if [ ! -e ${DESTDIR}${file} -o -s ${DESTDIR}${file} ]; then \
                ${ETC_INSTALL_FILE} -o ${owner} -g ${group} -m ${mode} \
index 2e399e77f45f40e898368a0dbe27fccad6f1cbc2..2ad59c36f53b3bf467280d908bae43c30c32650f 100644 (file)
 ./var
 ./var/db
 ./var/db/obsolete
+./var/games                    uname=games gname=games mode=0775
 ./var/mail
 ./var/run
 
index 6cd94edcd9f1031f1ecdee19f2ee72e6e94eb288..5694ec70f1efd7cf1f7cf748ef2691d98a07bb71 100644 (file)
@@ -13,7 +13,7 @@ SUBDIR=       adventure arithmetic \
        factor fortune \
        monop morse number \
        pig ppt primes \
-       random \
+       random tetris \
        wargames
 
 .if !defined(__MINIX)
diff --git a/games/tetris/Makefile b/games/tetris/Makefile
new file mode 100644 (file)
index 0000000..25a3639
--- /dev/null
@@ -0,0 +1,12 @@
+#      $NetBSD: Makefile,v 1.7 2010/02/03 15:34:39 roy Exp $
+#      @(#)Makefile    8.1 (Berkeley) 5/31/93
+
+PROG=  tetris
+SRCS=  input.c screen.c shapes.c scores.c tetris.c
+MAN=   tetris.6
+DPADD= ${LIBTERMINFO}
+LDADD= -lterminfo
+HIDEGAME=hidegame
+SETGIDGAME=yes
+
+.include <bsd.prog.mk>
diff --git a/games/tetris/input.c b/games/tetris/input.c
new file mode 100644 (file)
index 0000000..e5f8c12
--- /dev/null
@@ -0,0 +1,162 @@
+/*     $NetBSD: input.c,v 1.11 2009/05/25 04:33:53 dholland Exp $      */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)input.c     8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Tetris input.
+ */
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+
+#include <errno.h>
+#include <unistd.h>
+
+#include "input.h"
+#include "tetris.h"
+
+/* return true iff the given timeval is positive */
+#define        TV_POS(tv) \
+       ((tv)->tv_sec > 0 || ((tv)->tv_sec == 0 && (tv)->tv_usec > 0))
+
+/* subtract timeval `sub' from `res' */
+#define        TV_SUB(res, sub) \
+       (res)->tv_sec -= (sub)->tv_sec; \
+       (res)->tv_usec -= (sub)->tv_usec; \
+       if ((res)->tv_usec < 0) { \
+               (res)->tv_usec += 1000000; \
+               (res)->tv_sec--; \
+       }
+
+/*
+ * Do a `read wait': poll for reading from stdin, with timeout *tvp.
+ * On return, modify *tvp to reflect the amount of time spent waiting.
+ * It will be positive only if input appeared before the time ran out;
+ * otherwise it will be zero or perhaps negative.
+ *
+ * If tvp is nil, wait forever, but return if poll is interrupted.
+ *
+ * Return 0 => no input, 1 => can read() from stdin
+ */
+int
+rwait(struct timeval *tvp)
+{
+       struct pollfd set[1];
+       struct timeval starttv, endtv;
+       int timeout;
+#define        NILTZ ((struct timezone *)0)
+
+       if (tvp) {
+               (void) gettimeofday(&starttv, NILTZ);
+               endtv = *tvp;
+               timeout = tvp->tv_sec * 1000 + tvp->tv_usec / 1000;
+       } else
+               timeout = INFTIM;
+again:
+       set[0].fd = STDIN_FILENO;
+       set[0].events = POLLIN;
+       switch (poll(set, 1, timeout)) {
+
+       case -1:
+               if (tvp == 0)
+                       return (-1);
+               if (errno == EINTR)
+                       goto again;
+               stop("poll failed, help");
+               /* NOTREACHED */
+
+       case 0: /* timed out */
+               if (tvp) {
+                       tvp->tv_sec = 0;
+                       tvp->tv_usec = 0;
+               }
+               return (0);
+       }
+       if (tvp) {
+               /* since there is input, we may not have timed out */
+               (void) gettimeofday(&endtv, NILTZ);
+               TV_SUB(&endtv, &starttv);
+               TV_SUB(tvp, &endtv);    /* adjust *tvp by elapsed time */
+       }
+       return (1);
+}
+
+/*
+ * `sleep' for the current turn time.
+ * Eat any input that might be available.
+ */
+void
+tsleep(void)
+{
+       struct timeval tv;
+       char c;
+
+       tv.tv_sec = 0;
+       tv.tv_usec = fallrate;
+       while (TV_POS(&tv))
+               if (rwait(&tv) && read(0, &c, 1) != 1)
+                       break;
+}
+
+/*
+ * getchar with timeout.
+ */
+int
+tgetchar(void)
+{
+       static struct timeval timeleft;
+       char c;
+
+       /*
+        * Reset timeleft to fallrate whenever it is not positive.
+        * In any case, wait to see if there is any input.  If so,
+        * take it, and update timeleft so that the next call to
+        * tgetchar() will not wait as long.  If there is no input,
+        * make timeleft zero or negative, and return -1.
+        *
+        * Most of the hard work is done by rwait().
+        */
+       if (!TV_POS(&timeleft)) {
+               faster();       /* go faster */
+               timeleft.tv_sec = 0;
+               timeleft.tv_usec = fallrate;
+       }
+       if (!rwait(&timeleft))
+               return (-1);
+       if (read(0, &c, 1) != 1)
+               stop("end of file, help");
+       return ((int)(unsigned char)c);
+}
diff --git a/games/tetris/input.h b/games/tetris/input.h
new file mode 100644 (file)
index 0000000..856c66a
--- /dev/null
@@ -0,0 +1,39 @@
+/*     $NetBSD: input.h,v 1.5 2004/01/27 20:30:30 jsm Exp $    */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)input.h     8.1 (Berkeley) 5/31/93
+ */
+
+int    rwait(struct timeval *);
+int    tgetchar(void);
+void   tsleep(void);
diff --git a/games/tetris/pathnames.h b/games/tetris/pathnames.h
new file mode 100644 (file)
index 0000000..f96e3cc
--- /dev/null
@@ -0,0 +1,37 @@
+/*     $NetBSD: pathnames.h,v 1.3 2003/08/07 09:37:48 agc Exp $        */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)pathnames.h 8.1 (Berkeley) 5/31/93
+ */
+
+#define _PATH_SCOREFILE        "/var/games/tetris.scores"
diff --git a/games/tetris/scores.c b/games/tetris/scores.c
new file mode 100644 (file)
index 0000000..83cc6f2
--- /dev/null
@@ -0,0 +1,956 @@
+/*     $NetBSD: scores.c,v 1.21 2013/10/19 17:23:08 christos Exp $     */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)scores.c    8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Score code for Tetris, by Darren Provine (kilroy@gboro.glassboro.edu)
+ * modified 22 January 1992, to limit the number of entries any one
+ * person has.
+ *
+ * Major whacks since then.
+ */
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <term.h>
+#include <unistd.h>
+
+#include "pathnames.h"
+#include "screen.h"
+#include "scores.h"
+#include "tetris.h"
+
+/*
+ * Allow updating the high scores unless we're built as part of /rescue.
+ */
+#ifndef RESCUEDIR
+#define ALLOW_SCORE_UPDATES
+#endif
+
+/*
+ * Within this code, we can hang onto one extra "high score", leaving
+ * room for our current score (whether or not it is high).
+ *
+ * We also sometimes keep tabs on the "highest" score on each level.
+ * As long as the scores are kept sorted, this is simply the first one at
+ * that level.
+ */
+#define NUMSPOTS (MAXHISCORES + 1)
+#define        NLEVELS (MAXLEVEL + 1)
+
+static time_t now;
+static int nscores;
+static int gotscores;
+static struct highscore scores[NUMSPOTS];
+
+static int checkscores(struct highscore *, int);
+static int cmpscores(const void *, const void *);
+static void getscores(int *);
+static void printem(int, int, struct highscore *, int, const char *);
+static char *thisuser(void);
+
+/* contents chosen to be a highly illegal username */
+static const char hsh_magic_val[HSH_MAGIC_SIZE] = "//:\0\0://";
+
+#define HSH_ENDIAN_NATIVE      0x12345678
+#define HSH_ENDIAN_OPP         0x78563412
+
+/* current file format version */
+#define HSH_VERSION            1
+
+/* codes for scorefile_probe return */
+#define SCOREFILE_ERROR                (-1)
+#define SCOREFILE_CURRENT      0       /* 40-byte */
+#define SCOREFILE_CURRENT_OPP  1       /* 40-byte, opposite-endian */
+#define SCOREFILE_599          2       /* 36-byte */
+#define SCOREFILE_599_OPP      3       /* 36-byte, opposite-endian */
+#define SCOREFILE_50           4       /* 32-byte */
+#define SCOREFILE_50_OPP       5       /* 32-byte, opposite-endian */
+
+/*
+ * Check (or guess) what kind of score file contents we have.
+ */
+static int
+scorefile_probe(int sd)
+{
+       struct stat st;
+       int t1, t2, t3, tx;
+       ssize_t result;
+       uint32_t numbers[3], offset56, offset60, offset64;
+
+       if (fstat(sd, &st) < 0) {
+               warn("Score file %s: fstat", _PATH_SCOREFILE);
+               return -1;
+       }
+
+       t1 = st.st_size % sizeof(struct highscore_ondisk) == 0;
+       t2 = st.st_size % sizeof(struct highscore_ondisk_599) == 0;
+       t3 = st.st_size % sizeof(struct highscore_ondisk_50) == 0;
+       tx = t1 + t2 + t3;
+       if (tx == 1) {
+               /* Size matches exact number of one kind of records */
+               if (t1) {
+                       return SCOREFILE_CURRENT;
+               } else if (t2) {
+                       return SCOREFILE_599;
+               } else {
+                       return SCOREFILE_50;
+               }
+       } else if (tx == 0) {
+               /* Size matches nothing, pick most likely as default */
+               goto wildguess;
+       }
+
+       /*
+        * File size is multiple of more than one structure size.
+        * (For example, 288 bytes could be 9*hso50 or 8*hso599.)
+        * Read the file and see if we can figure out what's going
+        * on. This is the layout of the first two records:
+        *
+        *   offset     hso / current   hso_599         hso_50
+        *              (40-byte)       (36-byte)       (32-byte)
+        *
+        *   0          name #0         name #0         name #0
+         *   4            :               :               :
+         *   8            :               :               :
+         *   12           :               :               :
+         *   16           :               :               :
+        *   20         score #0        score #0        score #0
+        *   24         level #0        level #0        level #0
+        *   28          (pad)          time #0         time #0
+        *   32         time #0                         name #1
+        *   36                         name #1           :
+         *   40         name #1           :               :
+         *   44           :               :               :
+         *   48           :               :               :
+        *   52           :               :             score #1
+        *   56           :             score #1        level #1
+        *   60         score #1        level #1        time #1
+        *   64         level #1        time #1         name #2
+        *   68          (pad)            :               :
+        *   72         time #1         name #2           :
+        *   76           :               :               :
+        *   80                  --- end ---
+        *
+        * There are a number of things we could check here, but the
+        * most effective test is based on the following restrictions:
+        *
+        *    - The level must be between 1 and 9 (inclusive)
+        *    - All times must be after 1985 and are before 2038,
+        *      so the high word must be 0 and the low word may not be
+        *      a small value.
+        *    - Integer values of 0 or 1-9 cannot be the beginning of
+        *      a login name string.
+        *    - Values of 1-9 are probably not a score.
+        *
+        * So we read the three words at offsets 56, 60, and 64, and
+        * poke at the values to try to figure things...
+        */
+
+       if (lseek(sd, 56, SEEK_SET) < 0) {
+               warn("Score file %s: lseek", _PATH_SCOREFILE);
+               return -1;
+       }
+       result = read(sd, &numbers, sizeof(numbers));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       if ((size_t)result != sizeof(numbers)) {
+               /*
+                * The smallest file whose size divides by more than
+                * one of the sizes is substantially larger than 64,
+                * so this should *never* happen.
+                */
+               warnx("Score file %s: Unexpected EOF", _PATH_SCOREFILE);
+               return -1;
+       }
+
+       offset56 = numbers[0];
+       offset60 = numbers[1];
+       offset64 = numbers[2];
+
+       if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
+               /* 40-byte structure */
+               return SCOREFILE_CURRENT;
+       } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
+               /* 36-byte structure */
+               return SCOREFILE_599;
+       } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
+               /* 32-byte structure */
+               return SCOREFILE_50;
+       }
+
+       /* None was a valid level; try opposite endian */
+       offset64 = bswap32(offset64);
+       offset60 = bswap32(offset60);
+       offset56 = bswap32(offset56);
+
+       if (offset64 >= MINLEVEL && offset64 <= MAXLEVEL) {
+               /* 40-byte structure */
+               return SCOREFILE_CURRENT_OPP;
+       } else if (offset60 >= MINLEVEL && offset60 <= MAXLEVEL) {
+               /* 36-byte structure */
+               return SCOREFILE_599_OPP;
+       } else if (offset56 >= MINLEVEL && offset56 <= MAXLEVEL) {
+               /* 32-byte structure */
+               return SCOREFILE_50_OPP;
+       }
+
+       /* That didn't work either, dunno what's going on */
+ wildguess:
+       warnx("Score file %s is likely corrupt", _PATH_SCOREFILE);
+       if (sizeof(void *) == 8 && sizeof(time_t) == 8) {
+               return SCOREFILE_CURRENT;
+       } else if (sizeof(time_t) == 8) {
+               return SCOREFILE_599;
+       } else {
+               return SCOREFILE_50;
+       }
+}
+
+/*
+ * Copy a string safely, making sure it's null-terminated.
+ */
+static void
+readname(char *to, size_t maxto, const char *from, size_t maxfrom)
+{
+       size_t amt;
+
+       amt = maxto < maxfrom ? maxto : maxfrom;
+       memcpy(to, from, amt);
+       to[maxto-1] = '\0';
+}
+
+/*
+ * Copy integers, byte-swapping if desired.
+ */
+static int32_t
+read32(int32_t val, int doflip)
+{
+       if (doflip) {
+               val = bswap32(val);
+       }
+       return val;
+}
+
+static int64_t
+read64(int64_t val, int doflip)
+{
+       if (doflip) {
+               val = bswap64(val);
+       }
+       return val;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk entries.
+ */
+static int
+readscores(int sd, int doflip)
+{
+       struct highscore_ondisk buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso_name, sizeof(buf[i].hso_name));
+               scores[i].hs_score = read32(buf[i].hso_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso_level, doflip);
+               scores[i].hs_time = read64(buf[i].hso_time, doflip);
+       }
+       return 0;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk_599 entries.
+ */
+static int
+readscores599(int sd, int doflip)
+{
+       struct highscore_ondisk_599 buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso599_name, sizeof(buf[i].hso599_name));
+               scores[i].hs_score = read32(buf[i].hso599_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso599_level, doflip);
+               /*
+                * Don't bother pasting the time together into a
+                * 64-bit value; just take whichever half is nonzero.
+                */
+               scores[i].hs_time =
+                       read32(buf[i].hso599_time[buf[i].hso599_time[0] == 0],
+                              doflip);
+       }
+       return 0;
+}
+
+/*
+ * Read up to MAXHISCORES scorefile_ondisk_50 entries.
+ */
+static int
+readscores50(int sd, int doflip)
+{
+       struct highscore_ondisk_50 buf[MAXHISCORES];
+       ssize_t result;
+       int i;
+
+       result = read(sd, buf, sizeof(buf));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               return -1;
+       }
+       nscores = result / sizeof(buf[0]);
+
+       for (i=0; i<nscores; i++) {
+               readname(scores[i].hs_name, sizeof(scores[i].hs_name),
+                        buf[i].hso50_name, sizeof(buf[i].hso50_name));
+               scores[i].hs_score = read32(buf[i].hso50_score, doflip);
+               scores[i].hs_level = read32(buf[i].hso50_level, doflip);
+               scores[i].hs_time = read32(buf[i].hso50_time, doflip);
+       }
+       return 0;
+}
+
+/*
+ * Read the score file.  Can be called from savescore (before showscores)
+ * or showscores (if savescore will not be called).  If the given pointer
+ * is not NULL, sets *fdp to an open file handle that corresponds to a
+ * read/write score file that is locked with LOCK_EX.  Otherwise, the
+ * file is locked with LOCK_SH for the read and closed before return.
+ */
+static void
+getscores(int *fdp)
+{
+       struct highscore_header header;
+       int sd, mint, lck;
+       mode_t mask;
+       const char *human;
+       int doflip;
+       ssize_t result;
+
+#ifdef ALLOW_SCORE_UPDATES
+       if (fdp != NULL) {
+               mint = O_RDWR | O_CREAT;
+               human = "read/write";
+               lck = LOCK_EX;
+       } else
+#endif
+       {
+               mint = O_RDONLY;
+               human = "reading";
+               lck = LOCK_SH;
+       }
+       setegid(egid);
+       mask = umask(S_IWOTH);
+       sd = open(_PATH_SCOREFILE, mint, 0666);
+       (void)umask(mask);
+       setegid(gid);
+       if (sd < 0) {
+               /*
+                * If the file simply isn't there because nobody's
+                * played yet, and we aren't going to be trying to
+                * update it, don't warn. Even if we are going to be
+                * trying to write it, don't fail -- we can still show
+                * the player the score they got.
+                */
+               if (fdp != NULL || errno != ENOENT) {
+                       warn("Cannot open %s for %s", _PATH_SCOREFILE, human);
+               }
+               goto fail;
+       }
+
+       /*
+        * Grab a lock.
+        * XXX: failure here should probably be more fatal than this.
+        */
+       if (flock(sd, lck))
+               warn("warning: score file %s cannot be locked",
+                   _PATH_SCOREFILE);
+
+       /*
+        * The current format (since -current of 20090525) is
+        *
+        *    struct highscore_header
+        *    up to MAXHIGHSCORES x struct highscore_ondisk
+        *
+        * Before this, there is no header, and the contents
+        * might be any of three formats:
+        *
+        *    highscore_ondisk       (64-bit machines with 64-bit time_t)
+        *    highscore_ondisk_599   (32-bit machines with 64-bit time_t)
+        *    highscore_ondisk_50    (32-bit machines with 32-bit time_t)
+        *
+        * The first two appear in 5.99 between the time_t change and
+        * 20090525, depending on whether the compiler inserts
+        * structure padding before an unaligned 64-bit time_t. The
+        * last appears in 5.0 and earlier.
+        *
+        * Any or all of these might also appear on other OSes where
+        * this code has been ported.
+        *
+        * Since the old file has no header, we will have to guess
+        * which of these formats it has.
+        */
+
+       /*
+        * First, look for a header.
+        */
+       result = read(sd, &header, sizeof(header));
+       if (result < 0) {
+               warn("Score file %s: read", _PATH_SCOREFILE);
+               goto sdfail;
+       }
+       if (result != 0 && (size_t)result != sizeof(header)) {
+               warnx("Score file %s: read: unexpected EOF", _PATH_SCOREFILE);
+               /*
+                * File is hopelessly corrupt, might as well truncate it
+                * and start over with empty scores.
+                */
+               if (lseek(sd, 0, SEEK_SET) < 0) {
+                       /* ? */
+                       warn("Score file %s: lseek", _PATH_SCOREFILE);
+                       goto sdfail;
+               }
+               if (ftruncate(sd, 0) == 0) {
+                       result = 0;
+               } else {
+                       goto sdfail;
+               }
+       }
+
+       if (result == 0) {
+               /* Empty file; that just means there are no scores. */
+               nscores = 0;
+       } else {
+               /*
+                * Is what we read a header, or the first 16 bytes of
+                * a score entry? hsh_magic_val is chosen to be
+                * something that is extremely unlikely to appear in
+                * hs_name[].
+                */
+               if (!memcmp(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE)) {
+                       /* Yes, we have a header. */
+
+                       if (header.hsh_endiantag == HSH_ENDIAN_NATIVE) {
+                               /* native endian */
+                               doflip = 0;
+                       } else if (header.hsh_endiantag == HSH_ENDIAN_OPP) {
+                               doflip = 1;
+                       } else {
+                               warnx("Score file %s: Unknown endian tag %u",
+                                       _PATH_SCOREFILE, header.hsh_endiantag);
+                               goto sdfail;
+                       }
+
+                       if (header.hsh_version != HSH_VERSION) {
+                               warnx("Score file %s: Unknown version code %u",
+                                       _PATH_SCOREFILE, header.hsh_version);
+                               goto sdfail;
+                       }
+
+                       if (readscores(sd, doflip) < 0) {
+                               goto sdfail;
+                       }
+               } else {
+                       /*
+                        * Ok, it wasn't a header. Try to figure out what
+                        * size records we have.
+                        */
+                       result = scorefile_probe(sd);
+                       if (lseek(sd, 0, SEEK_SET) < 0) {
+                               warn("Score file %s: lseek", _PATH_SCOREFILE);
+                               goto sdfail;
+                       }
+                       switch (result) {
+                       case SCOREFILE_CURRENT:
+                               result = readscores(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_CURRENT_OPP:
+                               result = readscores(sd, 1 /* do flip */);
+                               break;
+                       case SCOREFILE_599:
+                               result = readscores599(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_599_OPP:
+                               result = readscores599(sd, 1 /* do flip */);
+                               break;
+                       case SCOREFILE_50:
+                               result = readscores50(sd, 0 /* don't flip */);
+                               break;
+                       case SCOREFILE_50_OPP:
+                               result = readscores50(sd, 1 /* do flip */);
+                               break;
+                       default:
+                               goto sdfail;
+                       }
+                       if (result < 0) {
+                               goto sdfail;
+                       }
+               }
+       }
+       
+
+       if (fdp)
+               *fdp = sd;
+       else
+               close(sd);
+
+       return;
+
+sdfail:
+       close(sd);
+ fail:
+       if (fdp != NULL) {
+               *fdp = -1;
+       }
+       nscores = 0;
+}
+
+#ifdef ALLOW_SCORE_UPDATES
+/*
+ * Paranoid write wrapper; unlike fwrite() it preserves errno.
+ */
+static int
+dowrite(int sd, const void *vbuf, size_t len)
+{
+       const char *buf = vbuf;
+       ssize_t result;
+       size_t done = 0;
+
+       while (done < len) {
+               result = write(sd, buf+done, len-done);
+               if (result < 0) {
+                       if (errno == EINTR) {
+                               continue;
+                       }
+                       return -1;
+               }
+               done += result;
+       }
+       return 0;
+}
+#endif /* ALLOW_SCORE_UPDATES */
+
+/*
+ * Write the score file out.
+ */
+static void
+putscores(int sd)
+{
+#ifdef ALLOW_SCORE_UPDATES
+       struct highscore_header header;
+       struct highscore_ondisk buf[MAXHISCORES];
+       int i;
+
+       if (sd == -1) {
+               return;
+       }
+
+       memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE);
+       header.hsh_endiantag = HSH_ENDIAN_NATIVE;
+       header.hsh_version = HSH_VERSION;
+
+       for (i=0; i<nscores; i++) {
+               strncpy(buf[i].hso_name, scores[i].hs_name,
+                       sizeof(buf[i].hso_name));
+               buf[i].hso_score = scores[i].hs_score;
+               buf[i].hso_level = scores[i].hs_level;
+               buf[i].hso_pad = 0xbaadf00d;
+               buf[i].hso_time = scores[i].hs_time;
+       }
+
+       if (lseek(sd, 0, SEEK_SET) < 0) {
+               warn("Score file %s: lseek", _PATH_SCOREFILE);
+               goto fail;
+       }
+       if (dowrite(sd, &header, sizeof(header)) < 0 ||
+           dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) {
+               warn("Score file %s: write", _PATH_SCOREFILE);
+               goto fail;
+       }
+       return;
+ fail:
+       warnx("high scores may be damaged");
+#else
+       (void)sd;
+#endif /* ALLOW_SCORE_UPDATES */
+}
+
+/*
+ * Close the score file.
+ */
+static void
+closescores(int sd)
+{
+       flock(sd, LOCK_UN);
+       close(sd);
+}
+
+/*
+ * Read and update the scores file with the current reults.
+ */
+void
+savescore(int level)
+{
+       struct highscore *sp;
+       int i;
+       int change;
+       int sd;
+       const char *me;
+
+       getscores(&sd);
+       gotscores = 1;
+       (void)time(&now);
+
+       /*
+        * Allow at most one score per person per level -- see if we
+        * can replace an existing score, or (easiest) do nothing.
+        * Otherwise add new score at end (there is always room).
+        */
+       change = 0;
+       me = thisuser();
+       for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) {
+               if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0)
+                       continue;
+               if (score > sp->hs_score) {
+                       (void)printf("%s bettered %s %d score of %d!\n",
+                           "\nYou", "your old level", level,
+                           sp->hs_score * sp->hs_level);
+                       sp->hs_score = score;   /* new score */
+                       sp->hs_time = now;      /* and time */
+                       change = 1;
+               } else if (score == sp->hs_score) {
+                       (void)printf("%s tied %s %d high score.\n",
+                           "\nYou", "your old level", level);
+                       sp->hs_time = now;      /* renew it */
+                       change = 1;             /* gotta rewrite, sigh */
+               } /* else new score < old score: do nothing */
+               break;
+       }
+       if (i >= nscores) {
+               strcpy(sp->hs_name, me);
+               sp->hs_level = level;
+               sp->hs_score = score;
+               sp->hs_time = now;
+               nscores++;
+               change = 1;
+       }
+
+       if (change) {
+               /*
+                * Sort & clean the scores, then rewrite.
+                */
+               nscores = checkscores(scores, nscores);
+               putscores(sd);
+       }
+       closescores(sd);
+}
+
+/*
+ * Get login name, or if that fails, get something suitable.
+ * The result is always trimmed to fit in a score.
+ */
+static char *
+thisuser(void)
+{
+       const char *p;
+       struct passwd *pw;
+       size_t l;
+       static char u[sizeof(scores[0].hs_name)];
+
+       if (u[0])
+               return (u);
+       p = getlogin();
+       if (p == NULL || *p == '\0') {
+               pw = getpwuid(getuid());
+               if (pw != NULL)
+                       p = pw->pw_name;
+               else
+                       p = "  ???";
+       }
+       l = strlen(p);
+       if (l >= sizeof(u))
+               l = sizeof(u) - 1;
+       memcpy(u, p, l);
+       u[l] = '\0';
+       return (u);
+}
+
+/*
+ * Score comparison function for qsort.
+ *
+ * If two scores are equal, the person who had the score first is
+ * listed first in the highscore file.
+ */
+static int
+cmpscores(const void *x, const void *y)
+{
+       const struct highscore *a, *b;
+       long l;
+
+       a = x;
+       b = y;
+       l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score;
+       if (l < 0)
+               return (-1);
+       if (l > 0)
+               return (1);
+       if (a->hs_time < b->hs_time)
+               return (-1);
+       if (a->hs_time > b->hs_time)
+               return (1);
+       return (0);
+}
+
+/*
+ * If we've added a score to the file, we need to check the file and ensure
+ * that this player has only a few entries.  The number of entries is
+ * controlled by MAXSCORES, and is to ensure that the highscore file is not
+ * monopolised by just a few people.  People who no longer have accounts are
+ * only allowed the highest score.  Scores older than EXPIRATION seconds are
+ * removed, unless they are someone's personal best.
+ * Caveat:  the highest score on each level is always kept.
+ */
+static int
+checkscores(struct highscore *hs, int num)
+{
+       struct highscore *sp;
+       int i, j, k, numnames;
+       int levelfound[NLEVELS];
+       struct peruser {
+               char *name;
+               int times;
+       } count[NUMSPOTS];
+       struct peruser *pu;
+
+       /*
+        * Sort so that highest totals come first.
+        *
+        * levelfound[i] becomes set when the first high score for that
+        * level is encountered.  By definition this is the highest score.
+        */
+       qsort((void *)hs, nscores, sizeof(*hs), cmpscores);
+       for (i = MINLEVEL; i < NLEVELS; i++)
+               levelfound[i] = 0;
+       numnames = 0;
+       for (i = 0, sp = hs; i < num;) {
+               /*
+                * This is O(n^2), but do you think we care?
+                */
+               for (j = 0, pu = count; j < numnames; j++, pu++)
+                       if (strcmp(sp->hs_name, pu->name) == 0)
+                               break;
+               if (j == numnames) {
+                       /*
+                        * Add new user, set per-user count to 1.
+                        */
+                       pu->name = sp->hs_name;
+                       pu->times = 1;
+                       numnames++;
+               } else {
+                       /*
+                        * Two ways to keep this score:
+                        * - Not too many (per user), still has acct, &
+                        *      score not dated; or
+                        * - High score on this level.
+                        */
+                       if ((pu->times < MAXSCORES &&
+                            getpwnam(sp->hs_name) != NULL &&
+                            sp->hs_time + EXPIRATION >= now) ||
+                           levelfound[sp->hs_level] == 0)
+                               pu->times++;
+                       else {
+                               /*
+                                * Delete this score, do not count it,
+                                * do not pass go, do not collect $200.
+                                */
+                               num--;
+                               for (k = i; k < num; k++)
+                                       hs[k] = hs[k + 1];
+                               continue;
+                       }
+               }
+        if (sp->hs_level < NLEVELS && sp->hs_level >= 0)
+               levelfound[sp->hs_level] = 1;
+               i++, sp++;
+       }
+       return (num > MAXHISCORES ? MAXHISCORES : num);
+}
+
+/*
+ * Show current scores.  This must be called after savescore, if
+ * savescore is called at all, for two reasons:
+ * - Showscores munches the time field.
+ * - Even if that were not the case, a new score must be recorded
+ *   before it can be shown anyway.
+ */
+void
+showscores(int level)
+{
+       struct highscore *sp;
+       int i, n, c;
+       const char *me;
+       int levelfound[NLEVELS];
+
+       if (!gotscores)
+               getscores(NULL);
+       (void)printf("\n\t\t\t    Tetris High Scores\n");
+
+       /*
+        * If level == 0, the person has not played a game but just asked for
+        * the high scores; we do not need to check for printing in highlight
+        * mode.  If SOstr is null, we can't do highlighting anyway.
+        */
+       me = level && enter_standout_mode ? thisuser() : NULL;
+
+       /*
+        * Set times to 0 except for high score on each level.
+        */
+       for (i = MINLEVEL; i < NLEVELS; i++)
+               levelfound[i] = 0;
+       for (i = 0, sp = scores; i < nscores; i++, sp++) {
+        if (sp->hs_level < NLEVELS && sp->hs_level >= 0) {
+               if (levelfound[sp->hs_level])
+                       sp->hs_time = 0;
+                   else {
+                           sp->hs_time = 1;
+                       levelfound[sp->hs_level] = 1;
+                   }
+        }
+       }
+
+       /*
+        * Page each screenful of scores.
+        */
+       for (i = 0, sp = scores; i < nscores; sp += n) {
+               n = 40;
+               if (i + n > nscores)
+                       n = nscores - i;
+               printem(level, i + 1, sp, n, me);
+               if ((i += n) < nscores) {
+                       (void)printf("\nHit RETURN to continue.");
+                       (void)fflush(stdout);
+                       while ((c = getchar()) != '\n')
+                               if (c == EOF)
+                                       break;
+                       (void)printf("\n");
+               }
+       }
+}
+
+static void
+printem(int level, int offset, struct highscore *hs, int n, const char *me)
+{
+       struct highscore *sp;
+       int nrows, row, col, item, i, highlight;
+       char buf[100];
+#define        TITLE "Rank  Score   Name     (points/level)"
+
+       /*
+        * This makes a nice two-column sort with headers, but it's a bit
+        * convoluted...
+        */
+       printf("%s   %s\n", TITLE, n > 1 ? TITLE : "");
+
+       highlight = 0;
+       nrows = (n + 1) / 2;
+
+       for (row = 0; row < nrows; row++) {
+               for (col = 0; col < 2; col++) {
+                       item = col * nrows + row;
+                       if (item >= n) {
+                               /*
+                                * Can only occur on trailing columns.
+                                */
+                               (void)putchar('\n');
+                               continue;
+                       }
+                       sp = &hs[item];
+                       (void)snprintf(buf, sizeof(buf),
+                           "%3d%c %6d  %-11s (%6d on %d)",
+                           item + offset, sp->hs_time ? '*' : ' ',
+                           sp->hs_score * sp->hs_level,
+                           sp->hs_name, sp->hs_score, sp->hs_level);
+                       /*
+                        * Highlight if appropriate.  This works because
+                        * we only get one score per level.
+                        */
+                       if (me != NULL &&
+                           sp->hs_level == level &&
+                           sp->hs_score == score &&
+                           strcmp(sp->hs_name, me) == 0) {
+                               putpad(enter_standout_mode);
+                               highlight = 1;
+                       }
+                       (void)printf("%s", buf);
+                       if (highlight) {
+                               putpad(exit_standout_mode);
+                               highlight = 0;
+                       }
+
+                       /* fill in spaces so column 1 lines up */
+                       if (col == 0)
+                               for (i = 40 - strlen(buf); --i >= 0;)
+                                       (void)putchar(' ');
+                       else /* col == 1 */
+                               (void)putchar('\n');
+               }
+       }
+}
diff --git a/games/tetris/scores.h b/games/tetris/scores.h
new file mode 100644 (file)
index 0000000..2d72aec
--- /dev/null
@@ -0,0 +1,87 @@
+/*     $NetBSD: scores.h,v 1.5 2009/05/25 08:33:57 dholland Exp $      */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)scores.h    8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Tetris scores.
+ */
+
+/* Header for high score file. */
+#define HSH_MAGIC_SIZE 8
+struct highscore_header {
+       char hsh_magic[HSH_MAGIC_SIZE];
+       uint32_t hsh_endiantag;
+       uint32_t hsh_version;
+};
+
+/* Current on-disk high score record. */
+struct highscore_ondisk {
+       char hso_name[20];
+       int32_t hso_score;
+       int32_t hso_level;
+       int32_t hso_pad;
+       int64_t hso_time;
+};
+
+/* 5.99.x after time_t change, on 32-bit machines */
+struct highscore_ondisk_599 {
+       char hso599_name[20];
+       int32_t hso599_score;
+       int32_t hso599_level;
+       int32_t hso599_time[2];
+};
+
+/* 5.0 and earlier on-disk high score record. */
+struct highscore_ondisk_50 {
+       char hso50_name[20];
+       int32_t hso50_score;
+       int32_t hso50_level;
+       int32_t hso50_time;
+};
+
+/* In-memory high score record. */
+struct highscore {
+       char    hs_name[20];    /* login name */
+       int     hs_score;       /* raw score */
+       int     hs_level;       /* play level */
+       time_t  hs_time;        /* time at game end */
+};
+
+#define MAXHISCORES    80
+#define MAXSCORES      9       /* maximum high score entries per person */
+#define        EXPIRATION      (5L * 365 * 24 * 60 * 60)
+
+void   savescore(int);
+void   showscores(int);
diff --git a/games/tetris/screen.c b/games/tetris/screen.c
new file mode 100644 (file)
index 0000000..2d58303
--- /dev/null
@@ -0,0 +1,402 @@
+/*     $NetBSD: screen.c,v 1.27 2011/10/03 12:32:28 roy Exp $  */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)screen.c    8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Tetris screen control.
+ */
+
+#include <sys/cdefs.h>
+#include <sys/ioctl.h>
+
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <term.h>
+#include <termios.h>
+#include <unistd.h>
+
+#ifndef sigmask
+#define sigmask(s) (1 << ((s) - 1))
+#endif
+
+#include "screen.h"
+#include "tetris.h"
+
+static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */
+static int curscore;
+static int isset;              /* true => terminal is in game mode */
+static struct termios oldtt;
+static void (*tstp)(int);
+
+static void    scr_stop(int);
+static void    stopset(int) __dead;
+
+
+/*
+ * Routine used by tputs().
+ */
+int
+put(int c)
+{
+
+       return (putchar(c));
+}
+
+/*
+ * putstr() is for unpadded strings (either as in termcap(5) or
+ * simply literal strings); putpad() is for padded strings with
+ * count=1.  (See screen.h for putpad().)
+ */
+#define        putstr(s)       (void)fputs(s, stdout)
+
+static void
+moveto(int r, int c)
+{
+       char *buf;
+
+       buf = tiparm(cursor_address, r, c);
+       if (buf != NULL)
+               putpad(buf);
+}
+
+/*
+ * Set up from termcap.
+ */
+void
+scr_init(void)
+{
+
+       setupterm(NULL, 0, NULL);
+       if (clear_screen == NULL)
+               stop("cannot clear screen");
+       if (cursor_address == NULL || cursor_up == NULL)
+               stop("cannot do random cursor positioning");
+}
+
+/* this foolery is needed to modify tty state `atomically' */
+static jmp_buf scr_onstop;
+
+static void
+stopset(int sig)
+{
+       sigset_t set;
+
+       (void) signal(sig, SIG_DFL);
+       (void) kill(getpid(), sig);
+       sigemptyset(&set);
+       sigaddset(&set, sig);
+       (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0);
+       longjmp(scr_onstop, 1);
+}
+
+static void
+scr_stop(int sig)
+{
+       sigset_t set;
+
+       scr_end();
+       (void) kill(getpid(), sig);
+       sigemptyset(&set);
+       sigaddset(&set, sig);
+       (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0);
+       scr_set();
+       scr_msg(key_msg, 1);
+}
+
+/*
+ * Set up screen mode.
+ */
+void
+scr_set(void)
+{
+       struct winsize ws;
+       struct termios newtt;
+       sigset_t nsigset, osigset;
+       void (*ttou)(int);
+
+       sigemptyset(&nsigset);
+       sigaddset(&nsigset, SIGTSTP);
+       sigaddset(&nsigset, SIGTTOU);
+       (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);
+       if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
+               (void) signal(SIGTSTP, SIG_IGN);
+       if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN)
+               (void) signal(SIGTTOU, SIG_IGN);
+       /*
+        * At last, we are ready to modify the tty state.  If
+        * we stop while at it, stopset() above will longjmp back
+        * to the setjmp here and we will start over.
+        */
+       (void) setjmp(scr_onstop);
+       (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
+       Rows = 0, Cols = 0;
+       if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
+               Rows = ws.ws_row;
+               Cols = ws.ws_col;
+       }
+       if (Rows == 0)
+               Rows = lines;
+       if (Cols == 0)
+           Cols = columns;
+       if (Rows < MINROWS || Cols < MINCOLS) {
+               (void) fprintf(stderr,
+                   "the screen is too small: must be at least %dx%d, ",
+                   MINCOLS, MINROWS);
+               stop("");       /* stop() supplies \n */
+       }
+       if (tcgetattr(0, &oldtt) < 0)
+               stop("tcgetattr() fails");
+       newtt = oldtt;
+       newtt.c_lflag &= ~(ICANON|ECHO);
+       newtt.c_oflag &= ~OXTABS;
+       if (tcsetattr(0, TCSADRAIN, &newtt) < 0)
+               stop("tcsetattr() fails");
+       ospeed = cfgetospeed(&newtt);
+       (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);
+
+       /*
+        * We made it.  We are now in screen mode, modulo TIstr
+        * (which we will fix immediately).
+        */
+       if (enter_ca_mode)
+               putstr(enter_ca_mode);
+       if (cursor_invisible)
+               putstr(cursor_invisible);
+       if (tstp != SIG_IGN)
+               (void) signal(SIGTSTP, scr_stop);
+       if (ttou != SIG_IGN)
+               (void) signal(SIGTTOU, ttou);
+
+       isset = 1;
+       (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
+       scr_clear();
+}
+
+/*
+ * End screen mode.
+ */
+void
+scr_end(void)
+{
+       sigset_t nsigset, osigset;
+
+       sigemptyset(&nsigset);
+       sigaddset(&nsigset, SIGTSTP);
+       sigaddset(&nsigset, SIGTTOU);
+       (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);
+       /* move cursor to last line */
+       if (cursor_to_ll)
+               putstr(cursor_to_ll);
+       else
+               moveto(Rows - 1, 0);
+       /* exit screen mode */
+       if (exit_ca_mode)
+               putstr(exit_ca_mode);
+       if (cursor_normal)
+               putstr(cursor_normal);
+       (void) fflush(stdout);
+       (void) tcsetattr(0, TCSADRAIN, &oldtt);
+       isset = 0;
+       /* restore signals */
+       (void) signal(SIGTSTP, tstp);
+       (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
+}
+
+void
+stop(const char *why)
+{
+
+       if (isset)
+               scr_end();
+       (void) fprintf(stderr, "aborting: %s\n", why);
+       exit(1);
+}
+
+/*
+ * Clear the screen, forgetting the current contents in the process.
+ */
+void
+scr_clear(void)
+{
+
+       putpad(clear_screen);
+       curscore = -1;
+       memset((char *)curscreen, 0, sizeof(curscreen));
+}
+
+#if vax && !__GNUC__
+typedef int regcell;   /* pcc is bad at `register char', etc */
+#else
+typedef cell regcell;
+#endif
+
+/*
+ * Update the screen.
+ */
+void
+scr_update(void)
+{
+       cell *bp, *sp;
+       regcell so, cur_so = 0;
+       int i, ccol, j;
+       sigset_t nsigset, osigset;
+       static const struct shape *lastshape;
+
+       sigemptyset(&nsigset);
+       sigaddset(&nsigset, SIGTSTP);
+       (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset);
+
+       /* always leave cursor after last displayed point */
+       curscreen[D_LAST * B_COLS - 1] = -1;
+
+       if (score != curscore) {
+               if (cursor_home)
+                       putpad(cursor_home);
+               else
+                       moveto(0, 0);
+               (void) printf("Score: %d", score);
+               curscore = score;
+       }
+
+       /* draw preview of nextpattern */
+       if (showpreview && (nextshape != lastshape)) {
+               static int r=5, c=2;
+               int tr, tc, t; 
+
+               lastshape = nextshape;
+               
+               /* clean */
+               putpad(exit_standout_mode);
+               moveto(r-1, c-1); putstr("          ");
+               moveto(r,   c-1); putstr("          ");
+               moveto(r+1, c-1); putstr("          ");
+               moveto(r+2, c-1); putstr("          ");
+
+               moveto(r-3, c-2);
+               putstr("Next shape:");
+                                               
+               /* draw */
+               putpad(enter_standout_mode);
+               moveto(r, 2*c);
+               putstr("  ");
+               for(i=0; i<3; i++) {
+                       t = c + r*B_COLS;
+                       t += nextshape->off[i];
+
+                       tr = t / B_COLS;
+                       tc = t % B_COLS;
+
+                       moveto(tr, 2*tc);
+                       putstr("  ");
+               }
+               putpad(exit_standout_mode);
+       }
+       
+       bp = &board[D_FIRST * B_COLS];
+       sp = &curscreen[D_FIRST * B_COLS];
+       for (j = D_FIRST; j < D_LAST; j++) {
+               ccol = -1;
+               for (i = 0; i < B_COLS; bp++, sp++, i++) {
+                       if (*sp == (so = *bp))
+                               continue;
+                       *sp = so;
+                       if (i != ccol) {
+                               if (cur_so && move_standout_mode) {
+                                       putpad(exit_standout_mode);
+                                       cur_so = 0;
+                               }
+                               moveto(RTOD(j), CTOD(i));
+                       }
+                       if (enter_standout_mode) {
+                               if (so != cur_so) {
+                                       putpad(so ?
+                                           enter_standout_mode :
+                                           exit_standout_mode);
+                                       cur_so = so;
+                               }
+                               putstr("  ");
+                       } else
+                               putstr(so ? "XX" : "  ");
+                       ccol = i + 1;
+                       /*
+                        * Look ahead a bit, to avoid extra motion if
+                        * we will be redrawing the cell after the next.
+                        * Motion probably takes four or more characters,
+                        * so we save even if we rewrite two cells
+                        * `unnecessarily'.  Skip it all, though, if
+                        * the next cell is a different color.
+                        */
+#define        STOP (B_COLS - 3)
+                       if (i > STOP || sp[1] != bp[1] || so != bp[1])
+                               continue;
+                       if (sp[2] != bp[2])
+                               sp[1] = -1;
+                       else if (i < STOP && so == bp[2] && sp[3] != bp[3]) {
+                               sp[2] = -1;
+                               sp[1] = -1;
+                       }
+               }
+       }
+       if (cur_so)
+               putpad(exit_standout_mode);
+       (void) fflush(stdout);
+       (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0);
+}
+
+/*
+ * Write a message (set!=0), or clear the same message (set==0).
+ * (We need its length in case we have to overwrite with blanks.)
+ */
+void
+scr_msg(char *s, int set)
+{
+       
+       if (set || clr_eol == NULL) {
+               int l = strlen(s);
+
+               moveto(Rows - 2, ((Cols - l) >> 1) - 1);
+               if (set)
+                       putstr(s);
+               else
+                       while (--l >= 0)
+                               (void) putchar(' ');
+       } else {
+               moveto(Rows - 2, 0);
+               putpad(clr_eol);
+       }
+}
diff --git a/games/tetris/screen.h b/games/tetris/screen.h
new file mode 100644 (file)
index 0000000..c0b3905
--- /dev/null
@@ -0,0 +1,54 @@
+/*     $NetBSD: screen.h,v 1.9 2009/08/12 08:51:21 dholland Exp $      */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)screen.h    8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Capabilities from TERMCAP (used in the score code).
+ */
+extern char *SEstr;            /* end standout mode */
+extern char *SOstr;            /* begin standout mode */
+
+/*
+ * putpad() is for padded strings with count=1.
+ */
+#define        putpad(s)       tputs(s, 1, put)
+
+int    put(int);               /* just calls putchar; for tputs */
+void   scr_clear(void);
+void   scr_end(void);
+void   scr_init(void);
+void   scr_msg(char *, int);
+void   scr_set(void);
+void   scr_update(void);
diff --git a/games/tetris/shapes.c b/games/tetris/shapes.c
new file mode 100644 (file)
index 0000000..0ab4310
--- /dev/null
@@ -0,0 +1,105 @@
+/*     $NetBSD: shapes.c,v 1.8 2009/05/25 04:33:53 dholland Exp $      */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)shapes.c    8.1 (Berkeley) 5/31/93
+ */
+
+/*
+ * Tetris shapes and related routines.
+ *
+ * Note that the first 7 are `well known'.
+ */
+
+#include <sys/cdefs.h>
+#include "tetris.h"
+
+#define        TL      -B_COLS-1       /* top left */
+#define        TC      -B_COLS         /* top center */
+#define        TR      -B_COLS+1       /* top right */
+#define        ML      -1              /* middle left */
+#define        MR      1               /* middle right */
+#define        BL      B_COLS-1        /* bottom left */
+#define        BC      B_COLS          /* bottom center */
+#define        BR      B_COLS+1        /* bottom right */
+
+const struct shape shapes[] = {
+       /* 0*/  { 7,    { TL, TC, MR, } },
+       /* 1*/  { 8,    { TC, TR, ML, } },
+       /* 2*/  { 9,    { ML, MR, BC, } },
+       /* 3*/  { 3,    { TL, TC, ML, } },
+       /* 4*/  { 12,   { ML, BL, MR, } },
+       /* 5*/  { 15,   { ML, BR, MR, } },
+       /* 6*/  { 18,   { ML, MR, 2   } },      /* sticks out */
+       /* 7*/  { 0,    { TC, ML, BL, } },
+       /* 8*/  { 1,    { TC, MR, BR, } },
+       /* 9*/  { 10,   { TC, MR, BC, } },
+       /*10*/  { 11,   { TC, ML, MR, } },
+       /*11*/  { 2,    { TC, ML, BC, } },
+       /*12*/  { 13,   { TC, BC, BR, } },
+       /*13*/  { 14,   { TR, ML, MR, } },
+       /*14*/  { 4,    { TL, TC, BC, } },
+       /*15*/  { 16,   { TR, TC, BC, } },
+       /*16*/  { 17,   { TL, MR, ML, } },
+       /*17*/  { 5,    { TC, BC, BL, } },
+       /*18*/  { 6,    { TC, BC, 2*B_COLS } }  /* sticks out */
+};
+
+/*
+ * Return true iff the given shape fits in the given position,
+ * taking the current board into account.
+ */
+int
+fits_in(const struct shape *shape, int pos)
+{
+       const int *o = shape->off;
+
+       if (board[pos] || board[pos + *o++] || board[pos + *o++] ||
+           board[pos + *o])
+               return 0;
+       return 1;
+}
+
+/*
+ * Write the given shape into the current board, turning it on
+ * if `onoff' is 1, and off if `onoff' is 0.
+ */
+void
+place(const struct shape *shape, int pos, int onoff)
+{
+       const int *o = shape->off;
+
+       board[pos] = onoff;
+       board[pos + *o++] = onoff;
+       board[pos + *o++] = onoff;
+       board[pos + *o] = onoff;
+}
diff --git a/games/tetris/tetris.6 b/games/tetris/tetris.6
new file mode 100644 (file)
index 0000000..04eca0d
--- /dev/null
@@ -0,0 +1,156 @@
+.\"    $NetBSD: tetris.6,v 1.11 2005/09/15 02:09:42 wiz Exp $
+.\"
+.\" Copyright (c) 1992, 1993
+.\"    The Regents of the University of California.  All rights reserved.
+.\"
+.\" This code is derived from software contributed to Berkeley by
+.\" Nancy L. Tinkham and Darren F. Provine.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\"    notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\"    notice, this list of conditions and the following disclaimer in the
+.\"    documentation and/or other materials provided with the distribution.
+.\" 3. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\"    @(#)tetris.6    8.1 (Berkeley) 5/31/93
+.\"
+.Dd May 31, 1993
+.Dt TETRIS 6
+.Os
+.Sh NAME
+.Nm tetris
+.Nd the game of tetris
+.Sh SYNOPSIS
+.Nm
+.Op Fl ps
+.Op Fl k Ar keys
+.Op Fl l Ar level
+.Sh DESCRIPTION
+The
+.Nm
+command runs display-based game which must be played on a CRT terminal.
+The object is to fit the shapes together forming complete rows,
+which then vanish.
+When the shapes fill up to the top, the game ends.
+You can optionally select a level of play, or custom-select control keys.
+.Pp
+The default level of play is 2.
+.Pp
+The default control keys are as follows:
+.Pp
+.Bl -tag -width "xxspacexx" -compact -offset indent
+.It j
+move left
+.It k
+rotate 1/4 turn counterclockwise
+.It l
+move right
+.It Aq space
+drop
+.It p
+pause
+.It q
+quit
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl k
+The default control keys can be changed using the
+.Fl k
+option.
+The
+.Ar keys
+argument must have the six keys in order, and, remember to quote any
+space or tab characters from the shell.
+For example:
+.sp
+.Dl "tetris -l 2 -k 'jkl pq'"
+.sp
+will play the default games, i.e. level 2 and with the default
+control keys.
+The current key settings are displayed at the bottom of the screen
+during play.
+.It Fl l
+Select a level of play.
+.It Fl s
+Display the top scores.
+.It Fl p
+Switch on previewing of the shape that will appear next.
+.El
+.Sh PLAY
+At the start of the game, a shape will appear at the top of the screen,
+falling one square at a time.
+The speed at which it falls is determined directly by the level:
+if you select level 2, the blocks will fall twice per second;
+at level 9, they fall 9 times per second.
+(As the game goes on, things speed up,
+no matter what your initial selection.)
+When this shape
+.Dq touches down
+on the bottom of the field, another will appear at the top.
+.Pp
+You can move shapes to the left or right, rotate them counterclockwise,
+or drop them to the bottom by pressing the appropriate keys.
+As you fit them together, completed horizontal rows vanish,
+and any blocks above fall down to fill in.
+When the blocks stack up to the top of the screen, the game is over.
+.Sh SCORING
+You get one point for every block you fit into the stack,
+and one point for every space a block falls when you hit the drop key.
+(Dropping the blocks is therefore a good way to increase your score.)
+Your total score is the product of the level of play
+and your accumulated
+.ie t points\(em200
+.el points -- 200
+points on level 3 gives you a score of 600.
+Each player gets at most one entry on any level,
+for a total of nine scores in the high scores file.
+Players who no longer have accounts are limited to one score.
+Also, scores over 5 years old are expired.
+The exception to these conditions is that the highest score on a given
+level is
+.Em always
+kept,
+so that following generations can pay homage to those who have
+wasted serious amounts of time.
+.Pp
+The score list is produced at the end of the game.
+The printout includes each player's overall ranking,
+name, score, and how many points were scored on what level.
+Scores which are the highest on a given level
+are marked with asterisks
+.Dq * .
+.Sh FILES
+.Bl -tag -width /var/games/tetris.scoresxx
+.It /var/games/tetris.scores
+high score file
+.El
+.Sh AUTHORS
+Adapted from a 1989 International Obfuscated C Code Contest winner by
+Chris Torek and Darren F. Provine.
+.Pp
+Manual adapted from the original entry written by Nancy L. Tinkham and
+Darren F. Provine.
+.Pp
+Code for previewing next shape added by Hubert Feyrer in 1999.
+.Sh BUGS
+The higher levels are unplayable without a fast terminal connection.
diff --git a/games/tetris/tetris.c b/games/tetris/tetris.c
new file mode 100644 (file)
index 0000000..4943249
--- /dev/null
@@ -0,0 +1,334 @@
+/*     $NetBSD: tetris.c,v 1.24 2011/08/31 16:24:56 plunky Exp $       */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)tetris.c    8.1 (Berkeley) 5/31/93
+ */
+
+#include <sys/cdefs.h>
+#ifndef lint
+__COPYRIGHT("@(#) Copyright (c) 1992, 1993\
+ The Regents of the University of California.  All rights reserved.");
+#endif /* not lint */
+
+/*
+ * Tetris (or however it is spelled).
+ */
+
+#include <sys/time.h>
+
+#include <err.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "input.h"
+#include "scores.h"
+#include "screen.h"
+#include "tetris.h"
+
+cell   board[B_SIZE];          /* 1 => occupied, 0 => empty */
+
+int    Rows, Cols;             /* current screen size */
+
+static const struct shape *curshape;
+const struct shape *nextshape;
+
+long   fallrate;               /* less than 1 million; smaller => faster */
+
+int    score;                  /* the obvious thing */
+gid_t  gid, egid;
+
+char   key_msg[100];
+int    showpreview;
+
+static void elide(void);
+static void setup_board(void);
+static void onintr(int) __dead;
+static void usage(void) __dead;
+
+/*
+ * Set up the initial board.  The bottom display row is completely set,
+ * along with another (hidden) row underneath that.  Also, the left and
+ * right edges are set.
+ */
+static void
+setup_board(void)
+{
+       int i;
+       cell *p;
+
+       p = board;
+       for (i = B_SIZE; i; i--)
+               *p++ = i <= (2 * B_COLS) || (i % B_COLS) < 2;
+}
+
+/*
+ * Elide any full active rows.
+ */
+static void
+elide(void)
+{
+       int i, j, base;
+       cell *p;
+
+       for (i = A_FIRST; i < A_LAST; i++) {
+               base = i * B_COLS + 1;
+               p = &board[base];
+               for (j = B_COLS - 2; *p++ != 0;) {
+                       if (--j <= 0) {
+                               /* this row is to be elided */
+                               memset(&board[base], 0, B_COLS - 2);
+                               scr_update();
+                               tsleep();
+                               while (--base != 0)
+                                       board[base + B_COLS] = board[base];
+                               scr_update();
+                               tsleep();
+                               break;
+                       }
+               }
+       }
+}
+
+int
+main(int argc, char *argv[])
+{
+       int pos, c;
+       const char *keys;
+       int level = 2;
+       char key_write[6][10];
+       int ch, i, j;
+       int fd;
+
+       gid = getgid();
+       egid = getegid();
+       setegid(gid);
+
+       fd = open("/dev/null", O_RDONLY);
+       if (fd < 3)
+               exit(1);
+       close(fd);
+
+       keys = "jkl pq";
+
+       while ((ch = getopt(argc, argv, "k:l:ps")) != -1)
+               switch(ch) {
+               case 'k':
+                       if (strlen(keys = optarg) != 6)
+                               usage();
+                       break;
+               case 'l':
+                       level = atoi(optarg);
+                       if (level < MINLEVEL || level > MAXLEVEL) {
+                               errx(1, "level must be from %d to %d",
+                                    MINLEVEL, MAXLEVEL);
+                       }
+                       break;
+               case 'p':
+                       showpreview = 1;
+                       break;
+               case 's':
+                       showscores(0);
+                       exit(0);
+               case '?':
+               default:
+                       usage();
+               }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc)
+               usage();
+
+       fallrate = 1000000 / level;
+
+       for (i = 0; i <= 5; i++) {
+               for (j = i+1; j <= 5; j++) {
+                       if (keys[i] == keys[j]) {
+                               errx(1, "duplicate command keys specified.");
+                       }
+               }
+               if (keys[i] == ' ')
+                       strcpy(key_write[i], "<space>");
+               else {
+                       key_write[i][0] = keys[i];
+                       key_write[i][1] = '\0';
+               }
+       }
+
+       snprintf(key_msg, sizeof(key_msg),
+"%s - left   %s - rotate   %s - right   %s - drop   %s - pause   %s - quit",
+               key_write[0], key_write[1], key_write[2], key_write[3],
+               key_write[4], key_write[5]);
+
+       (void)signal(SIGINT, onintr);
+       scr_init();
+       setup_board();
+
+       srandom(getpid());
+       scr_set();
+
+       pos = A_FIRST*B_COLS + (B_COLS/2)-1;
+       nextshape = randshape();
+       curshape = randshape();
+
+       scr_msg(key_msg, 1);
+
+       for (;;) {
+               place(curshape, pos, 1);
+               scr_update();
+               place(curshape, pos, 0);
+               c = tgetchar();
+               if (c < 0) {
+                       /*
+                        * Timeout.  Move down if possible.
+                        */
+                       if (fits_in(curshape, pos + B_COLS)) {
+                               pos += B_COLS;
+                               continue;
+                       }
+
+                       /*
+                        * Put up the current shape `permanently',
+                        * bump score, and elide any full rows.
+                        */
+                       place(curshape, pos, 1);
+                       score++;
+                       elide();
+
+                       /*
+                        * Choose a new shape.  If it does not fit,
+                        * the game is over.
+                        */
+                       curshape = nextshape;
+                       nextshape = randshape();
+                       pos = A_FIRST*B_COLS + (B_COLS/2)-1;
+                       if (!fits_in(curshape, pos))
+                               break;
+                       continue;
+               }
+
+               /*
+                * Handle command keys.
+                */
+               if (c == keys[5]) {
+                       /* quit */
+                       break;
+               }
+               if (c == keys[4]) {
+                       static char msg[] =
+                           "paused - press RETURN to continue";
+
+                       place(curshape, pos, 1);
+                       do {
+                               scr_update();
+                               scr_msg(key_msg, 0);
+                               scr_msg(msg, 1);
+                               (void) fflush(stdout);
+                       } while (rwait(NULL) == -1);
+                       scr_msg(msg, 0);
+                       scr_msg(key_msg, 1);
+                       place(curshape, pos, 0);
+                       continue;
+               }
+               if (c == keys[0]) {
+                       /* move left */
+                       if (fits_in(curshape, pos - 1))
+                               pos--;
+                       continue;
+               }
+               if (c == keys[1]) {
+                       /* turn */
+                       const struct shape *new = &shapes[curshape->rot];
+
+                       if (fits_in(new, pos))
+                               curshape = new;
+                       continue;
+               }
+               if (c == keys[2]) {
+                       /* move right */
+                       if (fits_in(curshape, pos + 1))
+                               pos++;
+                       continue;
+               }
+               if (c == keys[3]) {
+                       /* move to bottom */
+                       while (fits_in(curshape, pos + B_COLS)) {
+                               pos += B_COLS;
+                               score++;
+                       }
+                       continue;
+               }
+               if (c == '\f') {
+                       scr_clear();
+                       scr_msg(key_msg, 1);
+               }
+       }
+
+       scr_clear();
+       scr_end();
+
+       (void)printf("Your score:  %d point%s  x  level %d  =  %d\n",
+           score, score == 1 ? "" : "s", level, score * level);
+       savescore(level);
+
+       printf("\nHit RETURN to see high scores, ^C to skip.\n");
+
+       while ((i = getchar()) != '\n')
+               if (i == EOF)
+                       break;
+
+       showscores(level);
+
+       exit(0);
+}
+
+static void
+onintr(int signo __unused)
+{
+       scr_clear();
+       scr_end();
+       exit(0);
+}
+
+static void
+usage(void)
+{
+       (void)fprintf(stderr, "usage: %s [-ps] [-k keys] [-l level]\n",
+           getprogname());
+       exit(1);
+}
diff --git a/games/tetris/tetris.h b/games/tetris/tetris.h
new file mode 100644 (file)
index 0000000..37f0d9b
--- /dev/null
@@ -0,0 +1,173 @@
+/*     $NetBSD: tetris.h,v 1.12 2009/08/12 08:51:21 dholland Exp $     */
+
+/*-
+ * Copyright (c) 1992, 1993
+ *     The Regents of the University of California.  All rights reserved.
+ *
+ * This code is derived from software contributed to Berkeley by
+ * Chris Torek and Darren F. Provine.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ *     @(#)tetris.h    8.1 (Berkeley) 5/31/93
+ */
+
+#include <sys/types.h>
+
+/*
+ * Definitions for Tetris.
+ */
+
+/*
+ * The display (`board') is composed of 23 rows of 12 columns of characters
+ * (numbered 0..22 and 0..11), stored in a single array for convenience.
+ * Columns 1 to 10 of rows 1 to 20 are the actual playing area, where
+ * shapes appear.  Columns 0 and 11 are always occupied, as are all
+ * columns of rows 21 and 22.  Rows 0 and 22 exist as boundary areas
+ * so that regions `outside' the visible area can be examined without
+ * worrying about addressing problems.
+ */
+
+       /* the board */
+#define        B_COLS  12
+#define        B_ROWS  23
+#define        B_SIZE  (B_ROWS * B_COLS)
+
+typedef unsigned char cell;
+extern cell    board[B_SIZE];  /* 1 => occupied, 0 => empty */
+
+       /* the displayed area (rows) */
+#define        D_FIRST 1
+#define        D_LAST  22
+
+       /* the active area (rows) */
+#define        A_FIRST 1
+#define        A_LAST  21
+
+/*
+ * Minimum display size.
+ */
+#define        MINROWS 23
+#define        MINCOLS 40
+
+extern int     Rows, Cols;     /* current screen size */
+
+/*
+ * Translations from board coordinates to display coordinates.
+ * As with board coordinates, display coordiates are zero origin.
+ */
+#define        RTOD(x) ((x) - 1)
+#define        CTOD(x) ((x) * 2 + (((Cols - 2 * B_COLS) >> 1) - 1))
+
+/*
+ * A `shape' is the fundamental thing that makes up the game.  There
+ * are 7 basic shapes, each consisting of four `blots':
+ *
+ *     X.X       X.X           X.X
+ *       X.X   X.X     X.X.X   X.X     X.X.X   X.X.X   X.X.X.X
+ *                       X             X           X
+ *
+ *       0       1       2       3       4       5       6
+ *
+ * Except for 3 and 6, the center of each shape is one of the blots.
+ * This blot is designated (0,0).  The other three blots can then be
+ * described as offsets from the center.  Shape 3 is the same under
+ * rotation, so its center is effectively irrelevant; it has been chosen
+ * so that it `sticks out' upward and leftward.  Except for shape 6,
+ * all the blots are contained in a box going from (-1,-1) to (+1,+1);
+ * shape 6's center `wobbles' as it rotates, so that while it `sticks out'
+ * rightward, its rotation---a vertical line---`sticks out' downward.
+ * The containment box has to include the offset (2,0), making the overall
+ * containment box range from offset (-1,-1) to (+2,+1).  (This is why
+ * there is only one row above, but two rows below, the display area.)
+ *
+ * The game works by choosing one of these shapes at random and putting
+ * its center at the middle of the first display row (row 1, column 5).
+ * The shape is moved steadily downward until it collides with something:
+ * either  another shape, or the bottom of the board.  When the shape can
+ * no longer be moved downwards, it is merged into the current board.
+ * At this time, any completely filled rows are elided, and blots above
+ * these rows move down to make more room.  A new random shape is again
+ * introduced at the top of the board, and the whole process repeats.
+ * The game ends when the new shape will not fit at (1,5).
+ *
+ * While the shapes are falling, the user can rotate them counterclockwise
+ * 90 degrees (in addition to moving them left or right), provided that the
+ * rotation puts the blots in empty spaces.  The table of shapes is set up
+ * so that each shape contains the index of the new shape obtained by
+ * rotating the current shape.  Due to symmetry, each shape has exactly
+ * 1, 2, or 4 rotations total; the first 7 entries in the table represent
+ * the primary shapes, and the remaining 12 represent their various
+ * rotated forms.
+ */
+struct shape {
+       int     rot;    /* index of rotated version of this shape */
+       int     off[3]; /* offsets to other blots if center is at (0,0) */
+};
+
+extern const struct shape shapes[];
+#define        randshape() (&shapes[random() % 7])
+
+extern const struct shape *nextshape;
+
+/*
+ * Shapes fall at a rate faster than once per second.
+ *
+ * The initial rate is determined by dividing 1 million microseconds
+ * by the game `level'.  (This is at most 1 million, or one second.)
+ * Each time the fall-rate is used, it is decreased a little bit,
+ * depending on its current value, via the `faster' macro below.
+ * The value eventually reaches a limit, and things stop going faster,
+ * but by then the game is utterly impossible.
+ */
+extern long    fallrate;       /* less than 1 million; smaller => faster */
+#define        faster() (fallrate -= fallrate / 3000)
+
+/*
+ * Game level must be between 1 and 9.  This controls the initial fall rate
+ * and affects scoring.
+ */
+#define        MINLEVEL        1
+#define        MAXLEVEL        9
+
+/*
+ * Scoring is as follows:
+ *
+ * When the shape comes to rest, and is integrated into the board,
+ * we score one point.  If the shape is high up (at a low-numbered row),
+ * and the user hits the space bar, the shape plummets all the way down,
+ * and we score a point for each row it falls (plus one more as soon as
+ * we find that it is at rest and integrate it---until then, it can
+ * still be moved or rotated).
+ */
+extern int     score;          /* the obvious thing */
+extern gid_t   gid, egid;
+
+extern char    key_msg[100];
+extern int     showpreview;
+
+int    fits_in(const struct shape *, int);
+void   place(const struct shape *, int, int);
+void   stop(const char *) __dead;
index 2eb5aa4f8952fb951af8b09dd92216cf58a96828..2b0163fbc030443538399649f2aa8530dcd5c8b8 100644 (file)
@@ -82,6 +82,7 @@
 2013/12/1 12:00:00,games/ppt
 2013/12/1 12:00:00,games/primes
 2013/12/1 12:00:00,games/random
+2013/12/1 12:00:00,games/tetris
 2013/12/1 12:00:00,games/wargames
 2013/12/1 12:00:00,gnu/dist/texinfo
 2013/12/1 12:00:00,gnu/usr.bin/texinfo