summaryrefslogtreecommitdiffstats
path: root/tetris
diff options
context:
space:
mode:
authorcgd <cgd@NetBSD.org>1994-05-06 06:50:50 +0000
committercgd <cgd@NetBSD.org>1994-05-06 06:50:50 +0000
commit1a4c6defc63314df63eb514291290a64ff2c73b0 (patch)
tree9bc0ce864e36ca443574b59eb852c3a60926e794 /tetris
parent64659f78b3eda22f05a1683c2b356977ea22705e (diff)
downloadbsdgames-darwin-1a4c6defc63314df63eb514291290a64ff2c73b0.tar.gz
bsdgames-darwin-1a4c6defc63314df63eb514291290a64ff2c73b0.tar.zst
bsdgames-darwin-1a4c6defc63314df63eb514291290a64ff2c73b0.zip
ascii tetris!
Diffstat (limited to 'tetris')
-rw-r--r--tetris/Makefile10
-rw-r--r--tetris/input.c180
-rw-r--r--tetris/input.h42
-rw-r--r--tetris/pathnames.h39
-rw-r--r--tetris/scores.c472
-rw-r--r--tetris/scores.h54
-rw-r--r--tetris/screen.c454
-rw-r--r--tetris/screen.h56
-rw-r--r--tetris/shapes.c111
-rw-r--r--tetris/tetris.6154
-rw-r--r--tetris/tetris.c312
-rw-r--r--tetris/tetris.h169
12 files changed, 2053 insertions, 0 deletions
diff --git a/tetris/Makefile b/tetris/Makefile
new file mode 100644
index 00000000..88a59c38
--- /dev/null
+++ b/tetris/Makefile
@@ -0,0 +1,10 @@
+# @(#)Makefile 8.1 (Berkeley) 5/31/93
+
+PROG= tetris
+SRCS= input.c screen.c shapes.c scores.c tetris.c
+MAN6= tetris.0
+DPADD= ${LIBTERM}
+LDADD= -ltermcap
+HIDEGAME=hidegame
+
+.include <bsd.prog.mk>
diff --git a/tetris/input.c b/tetris/input.c
new file mode 100644
index 00000000..48da0884
--- /dev/null
+++ b/tetris/input.c
@@ -0,0 +1,180 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 <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': select 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 select is interrupted.
+ *
+ * Return 0 => no input, 1 => can read() from stdin
+ */
+int
+rwait(tvp)
+ register struct timeval *tvp;
+{
+ int i;
+ struct timeval starttv, endtv, *s;
+ extern int errno;
+#define NILTZ ((struct timezone *)0)
+
+ /*
+ * Someday, select() will do this for us.
+ * Just in case that day is now, and no one has
+ * changed this, we use a temporary.
+ */
+ if (tvp) {
+ (void) gettimeofday(&starttv, NILTZ);
+ endtv = *tvp;
+ s = &endtv;
+ } else
+ s = 0;
+again:
+ i = 1;
+ switch (select(1, (fd_set *)&i, (fd_set *)0, (fd_set *)0, s)) {
+
+ case -1:
+ if (tvp == 0)
+ return (-1);
+ if (errno == EINTR)
+ goto again;
+ stop("select failed, help");
+ /* NOTREACHED */
+
+ case 0: /* timed out */
+ 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 (using select).
+ * Eat any input that might be available.
+ */
+void
+tsleep()
+{
+ 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;
+}
+
+/*
+ * Eat up any input (used at end of game).
+ */
+void
+eat_input()
+{
+ struct timeval tv;
+ char c;
+
+ do {
+ tv.tv_sec = tv.tv_usec = 0;
+ } while (rwait(&tv) && read(0, &c, 1) == 1);
+}
+
+/*
+ * getchar with timeout.
+ */
+int
+tgetchar()
+{
+ 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/tetris/input.h b/tetris/input.h
new file mode 100644
index 00000000..733182d7
--- /dev/null
+++ b/tetris/input.h
@@ -0,0 +1,42 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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
+ */
+
+void eat_input __P((void));
+int rwait __P((struct timeval *));
+int tgetchar __P((void));
+void tsleep __P((void));
diff --git a/tetris/pathnames.h b/tetris/pathnames.h
new file mode 100644
index 00000000..ba08dd11
--- /dev/null
+++ b/tetris/pathnames.h
@@ -0,0 +1,39 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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/tetris/scores.c b/tetris/scores.c
new file mode 100644
index 00000000..e7e6bb59
--- /dev/null
+++ b/tetris/scores.c
@@ -0,0 +1,472 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+/*
+ * XXX - need a <termcap.h>
+ */
+int tputs __P((const char *, int, int (*)(int)));
+
+#include "pathnames.h"
+#include "screen.h"
+#include "scores.h"
+#include "tetris.h"
+
+/*
+ * 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 __P((struct highscore *, int));
+static int cmpscores __P((const void *, const void *));
+static void getscores __P((FILE **));
+static void printem __P((int, int, struct highscore *, int, const char *));
+static char *thisuser __P((void));
+
+/*
+ * 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 *fpp to an open file pointer 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.
+ *
+ * Note, we assume closing the stdio file releases the lock.
+ */
+static void
+getscores(fpp)
+ FILE **fpp;
+{
+ int sd, mint, lck;
+ char *mstr, *human;
+ FILE *sf;
+
+ if (fpp != NULL) {
+ mint = O_RDWR | O_CREAT;
+ mstr = "r+";
+ human = "read/write";
+ lck = LOCK_EX;
+ } else {
+ mint = O_RDONLY;
+ mstr = "r";
+ human = "reading";
+ lck = LOCK_SH;
+ }
+ sd = open(_PATH_SCOREFILE, mint, 0666);
+ if (sd < 0) {
+ if (fpp == NULL) {
+ nscores = 0;
+ return;
+ }
+ (void)fprintf(stderr, "tetris: cannot open %s for %s: %s\n",
+ _PATH_SCOREFILE, human, strerror(errno));
+ exit(1);
+ }
+ if ((sf = fdopen(sd, mstr)) == NULL) {
+ (void)fprintf(stderr, "tetris: cannot fdopen %s for %s: %s\n",
+ _PATH_SCOREFILE, human, strerror(errno));
+ exit(1);
+ }
+
+ /*
+ * Grab a lock.
+ */
+ if (flock(sd, lck))
+ (void)fprintf(stderr,
+ "tetris: warning: score file %s cannot be locked: %s\n",
+ _PATH_SCOREFILE, strerror(errno));
+
+ nscores = fread(scores, sizeof(scores[0]), MAXHISCORES, sf);
+ if (ferror(sf)) {
+ (void)fprintf(stderr, "tetris: error reading %s: %s\n",
+ _PATH_SCOREFILE, strerror(errno));
+ exit(1);
+ }
+
+ if (fpp)
+ *fpp = sf;
+ else
+ (void)fclose(sf);
+}
+
+void
+savescore(level)
+ int level;
+{
+ register struct highscore *sp;
+ register int i;
+ int change;
+ FILE *sf;
+ const char *me;
+
+ getscores(&sf);
+ 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);
+ rewind(sf);
+ if (fwrite(scores, sizeof(*sp), nscores, sf) != nscores ||
+ fflush(sf) == EOF)
+ (void)fprintf(stderr,
+ "tetris: error writing %s: %s -- %s\n",
+ _PATH_SCOREFILE, strerror(errno),
+ "high scores may be damaged");
+ }
+ (void)fclose(sf); /* releases lock */
+}
+
+/*
+ * Get login name, or if that fails, get something suitable.
+ * The result is always trimmed to fit in a score.
+ */
+static char *
+thisuser()
+{
+ register const char *p;
+ register struct passwd *pw;
+ register 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;
+ bcopy(p, u, 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(x, y)
+ const void *x, *y;
+{
+ register const struct highscore *a, *b;
+ register 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(hs, num)
+ register struct highscore *hs;
+ int num;
+{
+ register struct highscore *sp;
+ register int i, j, k, numnames;
+ int levelfound[NLEVELS];
+ struct peruser {
+ char *name;
+ int times;
+ } count[NUMSPOTS];
+ register 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;
+ }
+ }
+ 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(level)
+ int level;
+{
+ register struct highscore *sp;
+ register int i, n, c;
+ const char *me;
+ int levelfound[NLEVELS];
+
+ if (!gotscores)
+ getscores((FILE **)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 && SOstr ? 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 (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(level, offset, hs, n, me)
+ int level, offset;
+ register struct highscore *hs;
+ register int n;
+ const char *me;
+{
+ register 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;
+ }
+ (void)printf(item + offset < 10 ? " " : " ");
+ sp = &hs[item];
+ (void)sprintf(buf,
+ "%d%c %6d %-11s (%d 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(SOstr);
+ highlight = 1;
+ }
+ (void)printf("%s", buf);
+ if (highlight) {
+ putpad(SEstr);
+ 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/tetris/scores.h b/tetris/scores.h
new file mode 100644
index 00000000..7a4865ad
--- /dev/null
+++ b/tetris/scores.h
@@ -0,0 +1,54 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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.
+ */
+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 __P((int));
+void showscores __P((int));
diff --git a/tetris/screen.c b/tetris/screen.c
new file mode 100644
index 00000000..41e3f4a8
--- /dev/null
+++ b/tetris/screen.c
@@ -0,0 +1,454 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 <sgtty.h>
+#include <sys/ioctl.h>
+
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifndef sigmask
+#define sigmask(s) (1 << ((s) - 1))
+#endif
+
+#include "screen.h"
+#include "tetris.h"
+
+/*
+ * XXX - need a <termcap.h>
+ */
+int tgetent __P((char *, const char *));
+int tgetflag __P((const char *));
+int tgetnum __P((const char *));
+int tputs __P((const char *, int, int (*)(int)));
+
+static cell curscreen[B_SIZE]; /* 1 => standout (or otherwise marked) */
+static int curscore;
+static int isset; /* true => terminal is in game mode */
+static struct sgttyb oldtt;
+static void (*tstp)();
+
+char *tgetstr(), *tgoto();
+
+
+/*
+ * Capabilities from TERMCAP.
+ */
+char PC, *BC, *UP; /* tgoto requires globals: ugh! */
+short ospeed;
+
+static char
+ *bcstr, /* backspace char */
+ *CEstr, /* clear to end of line */
+ *CLstr, /* clear screen */
+ *CMstr, /* cursor motion string */
+#ifdef unneeded
+ *CRstr, /* "\r" equivalent */
+#endif
+ *HOstr, /* cursor home */
+ *LLstr, /* last line, first column */
+ *pcstr, /* pad character */
+ *TEstr, /* end cursor motion mode */
+ *TIstr; /* begin cursor motion mode */
+char
+ *SEstr, /* end standout mode */
+ *SOstr; /* begin standout mode */
+static int
+ COnum, /* co# value */
+ LInum, /* li# value */
+ MSflag; /* can move in standout mode */
+
+
+struct tcsinfo { /* termcap string info; some abbrevs above */
+ char tcname[3];
+ char **tcaddr;
+} tcstrings[] = {
+ "bc", &bcstr,
+ "ce", &CEstr,
+ "cl", &CLstr,
+ "cm", &CMstr,
+#ifdef unneeded
+ "cr", &CRstr,
+#endif
+ "le", &BC, /* move cursor left one space */
+ "pc", &pcstr,
+ "se", &SEstr,
+ "so", &SOstr,
+ "te", &TEstr,
+ "ti", &TIstr,
+ "up", &UP, /* cursor up */
+ 0
+};
+
+/* This is where we will actually stuff the information */
+
+static char combuf[1024], tbuf[1024];
+
+
+/*
+ * Routine used by tputs().
+ */
+int
+put(c)
+ 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)
+#define moveto(r, c) putpad(tgoto(CMstr, c, r))
+
+/*
+ * Set up from termcap.
+ */
+void
+scr_init()
+{
+ static int bsflag, xsflag, sgnum;
+#ifdef unneeded
+ static int ncflag;
+#endif
+ char *term, *fill;
+ static struct tcninfo { /* termcap numeric and flag info */
+ char tcname[3];
+ int *tcaddr;
+ } tcflags[] = {
+ "bs", &bsflag,
+ "ms", &MSflag,
+#ifdef unneeded
+ "nc", &ncflag,
+#endif
+ "xs", &xsflag,
+ 0
+ }, tcnums[] = {
+ "co", &COnum,
+ "li", &LInum,
+ "sg", &sgnum,
+ 0
+ };
+
+ if ((term = getenv("TERM")) == NULL)
+ stop("you must set the TERM environment variable");
+ if (tgetent(tbuf, term) <= 0)
+ stop("cannot find your termcap");
+ fill = combuf;
+ {
+ register struct tcsinfo *p;
+
+ for (p = tcstrings; p->tcaddr; p++)
+ *p->tcaddr = tgetstr(p->tcname, &fill);
+ }
+ {
+ register struct tcninfo *p;
+
+ for (p = tcflags; p->tcaddr; p++)
+ *p->tcaddr = tgetflag(p->tcname);
+ for (p = tcnums; p->tcaddr; p++)
+ *p->tcaddr = tgetnum(p->tcname);
+ }
+ if (bsflag)
+ BC = "\b";
+ else if (BC == NULL && bcstr != NULL)
+ BC = bcstr;
+ if (CLstr == NULL)
+ stop("cannot clear screen");
+ if (CMstr == NULL || UP == NULL || BC == NULL)
+ stop("cannot do random cursor positioning via tgoto()");
+ PC = pcstr ? *pcstr : 0;
+ if (sgnum >= 0 || xsflag)
+ SOstr = SEstr = NULL;
+#ifdef unneeded
+ if (ncflag)
+ CRstr = NULL;
+ else if (CRstr == NULL)
+ CRstr = "\r";
+#endif
+}
+
+/* this foolery is needed to modify tty state `atomically' */
+static jmp_buf scr_onstop;
+
+#define sigunblock(mask) sigsetmask(sigblock(0) & ~(mask))
+
+static void
+stopset(sig)
+ int sig;
+{
+ (void) signal(sig, SIG_DFL);
+ (void) kill(getpid(), sig);
+ (void) sigunblock(sigmask(sig));
+ longjmp(scr_onstop, 1);
+}
+
+static void
+scr_stop()
+{
+ scr_end();
+ (void) kill(getpid(), SIGTSTP);
+ (void) sigunblock(sigmask(SIGTSTP));
+ scr_set();
+ scr_msg(key_msg, 1);
+}
+
+/*
+ * Set up screen mode.
+ */
+void
+scr_set()
+{
+ struct winsize ws;
+ struct sgttyb newtt;
+ volatile int omask;
+ void (*ttou)();
+
+ omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
+ if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN)
+ (void) signal(SIGTSTP, SIG_IGN);
+ if ((ttou = signal(SIGTSTP, stopset)) == SIG_IGN)
+ (void) signal(SIGTSTP, 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) sigsetmask(omask);
+ Rows = 0, Cols = 0;
+ if (ioctl(0, TIOCGWINSZ, &ws) == 0) {
+ Rows = ws.ws_row;
+ Cols = ws.ws_col;
+ }
+ if (Rows == 0)
+ Rows = LInum;
+ if (Cols == 0)
+ Cols = COnum;
+ if (Rows < MINROWS || Cols < MINCOLS) {
+ (void) fprintf(stderr,
+ "the screen is too small: must be at least %d x %d",
+ MINROWS, MINCOLS);
+ stop(""); /* stop() supplies \n */
+ }
+ if (ioctl(0, TIOCGETP, &oldtt))
+ stop("ioctl(TIOCGETP) fails");
+ newtt = oldtt;
+ newtt.sg_flags = (newtt.sg_flags | CBREAK) & ~(CRMOD | ECHO);
+ if ((newtt.sg_flags & TBDELAY) == XTABS)
+ newtt.sg_flags &= ~TBDELAY;
+ if (ioctl(0, TIOCSETN, &newtt))
+ stop("ioctl(TIOCSETN) fails");
+ ospeed = newtt.sg_ospeed;
+ omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
+
+ /*
+ * We made it. We are now in screen mode, modulo TIstr
+ * (which we will fix immediately).
+ */
+ if (TIstr)
+ putstr(TIstr); /* termcap(5) says this is not padded */
+ if (tstp != SIG_IGN)
+ (void) signal(SIGTSTP, scr_stop);
+ (void) signal(SIGTTOU, ttou);
+
+ isset = 1;
+ (void) sigsetmask(omask);
+ scr_clear();
+}
+
+/*
+ * End screen mode.
+ */
+void
+scr_end()
+{
+ int omask = sigblock(sigmask(SIGTSTP) | sigmask(SIGTTOU));
+
+ /* move cursor to last line */
+ if (LLstr)
+ putstr(LLstr); /* termcap(5) says this is not padded */
+ else
+ moveto(Rows - 1, 0);
+ /* exit screen mode */
+ if (TEstr)
+ putstr(TEstr); /* termcap(5) says this is not padded */
+ (void) fflush(stdout);
+ (void) ioctl(0, TIOCSETN, &oldtt);
+ isset = 0;
+ /* restore signals */
+ (void) signal(SIGTSTP, tstp);
+ (void) sigsetmask(omask);
+}
+
+void
+stop(why)
+ 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()
+{
+
+ putpad(CLstr);
+ curscore = -1;
+ bzero((char *)curscreen, 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()
+{
+ register cell *bp, *sp;
+ register regcell so, cur_so = 0;
+ register int i, ccol, j;
+ int omask = sigblock(sigmask(SIGTSTP));
+
+ /* always leave cursor after last displayed point */
+ curscreen[D_LAST * B_COLS - 1] = -1;
+
+ if (score != curscore) {
+ if (HOstr)
+ putpad(HOstr);
+ else
+ moveto(0, 0);
+ (void) printf("%d", score);
+ curscore = score;
+ }
+
+ 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 && MSflag) {
+ putpad(SEstr);
+ cur_so = 0;
+ }
+ moveto(RTOD(j), CTOD(i));
+ }
+ if (SOstr) {
+ if (so != cur_so) {
+ putpad(so ? SOstr : SEstr);
+ 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(SEstr);
+ (void) fflush(stdout);
+ (void) sigsetmask(omask);
+}
+
+/*
+ * 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(s, set)
+ register char *s;
+ int set;
+{
+
+ if (set || CEstr == NULL) {
+ register 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(CEstr);
+ }
+}
diff --git a/tetris/screen.h b/tetris/screen.h
new file mode 100644
index 00000000..fd6793d1
--- /dev/null
+++ b/tetris/screen.h
@@ -0,0 +1,56 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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).
+ */
+char *SEstr; /* end standout mode */
+char *SOstr; /* begin standout mode */
+
+/*
+ * putpad() is for padded strings with count=1.
+ */
+#define putpad(s) tputs(s, 1, put)
+
+int put __P((int)); /* just calls putchar; for tputs */
+void scr_clear __P((void));
+void scr_end __P((void));
+void scr_init __P((void));
+void scr_msg __P((char *, int));
+void scr_set __P((void));
+void scr_update __P((void));
diff --git a/tetris/shapes.c b/tetris/shapes.c
new file mode 100644
index 00000000..39ba3788
--- /dev/null
+++ b/tetris/shapes.c
@@ -0,0 +1,111 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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 */
+
+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, /* sticks out */ 2,
+ /* 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, /* sticks out */ 2*B_COLS,
+};
+
+/*
+ * Return true iff the given shape fits in the given position,
+ * taking the current board into account.
+ */
+int
+fits_in(shape, pos)
+ struct shape *shape;
+ register int pos;
+{
+ register 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(shape, pos, onoff)
+ struct shape *shape;
+ register int pos, onoff;
+{
+ register int *o = shape->off;
+
+ board[pos] = onoff;
+ board[pos + *o++] = onoff;
+ board[pos + *o++] = onoff;
+ board[pos + *o] = onoff;
+}
diff --git a/tetris/tetris.6 b/tetris/tetris.6
new file mode 100644
index 00000000..9513ca05
--- /dev/null
+++ b/tetris/tetris.6
@@ -0,0 +1,154 @@
+.\" 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. All advertising materials mentioning features or use of this software
+.\" must display the following acknowledgement:
+.\" This product includes software developed by the University of
+.\" California, Berkeley and its contributors.
+.\" 4. 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 s
+.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 "<space>" -compact -offset indent
+.It j
+move left
+.It k
+rotate 1/4 turn counterclockwise
+.It l
+move right
+.It <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.
+.El
+.Pp
+.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 BUGS
+The higher levels are unplayable without a fast terminal connection.
+.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.
diff --git a/tetris/tetris.c b/tetris/tetris.c
new file mode 100644
index 00000000..63265bc6
--- /dev/null
+++ b/tetris/tetris.c
@@ -0,0 +1,312 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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
+ */
+
+#ifndef lint
+static char copyright[] =
+"@(#) Copyright (c) 1992, 1993\n\
+ The Regents of the University of California. All rights reserved.\n";
+#endif /* not lint */
+
+/*
+ * Tetris (or however it is spelled).
+ */
+
+#include <sys/time.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"
+
+void onintr __P((int));
+void usage __P((void));
+
+/*
+ * 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()
+{
+ register int i;
+ register cell *p;
+
+ p = board;
+ for (i = B_SIZE; i; i--)
+#ifndef mips
+ *p++ = i <= (2 * B_COLS) || (i % B_COLS) < 2;
+#else /* work around compiler bug */
+ *p++ = i <= (2 * B_COLS) || (i % B_COLS) < 2 ? 1 : 0;
+#endif
+}
+
+/*
+ * Elide any full active rows.
+ */
+static void
+elide()
+{
+ register int i, j, base;
+ register 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 */
+ bzero(&board[base], B_COLS - 2);
+ scr_update();
+ tsleep();
+ while (--base != 0)
+ board[base + B_COLS] = board[base];
+ scr_update();
+ tsleep();
+ break;
+ }
+ }
+ }
+}
+
+int
+main(argc, argv)
+ int argc;
+ char *argv[];
+{
+ register int pos, c;
+ register struct shape *curshape;
+ register char *keys;
+ register int level = 2;
+ char key_write[6][10];
+ int ch, i, j;
+
+ keys = "jkl pq";
+
+ while ((ch = getopt(argc, argv, "k:l:s")) != EOF)
+ switch(ch) {
+ case 'k':
+ if (strlen(keys = optarg) != 6)
+ usage();
+ break;
+ case 'l':
+ level = atoi(optarg);
+ if (level < MINLEVEL || level > MAXLEVEL) {
+ (void)fprintf(stderr,
+ "tetris: level must be from %d to %d",
+ MINLEVEL, MAXLEVEL);
+ exit(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]) {
+ (void)fprintf(stderr,
+ "%s: Duplicate command keys specified.\n",
+ argv[0]);
+ exit (1);
+ }
+ }
+ if (keys[i] == ' ')
+ strcpy(key_write[i], "<space>");
+ else {
+ key_write[i][0] = keys[i];
+ key_write[i][1] = '\0';
+ }
+ }
+
+ sprintf(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;
+ 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 = 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((struct timeval *)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 */
+ 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_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);
+}
+
+void
+onintr(signo)
+ int signo;
+{
+ scr_clear();
+ scr_end();
+ exit(0);
+}
+
+void
+usage()
+{
+ (void)fprintf(stderr, "usage: tetris [-s] [-l level] [-keys]\n");
+ exit(1);
+}
diff --git a/tetris/tetris.h b/tetris/tetris.h
new file mode 100644
index 00000000..b95627bb
--- /dev/null
+++ b/tetris/tetris.h
@@ -0,0 +1,169 @@
+/*-
+ * 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. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by the University of
+ * California, Berkeley and its contributors.
+ * 4. 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
+ */
+
+/*
+ * 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;
+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
+
+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 struct shape shapes[];
+#define randshape() (&shapes[random() % 7])
+
+/*
+ * 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.
+ */
+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).
+ */
+int score; /* the obvious thing */
+
+char key_msg[100];
+
+int fits_in __P((struct shape *, int));
+void place __P((struct shape *, int, int));
+void stop __P((char *));