diff options
Diffstat (limited to 'tetris')
-rw-r--r-- | tetris/Makefile | 13 | ||||
-rw-r--r-- | tetris/input.c | 162 | ||||
-rw-r--r-- | tetris/input.h | 39 | ||||
-rw-r--r-- | tetris/pathnames.h | 37 | ||||
-rw-r--r-- | tetris/scores.c | 960 | ||||
-rw-r--r-- | tetris/scores.h | 89 | ||||
-rw-r--r-- | tetris/screen.c | 430 | ||||
-rw-r--r-- | tetris/screen.c.orig | 424 | ||||
-rw-r--r-- | tetris/screen.h | 54 | ||||
-rw-r--r-- | tetris/shapes.c | 106 | ||||
-rw-r--r-- | tetris/tetris.6 | 161 | ||||
-rw-r--r-- | tetris/tetris.c | 338 | ||||
-rw-r--r-- | tetris/tetris.h | 175 |
13 files changed, 2988 insertions, 0 deletions
diff --git a/tetris/Makefile b/tetris/Makefile new file mode 100644 index 0000000..0d9c0d6 --- /dev/null +++ b/tetris/Makefile @@ -0,0 +1,13 @@ +# $NetBSD: Makefile,v 1.7 2010/02/03 15:34:39 roy Exp $ +# @(#)Makefile 8.1 (Berkeley) 5/31/93 + +PROG= tetris +SRCS= input.c screen.c shapes.c scores.c tetris.c +MAN= tetris.6 +DPADD= ${LIBTERMINFO} +# 20150209 bkw: s/terminfo/curses/, add -lbsd +LDADD=-lcurses -lbsd +HIDEGAME=hidegame +SETGIDGAME=yes + +.include <bsd.prog.mk> diff --git a/tetris/input.c b/tetris/input.c new file mode 100644 index 0000000..e5f8c12 --- /dev/null +++ b/tetris/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); +} diff --git a/tetris/input.h b/tetris/input.h new file mode 100644 index 0000000..856c66a --- /dev/null +++ b/tetris/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); diff --git a/tetris/pathnames.h b/tetris/pathnames.h new file mode 100644 index 0000000..f96e3cc --- /dev/null +++ b/tetris/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" diff --git a/tetris/scores.c b/tetris/scores.c new file mode 100644 index 0000000..eca0ce3 --- /dev/null +++ b/tetris/scores.c @@ -0,0 +1,960 @@ +/* $NetBSD: scores.c,v 1.22 2014/03/22 19:05:30 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.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 <sys/file.h> /* 20150211 bkw: for flock() */ +#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]; + int i; + + if (sd == -1) { + return; + } + + memcpy(header.hsh_magic, hsh_magic_val, HSH_MAGIC_SIZE); + header.hsh_endiantag = HSH_ENDIAN_NATIVE; + header.hsh_version = HSH_VERSION; + + for (i=0; i<nscores; i++) { + strncpy(buf[i].hso_name, scores[i].hs_name, + sizeof(buf[i].hso_name)); + buf[i].hso_score = scores[i].hs_score; + buf[i].hso_level = scores[i].hs_level; + buf[i].hso_pad = 0xbaadf00d; + buf[i].hso_time = scores[i].hs_time; + } + + if (lseek(sd, 0, SEEK_SET) < 0) { + warn("Score file %s: lseek", _PATH_SCOREFILE); + goto fail; + } + if (dowrite(sd, &header, sizeof(header)) < 0 || + dowrite(sd, buf, sizeof(buf[0]) * nscores) < 0) { + warn("Score file %s: write", _PATH_SCOREFILE); + goto fail; + } + return; + fail: + warnx("high scores may be damaged"); +#else + (void)sd; +#endif /* ALLOW_SCORE_UPDATES */ +} + +/* + * Close the score file. + */ +static void +closescores(int sd) +{ + flock(sd, LOCK_UN); + close(sd); +} + +/* + * Read and update the scores file with the current reults. + */ +void +savescore(int level) +{ + struct highscore *sp; + int i; + int change; + int sd; + const char *me; + + getscores(&sd); + gotscores = 1; + (void)time(&now); + + /* + * Allow at most one score per person per level -- see if we + * can replace an existing score, or (easiest) do nothing. + * Otherwise add new score at end (there is always room). + */ + change = 0; + me = thisuser(); + for (i = 0, sp = &scores[0]; i < nscores; i++, sp++) { + if (sp->hs_level != level || strcmp(sp->hs_name, me) != 0) + continue; + if (score > sp->hs_score) { + (void)printf("%s bettered %s %d score of %d!\n", + "\nYou", "your old level", level, + sp->hs_score * sp->hs_level); + sp->hs_score = score; /* new score */ + sp->hs_time = now; /* and time */ + change = 1; + } else if (score == sp->hs_score) { + (void)printf("%s tied %s %d high score.\n", + "\nYou", "your old level", level); + sp->hs_time = now; /* renew it */ + change = 1; /* gotta rewrite, sigh */ + } /* else new score < old score: do nothing */ + break; + } + if (i >= nscores) { + strcpy(sp->hs_name, me); + sp->hs_level = level; + sp->hs_score = score; + sp->hs_time = now; + nscores++; + change = 1; + } + + if (change) { + /* + * Sort & clean the scores, then rewrite. + */ + nscores = checkscores(scores, nscores); + putscores(sd); + } + closescores(sd); +} + +/* + * Get login name, or if that fails, get something suitable. + * The result is always trimmed to fit in a score. + */ +static char * +thisuser(void) +{ + const char *p; + struct passwd *pw; + size_t l; + static char u[sizeof(scores[0].hs_name)]; + + if (u[0]) + return (u); + p = getlogin(); + if (p == NULL || *p == '\0') { + pw = getpwuid(getuid()); + if (pw != NULL) + p = pw->pw_name; + else + p = " ???"; + } + l = strlen(p); + if (l >= sizeof(u)) + l = sizeof(u) - 1; + memcpy(u, p, l); + u[l] = '\0'; + return (u); +} + +/* + * Score comparison function for qsort. + * + * If two scores are equal, the person who had the score first is + * listed first in the highscore file. + */ +static int +cmpscores(const void *x, const void *y) +{ + const struct highscore *a, *b; + long l; + + a = x; + b = y; + l = (long)b->hs_level * b->hs_score - (long)a->hs_level * a->hs_score; + if (l < 0) + return (-1); + if (l > 0) + return (1); + if (a->hs_time < b->hs_time) + return (-1); + if (a->hs_time > b->hs_time) + return (1); + return (0); +} + +/* + * If we've added a score to the file, we need to check the file and ensure + * that this player has only a few entries. The number of entries is + * controlled by MAXSCORES, and is to ensure that the highscore file is not + * monopolised by just a few people. People who no longer have accounts are + * only allowed the highest score. Scores older than EXPIRATION seconds are + * removed, unless they are someone's personal best. + * Caveat: the highest score on each level is always kept. + */ +static int +checkscores(struct highscore *hs, int num) +{ + struct highscore *sp; + int i, j, k, numnames; + int levelfound[NLEVELS]; + struct peruser { + char *name; + int times; + } count[NUMSPOTS]; + struct peruser *pu; + + /* + * Sort so that highest totals come first. + * + * levelfound[i] becomes set when the first high score for that + * level is encountered. By definition this is the highest score. + */ + qsort((void *)hs, nscores, sizeof(*hs), cmpscores); + for (i = MINLEVEL; i < NLEVELS; i++) + levelfound[i] = 0; + numnames = 0; + for (i = 0, sp = hs; i < num;) { + /* + * This is O(n^2), but do you think we care? + */ + for (j = 0, pu = count; j < numnames; j++, pu++) + if (strcmp(sp->hs_name, pu->name) == 0) + break; + if (j == numnames) { + /* + * Add new user, set per-user count to 1. + */ + pu->name = sp->hs_name; + pu->times = 1; + numnames++; + } else { + /* + * Two ways to keep this score: + * - Not too many (per user), still has acct, & + * score not dated; or + * - High score on this level. + */ + if ((pu->times < MAXSCORES && + getpwnam(sp->hs_name) != NULL && + sp->hs_time + EXPIRATION >= now) || + levelfound[sp->hs_level] == 0) + pu->times++; + else { + /* + * Delete this score, do not count it, + * do not pass go, do not collect $200. + */ + num--; + for (k = i; k < num; k++) + hs[k] = hs[k + 1]; + continue; + } + } + if (sp->hs_level < NLEVELS && sp->hs_level >= 0) + levelfound[sp->hs_level] = 1; + i++, sp++; + } + return (num > MAXHISCORES ? MAXHISCORES : num); +} + +/* + * Show current scores. This must be called after savescore, if + * savescore is called at all, for two reasons: + * - Showscores munches the time field. + * - Even if that were not the case, a new score must be recorded + * before it can be shown anyway. + */ +void +showscores(int level) +{ + struct highscore *sp; + int i, n, c; + const char *me; + int levelfound[NLEVELS]; + + if (!gotscores) + getscores(NULL); + (void)printf("\n\t\t\t Tetris High Scores\n"); + + /* + * If level == 0, the person has not played a game but just asked for + * the high scores; we do not need to check for printing in highlight + * mode. If SOstr is null, we can't do highlighting anyway. + */ + me = level && enter_standout_mode ? thisuser() : NULL; + + /* + * Set times to 0 except for high score on each level. + */ + for (i = MINLEVEL; i < NLEVELS; i++) + levelfound[i] = 0; + for (i = 0, sp = scores; i < nscores; i++, sp++) { + if (sp->hs_level < NLEVELS && sp->hs_level >= 0) { + if (levelfound[sp->hs_level]) + sp->hs_time = 0; + else { + sp->hs_time = 1; + levelfound[sp->hs_level] = 1; + } + } + } + + /* + * Page each screenful of scores. + */ + for (i = 0, sp = scores; i < nscores; sp += n) { + n = 40; + if (i + n > nscores) + n = nscores - i; + printem(level, i + 1, sp, n, me); + if ((i += n) < nscores) { + (void)printf("\nHit RETURN to continue."); + (void)fflush(stdout); + while ((c = getchar()) != '\n') + if (c == EOF) + break; + (void)printf("\n"); + } + } +} + +static void +printem(int level, int offset, struct highscore *hs, int n, const char *me) +{ + struct highscore *sp; + int nrows, row, col, item, i, highlight; + char buf[100]; +#define TITLE "Rank Score Name (points/level)" + + /* + * This makes a nice two-column sort with headers, but it's a bit + * convoluted... + */ + printf("%s %s\n", TITLE, n > 1 ? TITLE : ""); + + highlight = 0; + nrows = (n + 1) / 2; + + for (row = 0; row < nrows; row++) { + for (col = 0; col < 2; col++) { + item = col * nrows + row; + if (item >= n) { + /* + * Can only occur on trailing columns. + */ + (void)putchar('\n'); + continue; + } + sp = &hs[item]; + (void)snprintf(buf, sizeof(buf), + "%3d%c %6d %-11s (%6d on %d)", + item + offset, sp->hs_time ? '*' : ' ', + sp->hs_score * sp->hs_level, + sp->hs_name, sp->hs_score, sp->hs_level); + /* + * Highlight if appropriate. This works because + * we only get one score per level. + */ + if (me != NULL && + sp->hs_level == level && + sp->hs_score == score && + strcmp(sp->hs_name, me) == 0) { + putpad(enter_standout_mode); + highlight = 1; + } + (void)printf("%s", buf); + if (highlight) { + putpad(exit_standout_mode); + highlight = 0; + } + + /* fill in spaces so column 1 lines up */ + if (col == 0) + for (i = 40 - strlen(buf); --i >= 0;) + (void)putchar(' '); + else /* col == 1 */ + (void)putchar('\n'); + } + } +} diff --git a/tetris/scores.h b/tetris/scores.h new file mode 100644 index 0000000..5c8db42 --- /dev/null +++ b/tetris/scores.h @@ -0,0 +1,89 @@ +/* $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. + */ + +#include <stdint.h> /* 20150209 bkw */ + +/* Header for high score file. */ +#define HSH_MAGIC_SIZE 8 +struct highscore_header { + char hsh_magic[HSH_MAGIC_SIZE]; + uint32_t hsh_endiantag; + uint32_t hsh_version; +}; + +/* Current on-disk high score record. */ +struct highscore_ondisk { + char hso_name[20]; + int32_t hso_score; + int32_t hso_level; + int32_t hso_pad; + int64_t hso_time; +}; + +/* 5.99.x after time_t change, on 32-bit machines */ +struct highscore_ondisk_599 { + char hso599_name[20]; + int32_t hso599_score; + int32_t hso599_level; + int32_t hso599_time[2]; +}; + +/* 5.0 and earlier on-disk high score record. */ +struct highscore_ondisk_50 { + char hso50_name[20]; + int32_t hso50_score; + int32_t hso50_level; + int32_t hso50_time; +}; + +/* In-memory high score record. */ +struct highscore { + char hs_name[20]; /* login name */ + int hs_score; /* raw score */ + int hs_level; /* play level */ + time_t hs_time; /* time at game end */ +}; + +#define MAXHISCORES 80 +#define MAXSCORES 9 /* maximum high score entries per person */ +#define EXPIRATION (5L * 365 * 24 * 60 * 60) + +void savescore(int); +void showscores(int); diff --git a/tetris/screen.c b/tetris/screen.c new file mode 100644 index 0000000..af1b1d6 --- /dev/null +++ b/tetris/screen.c @@ -0,0 +1,430 @@ +/* $NetBSD: screen.c,v 1.29 2014/07/13 16:23:55 pgoyette 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; + if (nocolor == 1) + return; + if (set_a_foreground == NULL) + return; + + /* 20150209 bkw: original code: + buf = tiparm(set_a_foreground, c == 7 ? 0 : c); + ...assumes user has a white terminal background. On + Slackware, we have more terminals that default to a + black background (the console itself, Konsole, xfce4-terminal), + so let's assume that instead: */ + 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 */ + } + 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); 20150209 bkw */ + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + + /* + * We made it. We are now in screen mode, modulo TIstr + * (which we will fix immediately). + */ + if (enter_ca_mode) + putstr(enter_ca_mode); + if (cursor_invisible) + putstr(cursor_invisible); + if (tstp != SIG_IGN) + (void) signal(SIGTSTP, scr_stop); + if (ttou != SIG_IGN) + (void) signal(SIGTTOU, ttou); + + isset = 1; + (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); + scr_clear(); +} + +/* + * End screen mode. + */ +void +scr_end(void) +{ + sigset_t nsigset, osigset; + + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGTSTP); + sigaddset(&nsigset, SIGTTOU); + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + /* move cursor to last line */ + if (cursor_to_ll) + putstr(cursor_to_ll); + else + moveto(Rows - 1, 0); + /* exit screen mode */ + if (exit_ca_mode) + putstr(exit_ca_mode); + if (cursor_normal) + putstr(cursor_normal); + (void) fflush(stdout); + (void) tcsetattr(0, TCSADRAIN, &oldtt); + isset = 0; + /* restore signals */ + (void) signal(SIGTSTP, tstp); + (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); +} + +void +stop(const char *why) +{ + + if (isset) + scr_end(); + (void) fprintf(stderr, "aborting: %s\n", why); + exit(1); +} + +/* + * Clear the screen, forgetting the current contents in the process. + */ +void +scr_clear(void) +{ + + putpad(clear_screen); + curscore = -1; + memset((char *)curscreen, 0, sizeof(curscreen)); +} + +#if vax && !__GNUC__ +typedef int regcell; /* pcc is bad at `register char', etc */ +#else +typedef cell regcell; +#endif + +/* + * Update the screen. + */ +void +scr_update(void) +{ + cell *bp, *sp; + regcell so, cur_so = 0; + int i, ccol, j; + sigset_t nsigset, osigset; + static const struct shape *lastshape; + + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGTSTP); + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + + /* always leave cursor after last displayed point */ + curscreen[D_LAST * B_COLS - 1] = -1; + + if (score != curscore) { + if (cursor_home) + putpad(cursor_home); + else + moveto(0, 0); + (void) printf("Score: %d", score); + curscore = score; + } + + /* draw preview of nextpattern */ + if (showpreview && (nextshape != lastshape)) { + static int r=5, c=2; + int tr, tc, t; + + lastshape = nextshape; + + /* clean */ + putpad(exit_standout_mode); + moveto(r-1, c-1); putstr(" "); + moveto(r, c-1); putstr(" "); + moveto(r+1, c-1); putstr(" "); + moveto(r+2, c-1); putstr(" "); + + moveto(r-3, c-2); + putstr("Next shape:"); + + /* draw */ + putpad(enter_standout_mode); + setcolor(nextshape->color); + moveto(r, 2*c); + putstr(" "); + for(i=0; i<3; i++) { + t = c + r*B_COLS; + t += nextshape->off[i]; + + tr = t / B_COLS; + tc = t % B_COLS; + + moveto(tr, 2*tc); + putstr(" "); + } + putpad(exit_standout_mode); + } + + bp = &board[D_FIRST * B_COLS]; + sp = &curscreen[D_FIRST * B_COLS]; + for (j = D_FIRST; j < D_LAST; j++) { + ccol = -1; + for (i = 0; i < B_COLS; bp++, sp++, i++) { + if (*sp == (so = *bp)) + continue; + *sp = so; + if (i != ccol) { + if (cur_so && move_standout_mode) { + putpad(exit_standout_mode); + cur_so = 0; + } + moveto(RTOD(j), CTOD(i)); + } + if (enter_standout_mode) { + if (so != cur_so) { + putpad(so ? + enter_standout_mode : + exit_standout_mode); + cur_so = so; + } + setcolor(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); + } +} diff --git a/tetris/screen.c.orig b/tetris/screen.c.orig new file mode 100644 index 0000000..788a279 --- /dev/null +++ b/tetris/screen.c.orig @@ -0,0 +1,424 @@ +/* $NetBSD: screen.c,v 1.29 2014/07/13 16:23:55 pgoyette 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; + if (nocolor == 1) + return; + if (set_a_foreground == NULL) + return; + + buf = tiparm(set_a_foreground, c == 7 ? 0 : c); + if (buf != NULL) + putpad(buf); +} + +/* + * Set up from termcap. + */ +void +scr_init(void) +{ + + setupterm(NULL, 0, NULL); + if (clear_screen == NULL) + stop("cannot clear screen"); + if (cursor_address == NULL || cursor_up == NULL) + stop("cannot do random cursor positioning"); +} + +/* this foolery is needed to modify tty state `atomically' */ +static jmp_buf scr_onstop; + +static void +stopset(int sig) +{ + sigset_t set; + + (void) signal(sig, SIG_DFL); + (void) kill(getpid(), sig); + sigemptyset(&set); + sigaddset(&set, sig); + (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0); + longjmp(scr_onstop, 1); +} + +static void +scr_stop(int sig) +{ + sigset_t set; + + scr_end(); + (void) kill(getpid(), sig); + sigemptyset(&set); + sigaddset(&set, sig); + (void) sigprocmask(SIG_UNBLOCK, &set, (sigset_t *)0); + scr_set(); + scr_msg(key_msg, 1); +} + +/* + * Set up screen mode. + */ +void +scr_set(void) +{ + struct winsize ws; + struct termios newtt; + sigset_t nsigset, osigset; + void (*ttou)(int); + + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGTSTP); + sigaddset(&nsigset, SIGTTOU); + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + if ((tstp = signal(SIGTSTP, stopset)) == SIG_IGN) + (void) signal(SIGTSTP, SIG_IGN); + if ((ttou = signal(SIGTTOU, stopset)) == SIG_IGN) + (void) signal(SIGTTOU, SIG_IGN); + /* + * At last, we are ready to modify the tty state. If + * we stop while at it, stopset() above will longjmp back + * to the setjmp here and we will start over. + */ + (void) setjmp(scr_onstop); + (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); + Rows = 0, Cols = 0; + if (ioctl(0, TIOCGWINSZ, &ws) == 0) { + Rows = ws.ws_row; + Cols = ws.ws_col; + } + if (Rows == 0) + Rows = lines; + if (Cols == 0) + Cols = columns; + if (Rows < MINROWS || Cols < MINCOLS) { + (void) fprintf(stderr, + "the screen is too small: must be at least %dx%d, ", + MINCOLS, MINROWS); + stop(""); /* stop() supplies \n */ + } + if (tcgetattr(0, &oldtt) < 0) + stop("tcgetattr() fails"); + newtt = oldtt; + newtt.c_lflag &= ~(ICANON|ECHO); + newtt.c_oflag &= ~OXTABS; + if (tcsetattr(0, TCSADRAIN, &newtt) < 0) + stop("tcsetattr() fails"); + /* ospeed = cfgetospeed(&newtt); 20150209 bkw */ + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + + /* + * We made it. We are now in screen mode, modulo TIstr + * (which we will fix immediately). + */ + if (enter_ca_mode) + putstr(enter_ca_mode); + if (cursor_invisible) + putstr(cursor_invisible); + if (tstp != SIG_IGN) + (void) signal(SIGTSTP, scr_stop); + if (ttou != SIG_IGN) + (void) signal(SIGTTOU, ttou); + + isset = 1; + (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); + scr_clear(); +} + +/* + * End screen mode. + */ +void +scr_end(void) +{ + sigset_t nsigset, osigset; + + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGTSTP); + sigaddset(&nsigset, SIGTTOU); + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + /* move cursor to last line */ + if (cursor_to_ll) + putstr(cursor_to_ll); + else + moveto(Rows - 1, 0); + /* exit screen mode */ + if (exit_ca_mode) + putstr(exit_ca_mode); + if (cursor_normal) + putstr(cursor_normal); + (void) fflush(stdout); + (void) tcsetattr(0, TCSADRAIN, &oldtt); + isset = 0; + /* restore signals */ + (void) signal(SIGTSTP, tstp); + (void) sigprocmask(SIG_SETMASK, &osigset, (sigset_t *)0); +} + +void +stop(const char *why) +{ + + if (isset) + scr_end(); + (void) fprintf(stderr, "aborting: %s\n", why); + exit(1); +} + +/* + * Clear the screen, forgetting the current contents in the process. + */ +void +scr_clear(void) +{ + + putpad(clear_screen); + curscore = -1; + memset((char *)curscreen, 0, sizeof(curscreen)); +} + +#if vax && !__GNUC__ +typedef int regcell; /* pcc is bad at `register char', etc */ +#else +typedef cell regcell; +#endif + +/* + * Update the screen. + */ +void +scr_update(void) +{ + cell *bp, *sp; + regcell so, cur_so = 0; + int i, ccol, j; + sigset_t nsigset, osigset; + static const struct shape *lastshape; + + sigemptyset(&nsigset); + sigaddset(&nsigset, SIGTSTP); + (void) sigprocmask(SIG_BLOCK, &nsigset, &osigset); + + /* always leave cursor after last displayed point */ + curscreen[D_LAST * B_COLS - 1] = -1; + + if (score != curscore) { + if (cursor_home) + putpad(cursor_home); + else + moveto(0, 0); + (void) printf("Score: %d", score); + curscore = score; + } + + /* draw preview of nextpattern */ + if (showpreview && (nextshape != lastshape)) { + static int r=5, c=2; + int tr, tc, t; + + lastshape = nextshape; + + /* clean */ + putpad(exit_standout_mode); + moveto(r-1, c-1); putstr(" "); + moveto(r, c-1); putstr(" "); + moveto(r+1, c-1); putstr(" "); + moveto(r+2, c-1); putstr(" "); + + moveto(r-3, c-2); + putstr("Next shape:"); + + /* draw */ + putpad(enter_standout_mode); + setcolor(nextshape->color); + moveto(r, 2*c); + putstr(" "); + for(i=0; i<3; i++) { + t = c + r*B_COLS; + t += nextshape->off[i]; + + tr = t / B_COLS; + tc = t % B_COLS; + + moveto(tr, 2*tc); + putstr(" "); + } + putpad(exit_standout_mode); + } + + bp = &board[D_FIRST * B_COLS]; + sp = &curscreen[D_FIRST * B_COLS]; + for (j = D_FIRST; j < D_LAST; j++) { + ccol = -1; + for (i = 0; i < B_COLS; bp++, sp++, i++) { + if (*sp == (so = *bp)) + continue; + *sp = so; + if (i != ccol) { + if (cur_so && move_standout_mode) { + putpad(exit_standout_mode); + cur_so = 0; + } + moveto(RTOD(j), CTOD(i)); + } + if (enter_standout_mode) { + if (so != cur_so) { + putpad(so ? + enter_standout_mode : + exit_standout_mode); + cur_so = so; + } + setcolor(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); + } +} diff --git a/tetris/screen.h b/tetris/screen.h new file mode 100644 index 0000000..c0b3905 --- /dev/null +++ b/tetris/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); diff --git a/tetris/shapes.c b/tetris/shapes.c new file mode 100644 index 0000000..ef82771 --- /dev/null +++ b/tetris/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; +} diff --git a/tetris/tetris.6 b/tetris/tetris.6 new file mode 100644 index 0000000..b0c6778 --- /dev/null +++ b/tetris/tetris.6 @@ -0,0 +1,161 @@ +.\" $NetBSD: tetris.6,v 1.14 2014/07/15 16:17:15 wiz Exp $ +.\" +.\" Copyright (c) 1992, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" This code is derived from software contributed to Berkeley by +.\" Nancy L. Tinkham and Darren F. Provine. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" @(#)tetris.6 8.1 (Berkeley) 5/31/93 +.\" +.Dd July 13, 2014 +.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 runs display-based game which must be played on a CRT terminal. +The object is to fit the shapes together forming complete rows, +which then vanish. +When the shapes fill up to the top, the game ends. +You can optionally select a level of play, or custom-select control keys. +.Pp +The default level of play is 2. +.Pp +The default control keys are as follows: +.Pp +.Bl -tag -width "xxspacexx" -compact -offset indent +.It j +move left +.It k +rotate 1/4 turn counterclockwise +.It l +move right +.It Aq space +drop +.It p +pause +.It q +quit +.El +.Pp +The options are as follows: +.Bl -tag -width indent +.It Fl 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 six keys in order, and, remember to quote any +space or tab characters from the shell. +For example: +.sp +.Dl "tetris -l 2 -k 'jkl pq'" +.sp +will play the default games, i.e. level 2 and with the default +control keys. +The current key settings are displayed at the bottom of the screen +during play. +.It Fl l +Select a level of play. +.It Fl s +Display the top scores. +.It Fl p +Switch on previewing of the shape that will appear next. +.El +.Sh PLAY +At the start of the game, a shape will appear at the top of the screen, +falling one square at a time. +The speed at which it falls is determined directly by the level: +if you select level 2, the blocks will fall twice per second; +at level 9, they fall 9 times per second. +(As the game goes on, things speed up, +no matter what your initial selection.) +When this shape +.Dq touches down +on the bottom of the field, another will appear at the top. +.Pp +You can move shapes to the left or right, rotate them counterclockwise, +or drop them to the bottom by pressing the appropriate keys. +As you fit them together, completed horizontal rows vanish, +and any blocks above fall down to fill in. +When the blocks stack up to the top of the screen, the game is over. +.Sh SCORING +You get one point for every block you fit into the stack, +and one point for every space a block falls when you hit the drop key. +(Dropping the blocks is therefore a good way to increase your score.) +Your total score is the product of the level of play +and your accumulated +.ie t points\(em200 +.el points -- 200 +points on level 3 gives you a score of 600. +Each player gets at most one entry on any level, +for a total of nine scores in the high scores file. +Players who no longer have accounts are limited to one score. +Also, scores over 5 years old are expired. +The exception to these conditions is that the highest score on a given +level is +.Em always +kept, +so that following generations can pay homage to those who have +wasted serious amounts of time. +.Pp +The score list is produced at the end of the game. +The printout includes each player's overall ranking, +name, score, and how many points were scored on what level. +Scores which are the highest on a given level +are marked with asterisks +.Dq * . +.Sh FILES +.Bl -tag -width /var/games/tetris.scoresxx +.It /var/games/tetris.scores +high score file +.El +.Sh AUTHORS +Adapted from a 1989 International Obfuscated C Code Contest winner by +Chris Torek and Darren F. Provine. +.Pp +Manual adapted from the original entry written by Nancy L. Tinkham and +Darren F. Provine. +.Pp +Code for previewing next shape added by Hubert Feyrer in 1999. +.Sh BUGS +The higher levels are unplayable without a fast terminal connection. diff --git a/tetris/tetris.c b/tetris/tetris.c new file mode 100644 index 0000000..53a31f2 --- /dev/null +++ b/tetris/tetris.c @@ -0,0 +1,338 @@ +/* $NetBSD: tetris.c,v 1.27 2014/07/13 17:38:38 pgoyette Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek and Darren F. Provine. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tetris.c 8.1 (Berkeley) 5/31/93 + */ + +#include <sys/cdefs.h> +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1992, 1993\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +/* + * Tetris (or however it is spelled). + */ + +#include <sys/time.h> + +#include <err.h> +#include <fcntl.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "input.h" +#include "scores.h" +#include "screen.h" +#include "tetris.h" + +cell board[B_SIZE]; /* 1 => occupied, 0 => empty */ + +int Rows, Cols; /* current screen size */ + +static const struct shape *curshape; +const struct shape *nextshape; + +long fallrate; /* less than 1 million; smaller => faster */ + +int score; /* the obvious thing */ +gid_t gid, egid; + +char key_msg[100]; +int showpreview; +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]; + scr_update(); + tsleep(); + break; + } + } + } +} + +int +main(int argc, char *argv[]) +{ + int pos, c; + const char *keys; + int level = 2; + char key_write[6][10]; + int ch, i, j; + int fd; + + gid = getgid(); + egid = getegid(); + setegid(gid); + + fd = open("/dev/null", O_RDONLY); + if (fd < 3) + exit(1); + close(fd); + + keys = "jkl pq"; + + while ((ch = getopt(argc, argv, "bk:l:ps")) != -1) + switch(ch) { + case 'b': + nocolor = 1; + break; + case 'k': + if (strlen(keys = optarg) != 6) + usage(); + break; + case 'l': + level = atoi(optarg); + if (level < MINLEVEL || level > MAXLEVEL) { + errx(1, "level must be from %d to %d", + MINLEVEL, MAXLEVEL); + } + break; + case 'p': + showpreview = 1; + break; + case 's': + showscores(0); + exit(0); + case '?': + default: + usage(); + } + + argc -= optind; + argv += optind; + + if (argc) + usage(); + + fallrate = 1000000 / level; + + for (i = 0; i <= 5; i++) { + for (j = i+1; j <= 5; j++) { + if (keys[i] == keys[j]) { + errx(1, "duplicate command keys specified."); + } + } + if (keys[i] == ' ') + strcpy(key_write[i], "<space>"); + else { + key_write[i][0] = keys[i]; + key_write[i][1] = '\0'; + } + } + + snprintf(key_msg, sizeof(key_msg), +"%s - left %s - rotate %s - right %s - drop %s - pause %s - quit", + key_write[0], key_write[1], key_write[2], key_write[3], + key_write[4], key_write[5]); + + (void)signal(SIGINT, onintr); + scr_init(); + setup_board(); + + srandom(getpid()); + scr_set(); + + pos = A_FIRST*B_COLS + (B_COLS/2)-1; + nextshape = randshape(); + curshape = randshape(); + + scr_msg(key_msg, 1); + + for (;;) { + place(curshape, pos, 1); + scr_update(); + place(curshape, pos, 0); + c = tgetchar(); + if (c < 0) { + /* + * Timeout. Move down if possible. + */ + if (fits_in(curshape, pos + B_COLS)) { + pos += B_COLS; + continue; + } + + /* + * Put up the current shape `permanently', + * bump score, and elide any full rows. + */ + place(curshape, pos, 1); + score++; + elide(); + + /* + * Choose a new shape. If it does not fit, + * the game is over. + */ + curshape = nextshape; + nextshape = randshape(); + pos = A_FIRST*B_COLS + (B_COLS/2)-1; + if (!fits_in(curshape, pos)) + break; + continue; + } + + /* + * Handle command keys. + */ + if (c == keys[5]) { + /* quit */ + break; + } + if (c == keys[4]) { + static char msg[] = + "paused - press RETURN to continue"; + + place(curshape, pos, 1); + do { + scr_update(); + scr_msg(key_msg, 0); + scr_msg(msg, 1); + (void) fflush(stdout); + } while (rwait(NULL) == -1); + scr_msg(msg, 0); + scr_msg(key_msg, 1); + place(curshape, pos, 0); + continue; + } + if (c == keys[0]) { + /* move left */ + if (fits_in(curshape, pos - 1)) + pos--; + continue; + } + if (c == keys[1]) { + /* turn */ + const struct shape *new = &shapes[curshape->rot]; + + if (fits_in(new, pos)) + curshape = new; + continue; + } + if (c == keys[2]) { + /* move right */ + if (fits_in(curshape, pos + 1)) + pos++; + continue; + } + if (c == keys[3]) { + /* move to bottom */ + while (fits_in(curshape, pos + B_COLS)) { + pos += B_COLS; + score++; + } + continue; + } + if (c == '\f') { + scr_clear(); + scr_msg(key_msg, 1); + } + } + + scr_clear(); + scr_end(); + + (void)printf("Your score: %d point%s x level %d = %d\n", + score, score == 1 ? "" : "s", level, score * level); + savescore(level); + + printf("\nHit RETURN to see high scores, ^C to skip.\n"); + + while ((i = getchar()) != '\n') + if (i == EOF) + break; + + showscores(level); + + exit(0); +} + +static void +onintr(int signo __attribute__((unused))) +{ + scr_clear(); + scr_end(); + exit(0); +} + +static void +usage(void) +{ + (void)fprintf(stderr, "usage: %s [-ps] [-k keys] [-l level]\n", + getprogname()); + exit(1); +} diff --git a/tetris/tetris.h b/tetris/tetris.h new file mode 100644 index 0000000..559ef76 --- /dev/null +++ b/tetris/tetris.h @@ -0,0 +1,175 @@ +/* $NetBSD: tetris.h,v 1.14 2014/07/13 16:23:55 pgoyette Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek and Darren F. Provine. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)tetris.h 8.1 (Berkeley) 5/31/93 + */ + +#include <sys/types.h> + +/* + * Definitions for Tetris. + */ + +/* + * The display (`board') is composed of 23 rows of 12 columns of characters + * (numbered 0..22 and 0..11), stored in a single array for convenience. + * Columns 1 to 10 of rows 1 to 20 are the actual playing area, where + * shapes appear. Columns 0 and 11 are always occupied, as are all + * columns of rows 21 and 22. Rows 0 and 22 exist as boundary areas + * so that regions `outside' the visible area can be examined without + * worrying about addressing problems. + */ + + /* the board */ +#define B_COLS 12 +#define B_ROWS 23 +#define B_SIZE (B_ROWS * B_COLS) + +typedef unsigned char cell; +extern cell board[B_SIZE]; /* 1 => occupied, 0 => empty */ + + /* the displayed area (rows) */ +#define D_FIRST 1 +#define D_LAST 22 + + /* the active area (rows) */ +#define A_FIRST 1 +#define A_LAST 21 + +/* + * Minimum display size. + */ +#define MINROWS 23 +#define MINCOLS 40 + +extern int Rows, Cols; /* current screen size */ + +/* + * Translations from board coordinates to display coordinates. + * As with board coordinates, display coordiates are zero origin. + */ +#define RTOD(x) ((x) - 1) +#define CTOD(x) ((x) * 2 + (((Cols - 2 * B_COLS) >> 1) - 1)) + +/* + * A `shape' is the fundamental thing that makes up the game. There + * are 7 basic shapes, each consisting of four `blots': + * + * X.X X.X X.X + * X.X X.X X.X.X X.X X.X.X X.X.X X.X.X.X + * X X X + * + * 0 1 2 3 4 5 6 + * + * Except for 3 and 6, the center of each shape is one of the blots. + * This blot is designated (0,0). The other three blots can then be + * described as offsets from the center. Shape 3 is the same under + * rotation, so its center is effectively irrelevant; it has been chosen + * so that it `sticks out' upward and leftward. Except for shape 6, + * all the blots are contained in a box going from (-1,-1) to (+1,+1); + * shape 6's center `wobbles' as it rotates, so that while it `sticks out' + * rightward, its rotation---a vertical line---`sticks out' downward. + * The containment box has to include the offset (2,0), making the overall + * containment box range from offset (-1,-1) to (+2,+1). (This is why + * there is only one row above, but two rows below, the display area.) + * + * The game works by choosing one of these shapes at random and putting + * its center at the middle of the first display row (row 1, column 5). + * The shape is moved steadily downward until it collides with something: + * either another shape, or the bottom of the board. When the shape can + * no longer be moved downwards, it is merged into the current board. + * At this time, any completely filled rows are elided, and blots above + * these rows move down to make more room. A new random shape is again + * introduced at the top of the board, and the whole process repeats. + * The game ends when the new shape will not fit at (1,5). + * + * While the shapes are falling, the user can rotate them counterclockwise + * 90 degrees (in addition to moving them left or right), provided that the + * rotation puts the blots in empty spaces. The table of shapes is set up + * so that each shape contains the index of the new shape obtained by + * rotating the current shape. Due to symmetry, each shape has exactly + * 1, 2, or 4 rotations total; the first 7 entries in the table represent + * the primary shapes, and the remaining 12 represent their various + * rotated forms. + */ +struct shape { + int 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[random() % 7]) + +extern const struct shape *nextshape; + +/* + * Shapes fall at a rate faster than once per second. + * + * The initial rate is determined by dividing 1 million microseconds + * by the game `level'. (This is at most 1 million, or one second.) + * Each time the fall-rate is used, it is decreased a little bit, + * depending on its current value, via the `faster' macro below. + * The value eventually reaches a limit, and things stop going faster, + * but by then the game is utterly impossible. + */ +extern long fallrate; /* less than 1 million; smaller => faster */ +#define faster() (fallrate -= fallrate / 3000) + +/* + * Game level must be between 1 and 9. This controls the initial fall rate + * and affects scoring. + */ +#define MINLEVEL 1 +#define MAXLEVEL 9 + +/* + * Scoring is as follows: + * + * When the shape comes to rest, and is integrated into the board, + * we score one point. If the shape is high up (at a low-numbered row), + * and the user hits the space bar, the shape plummets all the way down, + * and we score a point for each row it falls (plus one more as soon as + * we find that it is at rest and integrate it---until then, it can + * still be moved or rotated). + */ +extern int score; /* the obvious thing */ +extern gid_t gid, egid; + +extern char key_msg[100]; +extern int showpreview; +extern int nocolor; + +int fits_in(const struct shape *, int); +void place(const struct shape *, int, int); +void stop(const char *) __dead; |