commit - ce018a0ebbc58cd872be678fbfaab27f2777c5ba
commit + adccbc637928f68349330b395a2c4247c56c049f
blob - 9fb7432d876bea5c923c2930e72957eb68c752ae
blob + 7046dcb753237f944fcd1c772b1a43093889d7c5
--- Makefile
+++ Makefile
SRC_PD2 = pinentry-dmenu2.sh
-MAN = dwm/dwm.1 st/st.1 dmenu/dmenu.1 dmenu/stest.1 slock/slock.1
+SRC_NETRIS = netris/input.c \
+ netris/screen.c \
+ netris/shapes.c \
+ netris/scores.c \
+ netris/tetris.c
-all: bin/dwm bin/st bin/bedstatus bin/dmenu bin/stest bin/xbgcd bin/slock bin/pinentry-dmenu2
+HDR_NETRIS = netris/input.h \
+ netris/pathnames.h \
+ netris/scores.h \
+ netris/screen.h \
+ netris/tetris.h
+MAN = dwm/dwm.1 st/st.1 dmenu/dmenu.1 dmenu/stest.1 slock/slock.1 netris/netris.6
+
+all: bin/dwm bin/st bin/bedstatus bin/dmenu bin/stest bin/xbgcd bin/slock bin/pinentry-dmenu2 bin/netris
+
check:
find etc/common etc/$$(uname) -not -type d | awk '{a=$$0; sub(/etc\/[^\/]+/, "/etc", a); system("diff -u " $$0 " " a)}'
rm -rf bin
install:
- mkdir -p ${DESTDIR}${PREFIX}/bin ${DESTDIR}${MANPREFIX}/man1 ${DESTDIR}${SCRIPTDIR}
+ mkdir -p ${DESTDIR}${PREFIX}/bin \
+ ${DESTDIR}${MANPREFIX}/man1 \
+ ${DESTDIR}${MANPREFIX}/man6 \
+ ${DESTDIR}${SCRIPTDIR} \
+ ${DESTDIR}${GAMESDIR}
cp -f bin/* ${DESTDIR}${PREFIX}/bin/
for f in scripts/*; do \
sed 's#@PREFIX@#${PREFIX}#g; s#@SCRIPTS@#${SCRIPTDIR}#; s#@TERM@#${TERM}#' < $$f \
chmod +x ${DESTDIR}${SCRIPTDIR}/$$(basename $$f); \
done
for f in ${MAN}; do \
- sed 's/VERSION/${VERSION}/g' < $$f > ${DESTDIR}${MANPREFIX}/man1/$$(basename "$$f"); \
+ s=$$(echo "$$f" | sed 's/^[^.]*.\([0-9]\)$$/\1/'); \
+ sed 's/VERSION/${VERSION}/g' < $$f > ${DESTDIR}${MANPREFIX}/man$$s/$$(basename "$$f"); \
done
+ touch ${DESTDIR}${GAMESDIR}/netris.scores
+ chgrp games ${DESTDIR}${PREFIX}/bin/netris ${DESTDIR}${GAMESDIR}/netris.scores
chmod u+s ${DESTDIR}${PREFIX}/bin/slock
+ chmod g+s ${DESTDIR}${PREFIX}/bin/netris
+ chmod g+rw ${DESTDIR}${GAMESDIR}/netris.scores
install-etc:
mkdir -p ${DESTDIR}/etc
cp -f ${SRC_PD2} $@
chmod +x $@
+bin/netris: ${SRC_NETRIS} ${HDR_NETRIS}
+ @mkdir -p bin
+ ${CC} -o $@ ${SRC_NETRIS} ${CFLAGS} `pkg-config --cflags --libs ncurses`
+
.PHONY: all clean install
blob - dbd7132964caac9ccca139d14029399fa647fa4d
blob + a5e33e3ea6d25995c0351dfe39701cc67b1b7ad4
--- README
+++ README
# Components
-## patches suckless software
+## patched suckless software
- [dwm](https://dwm.suckless.org)
- [dmenu](https://tools.suckless.org/dmenu)
- [slock](https://tools.suckless.org/slock)
- bedstatus
- xbgcd
- pinentry-dmenu2
+- netris (fork of NetBSD's tetris(6))
- various scripts
- my personal dotfiles
- /etc files
blob - 50211a2eb11a166c625eb089028c93b35c759fb8
blob + b145fe1d6bfd10678b672d57749e19839660204c
--- TODO
+++ TODO
# Import
-- netris
- surf
- tabbed
- nsxiv
blob - 5b2de08f9b8d95c1c7112b893e65ae60aade7c77
blob + 9675b0f832ce3cd952c42b917c1e7e0d3a3b98c2
--- config.mk
+++ config.mk
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/man
SCRIPTDIR = ${PREFIX}/libexec/desktop
+GAMESDIR = /var/games
TERM = st
VERSION = "benni-0.1"
blob - /dev/null
blob + 004ad970583589cd17b1e1a87e7f4dd689f80aaa (mode 644)
--- /dev/null
+++ netris/README.md
+# portable NetBSD tetris
+This project attempts to port NetBSD's tetris to as many platforms as possible.
+Additionally a few non-portability changes were made.
+
+## Supported Platforms
+- OpenBSD >= 7.5
+- FreeBSD
+- Linux (w/ glibc)
+
+## Dependencies
+- libncurses
+- libbsd (on Linux)
blob - /dev/null
blob + 12fb448bc1940fed94e6ce8c52d76b6f555c9837 (mode 644)
--- /dev/null
+++ netris/input.c
+/* $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"
+
+#ifndef INFTIM
+# define INFTIM (-1)
+#endif
+
+/* 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
+++ netris/input.h
+/* $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 + 71906cc9bb1ebd91dab69e980ce8e4e78bf08bf7 (mode 644)
--- /dev/null
+++ netris/netris.6
+.\" $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 f
+By default, the fallrate is staying constant.
+With this option, the fallrate increases over time.
+.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 + 364e4feb3e13745ce0b1a7bdb147ce572607eae9 (mode 644)
--- /dev/null
+++ netris/pathnames.h
+/* $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/netris.scores"
blob - /dev/null
blob + d534524cba48817ba75d37d4ba7d12c0e947a4a2 (mode 644)
--- /dev/null
+++ netris/scores.c
+/* $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>
+
+#ifdef __FreeBSD__
+# include <sys/endian.h>
+# define swap32 bswap32
+# define swap64 bswap64
+#elif defined(__linux__)
+# include <sys/file.h>
+# define swap32 __builtin_bswap32
+# define swap64 __builtin_bswap64
+#endif
+
+#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 = swap32(offset64);
+ offset60 = swap32(offset60);
+ offset56 = swap32(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 = swap32(val);
+ }
+ return val;
+}
+
+static int64_t
+read64(int64_t val, int doflip)
+{
+ if (doflip) {
+ val = swap64(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 + 989fd9c34006a0427c83c3f6f423f2d974029987 (mode 644)
--- /dev/null
+++ netris/scores.h
+/* $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
+ */
+
+#include <stdint.h>
+
+/*
+ * 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 + 2d8f7ba65b67b7d4d84b83fc806170c2bc1c7541 (mode 644)
--- /dev/null
+++ netris/screen.c
+/* $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
+
+#ifdef __linux__
+# define OXTABS XTABS
+#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;
+
+extern int ospeed;
+
+
+/*
+ * 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
+++ netris/screen.h
+/* $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
+++ netris/shapes.c
+/* $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 + eae53a1f3063cac97385c8c3455887f13e660fff (mode 755)
Binary files /dev/null and netris/tetris differ
blob - /dev/null
blob + 0a6ef0d49bb1ec4b28630a8dfef8d4a8876bd75b (mode 644)
--- /dev/null
+++ netris/tetris.c
+/* $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>
+#if !defined(lint) && !defined(__OpenBSD__) && !defined(__linux__)
+__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>
+
+#ifdef __linux__
+# include <bsd/stdlib.h>
+#endif
+
+#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;
+int dofaster;
+
+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, "bfk:l:ps")) != -1)
+ switch(ch) {
+ case 'b':
+ nocolor = 1;
+ break;
+ case 'f':
+ dofaster = 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)
+{
+ (void)signo;
+ scr_clear();
+ scr_end();
+ exit(0);
+}
+
+static void
+usage(void)
+{
+ (void)fprintf(stderr, "usage: tetris [-bps] [-k keys] [-l level]\n");
+ exit(1);
+}
blob - /dev/null
blob + 94b4c00e26c1fb64f5e6f4cf9afef7147badf02b (mode 644)
--- /dev/null
+++ netris/tetris.h
+/* $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>
+
+#ifndef __dead
+# ifdef __dead2
+# define __dead __dead2
+# else
+# define __dead
+# endif
+#endif
+
+
+
+/*
+ * 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 */
+extern int dofaster; /* if 1, accelerate shapes */
+#define faster() (dofaster ? (fallrate -= fallrate / 3000) : fallrate)
+
+/*
+ * 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;