Commit Diff


commit - /dev/null
commit + 11a5e2cf2a04c33a0fd3cf1ff80762abe46c5e86
blob - /dev/null
blob + 46427a9bcabe3545a145756e26fa456dde1a2925 (mode 644)
--- /dev/null
+++ Makefile
@@ -0,0 +1,15 @@
+#	$NetBSD: Makefile,v 1.10 2023/06/03 09:09:09 lukem 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
+
+COPTS.tetris.c+=	${CC_WNO_FORMAT_TRUNCATION}
+COPTS.scores.c+=	${CC_WNO_FORMAT_TRUNCATION}
+
+.include <bsd.prog.mk>
blob - /dev/null
blob + e5f8c12af2cc8ba216c95c2f06bd0051395d9913 (mode 644)
--- /dev/null
+++ input.c
@@ -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);
+}
blob - /dev/null
blob + 856c66a8ed9815422ab1505240fd8dbfdbe839b9 (mode 644)
--- /dev/null
+++ input.h
@@ -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);
blob - /dev/null
blob + f96e3cc592a2363ee094f33bbcb8cbef251981bb (mode 644)
--- /dev/null
+++ pathnames.h
@@ -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"
blob - /dev/null
blob + 1c7e31bfceae1c4062d2cea0f9ef519d3de26fde (mode 644)
--- /dev/null
+++ scores.c
@@ -0,0 +1,959 @@
+/*	$NetBSD: scores.c,v 1.26 2021/05/02 12:50:46 rillig 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;
+	int serrno;
+	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);
+	serrno = errno;
+	(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.
+		 */
+		errno = serrno;
+		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] = {0};
+	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++) {
+		memcpy(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');
+		}
+	}
+}
blob - /dev/null
blob + 2d72aec8139ea1bec7842333eb2b6d211cf0037f (mode 644)
--- /dev/null
+++ scores.h
@@ -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);
blob - /dev/null
blob + 3e4a734f8986e686e884f4f2597ef9494b628701 (mode 644)
--- /dev/null
+++ screen.c
@@ -0,0 +1,432 @@
+/*	$NetBSD: screen.c,v 1.34 2021/05/02 12:50:46 rillig 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);
+}
+
+static void
+setcolor(int c)
+{
+	char *buf;
+	char monochrome[] = "\033[0m";
+	if (nocolor == 1)
+		return;
+	if (set_a_foreground == NULL)
+		return;
+
+	if (c == 0 || c == 7)
+		buf = monochrome;
+	else
+		buf = tiparm(set_a_foreground, 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 */
+	}
+	Offset = (Rows - D_LAST + D_FIRST - 2) / 2;
+	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).
+	 */
+	const char *tstr;
+	if ((tstr = enter_ca_mode) != NULL)
+		putstr(tstr);
+	if ((tstr = cursor_invisible) != NULL)
+		putstr(tstr);
+	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 */
+	const char *tstr;
+	if ((tstr = cursor_to_ll) != NULL)
+		putstr(tstr);
+	else
+		moveto(Rows - 1, 0);
+	/* exit screen mode */
+	if ((tstr = exit_ca_mode) != NULL)
+		putstr(tstr);
+	if ((tstr = cursor_normal) != NULL)
+		putstr(tstr);
+	(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);
+		setcolor(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 */
+		setcolor(nextshape->color);
+		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 + Offset), CTOD(i));
+			}
+			if (enter_standout_mode) {
+				if (so != cur_so) {
+					setcolor(so);
+					putpad(so ?
+					    enter_standout_mode :
+					    exit_standout_mode);
+					cur_so = so;
+				}
+#ifdef DEBUG
+				char buf[3];
+				snprintf(buf, sizeof(buf), "%d%d", so, so);
+				putstr(buf);
+#else
+				putstr("  ");
+#endif
+			} 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);
+	}
+}
blob - /dev/null
blob + c0b39053158db7f01137d6fb5f9bc1ae7214a069 (mode 644)
--- /dev/null
+++ screen.h
@@ -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);
blob - /dev/null
blob + ef82771c812e3657eee73c95a1c47817b203d762 (mode 644)
--- /dev/null
+++ shapes.c
@@ -0,0 +1,106 @@
+/*	$NetBSD: shapes.c,v 1.9 2014/06/11 16:47:39 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.
+ *
+ *	@(#)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,  7,	{ TL, TC, MR, } },
+	/* 1*/	{ 1,  8,	{ TC, TR, ML, } },
+	/* 2*/	{ 2,  9,	{ ML, MR, BC, } },
+	/* 3*/	{ 3,  3,	{ TL, TC, ML, } },
+	/* 4*/	{ 4, 12,	{ ML, BL, MR, } },
+	/* 5*/	{ 5, 15,	{ ML, BR, MR, } },
+	/* 6*/	{ 6, 18,	{ ML, MR, 2   } },	/* sticks out */
+	/* 7*/	{ 7,  0,	{ TC, ML, BL, } },
+	/* 8*/	{ 1,  1,	{ TC, MR, BR, } },
+	/* 9*/	{ 2, 10,	{ TC, MR, BC, } },
+	/*10*/	{ 2, 11,	{ TC, ML, MR, } },
+	/*11*/	{ 2,  2,	{ TC, ML, BC, } },
+	/*12*/	{ 4, 13,	{ TC, BC, BR, } },
+	/*13*/	{ 4, 14,	{ TR, ML, MR, } },
+	/*14*/	{ 4,  4,	{ TL, TC, BC, } },
+	/*15*/	{ 5, 16,	{ TR, TC, BC, } },
+	/*16*/	{ 5, 17,	{ TL, MR, ML, } },
+	/*17*/	{ 5,  5,	{ TC, BC, BL, } },
+	/*18*/	{ 6,  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;
+	onoff = onoff ? shape->color : 0;
+
+	board[pos] = onoff;
+	board[pos + *o++] = onoff;
+	board[pos + *o++] = onoff;
+	board[pos + *o] = onoff;
+}
blob - /dev/null
blob + 32915f8c0967c763bc933458a6506bb68b5114fe (mode 644)
--- /dev/null
+++ tetris.6
@@ -0,0 +1,175 @@
+.\"	$NetBSD: tetris.6,v 1.18 2023/07/01 10:51:35 nia 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 July 1, 2023
+.Dt TETRIS 6
+.Os
+.Sh NAME
+.Nm tetris
+.Nd the game of tetris
+.Sh SYNOPSIS
+.Nm
+.Op Fl bps
+.Op Fl k Ar keys
+.Op Fl l Ar level
+.Sh DESCRIPTION
+The
+.Nm
+command is a display-based game.
+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
+.It n
+down
+.El
+.Pp
+The options are as follows:
+.Bl -tag -width indent
+.It Fl b
+By default, shapes are displayed colorfully if the user's CRT supports color.
+The
+.Fl b
+option can be used to restore the traditional black-and-white behavior.
+.It Fl k
+The default control keys can be changed using the
+.Fl k
+option.
+The
+.Ar keys
+argument must have the seven keys in order, and, remember to quote any
+space or tab characters from the shell.
+For example:
+.Pp
+.Dl "tetris -l 2 -k 'jkl pqn'"
+.Pp
+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 ENVIRONMENT
+.Nm
+honors the informal standard
+.Dv NO_COLOR .
+When it is set in the environment, no color will be used.
+.Sh FILES
+.Bl -tag -width /var/games/tetris.scoresxx
+.It /var/games/tetris.scores
+high score file
+.El
+.Sh AUTHORS
+.An -nosplit
+Adapted from a 1989 International Obfuscated C Code Contest winner by
+.An Chris Torek
+and
+.An Darren F. Provine .
+.Pp
+Manual adapted from the original entry written by
+.An Nancy L. Tinkham
+and
+.An Darren F. Provine .
+.Pp
+Code for previewing next shape added by
+.An Hubert Feyrer
+in 1999.
+.Sh BUGS
+The higher levels are unplayable without a fast terminal connection.
blob - /dev/null
blob + b8612c2c8fe35855035967b269a9cec6f96d3ccc (mode 644)
--- /dev/null
+++ tetris.c
@@ -0,0 +1,355 @@
+/*	$NetBSD: tetris.c,v 1.34 2023/07/01 10:51:35 nia 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 */
+int	Offset;			/* used to center board & shapes */
+
+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;
+int	nocolor;
+
+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) ? 7 : 0;
+}
+
+/*
+ * 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];
+				/* don't forget to clear 0th row */
+				memset(&board[1], 0, B_COLS - 2);
+				scr_update();
+				tsleep();
+				break;
+			}
+		}
+	}
+}
+
+int
+main(int argc, char *argv[])
+{
+	int pos, c;
+	const char *keys;
+	int level = 2;
+#define NUMKEYS 7
+	char key_write[NUMKEYS][10];
+	char *nocolor_env;
+	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 pqn";
+
+	while ((ch = getopt(argc, argv, "bk:l:ps")) != -1)
+		switch(ch) {
+		case 'b':
+			nocolor = 1;
+			break;
+		case 'k':
+			if (strlen(keys = optarg) != NUMKEYS)
+				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();
+
+	nocolor_env = getenv("NO_COLOR");
+
+	if (nocolor_env != NULL && nocolor_env[0] != '\0')
+		nocolor = 1;
+
+	fallrate = 1000000 / level;
+
+	for (i = 0; i <= (NUMKEYS-1); i++) {
+		for (j = i+1; j <= (NUMKEYS-1); 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  %s - down",
+		key_write[0], key_write[1], key_write[2], key_write[3],
+		key_write[4], key_write[5], key_write[6]);
+
+	(void)signal(SIGINT, onintr);
+	scr_init();
+	setup_board();
+
+	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 == keys[6]) {
+			/* move down */
+			if (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 [-bps] [-k keys] [-l level]\n",
+	    getprogname());
+	exit(1);
+}
blob - /dev/null
blob + 8e1479f198ca603125efb9302fa1a323c2fec7af (mode 644)
--- /dev/null
+++ tetris.h
@@ -0,0 +1,176 @@
+/*	$NetBSD: tetris.h,v 1.16 2020/07/21 02:42:05 nia 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 */
+extern int	Offset;		/* vert. offset to center board */
+
+/*
+ * 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	color;
+	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[arc4random_uniform(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;
+extern int	nocolor;
+
+int	fits_in(const struct shape *, int);
+void	place(const struct shape *, int, int);
+void	stop(const char *) __dead;