xref: /netbsd-src/games/tetris/tetris.c (revision d92552c3ea104e1b0b9116663d6c92428934d388)
1*d92552c3Snia /*	$NetBSD: tetris.c,v 1.34 2023/07/01 10:51:35 nia Exp $	*/
2aaeacec8Scgd 
3fece8b50Scgd /*-
4fece8b50Scgd  * Copyright (c) 1992, 1993
5fece8b50Scgd  *	The Regents of the University of California.  All rights reserved.
6fece8b50Scgd  *
7fece8b50Scgd  * This code is derived from software contributed to Berkeley by
8fece8b50Scgd  * Chris Torek and Darren F. Provine.
9fece8b50Scgd  *
10fece8b50Scgd  * Redistribution and use in source and binary forms, with or without
11fece8b50Scgd  * modification, are permitted provided that the following conditions
12fece8b50Scgd  * are met:
13fece8b50Scgd  * 1. Redistributions of source code must retain the above copyright
14fece8b50Scgd  *    notice, this list of conditions and the following disclaimer.
15fece8b50Scgd  * 2. Redistributions in binary form must reproduce the above copyright
16fece8b50Scgd  *    notice, this list of conditions and the following disclaimer in the
17fece8b50Scgd  *    documentation and/or other materials provided with the distribution.
18e5aeb4eaSagc  * 3. Neither the name of the University nor the names of its contributors
19fece8b50Scgd  *    may be used to endorse or promote products derived from this software
20fece8b50Scgd  *    without specific prior written permission.
21fece8b50Scgd  *
22fece8b50Scgd  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23fece8b50Scgd  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24fece8b50Scgd  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25fece8b50Scgd  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26fece8b50Scgd  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27fece8b50Scgd  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28fece8b50Scgd  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29fece8b50Scgd  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30fece8b50Scgd  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31fece8b50Scgd  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32fece8b50Scgd  * SUCH DAMAGE.
33fece8b50Scgd  *
34fece8b50Scgd  *	@(#)tetris.c	8.1 (Berkeley) 5/31/93
35fece8b50Scgd  */
36fece8b50Scgd 
37161dd449Slukem #include <sys/cdefs.h>
38fece8b50Scgd #ifndef lint
392fe2731dSlukem __COPYRIGHT("@(#) Copyright (c) 1992, 1993\
402fe2731dSlukem  The Regents of the University of California.  All rights reserved.");
41fece8b50Scgd #endif /* not lint */
42fece8b50Scgd 
43fece8b50Scgd /*
44fece8b50Scgd  * Tetris (or however it is spelled).
45fece8b50Scgd  */
46fece8b50Scgd 
47fece8b50Scgd #include <sys/time.h>
48fece8b50Scgd 
49233f519dSjsm #include <err.h>
505367f340Sjsm #include <fcntl.h>
51fece8b50Scgd #include <signal.h>
52fece8b50Scgd #include <stdio.h>
53fece8b50Scgd #include <stdlib.h>
54fece8b50Scgd #include <string.h>
55fece8b50Scgd #include <unistd.h>
56fece8b50Scgd 
57fece8b50Scgd #include "input.h"
58fece8b50Scgd #include "scores.h"
59fece8b50Scgd #include "screen.h"
60fece8b50Scgd #include "tetris.h"
61fece8b50Scgd 
62e2dbfa95Sjsm cell	board[B_SIZE];		/* 1 => occupied, 0 => empty */
63e2dbfa95Sjsm 
64e2dbfa95Sjsm int	Rows, Cols;		/* current screen size */
65ff18370bSnat int	Offset;			/* used to center board & shapes */
66e2dbfa95Sjsm 
671e99780eSdholland static const struct shape *curshape;
68e2dbfa95Sjsm const struct shape *nextshape;
69e2dbfa95Sjsm 
70e2dbfa95Sjsm long	fallrate;		/* less than 1 million; smaller => faster */
71e2dbfa95Sjsm 
72e2dbfa95Sjsm int	score;			/* the obvious thing */
735367f340Sjsm gid_t	gid, egid;
745367f340Sjsm 
75e2dbfa95Sjsm char	key_msg[100];
76e2dbfa95Sjsm int	showpreview;
7768006fcaSpgoyette int	nocolor;
78e2dbfa95Sjsm 
79cb5fd834Sjsm static void elide(void);
80cb5fd834Sjsm static void setup_board(void);
811e99780eSdholland static void onintr(int) __dead;
821e99780eSdholland static void usage(void) __dead;
83fece8b50Scgd 
84fece8b50Scgd /*
85fece8b50Scgd  * Set up the initial board.  The bottom display row is completely set,
86fece8b50Scgd  * along with another (hidden) row underneath that.  Also, the left and
87fece8b50Scgd  * right edges are set.
88fece8b50Scgd  */
89fece8b50Scgd static void
setup_board(void)90bbc67e0fSdholland setup_board(void)
91fece8b50Scgd {
9210b0b6bcSwiz 	int i;
9310b0b6bcSwiz 	cell *p;
94fece8b50Scgd 
95fece8b50Scgd 	p = board;
96fece8b50Scgd 	for (i = B_SIZE; i; i--)
97a5d27926Schristos 		*p++ = (i <= (2 * B_COLS) || (i % B_COLS) < 2) ? 7 : 0;
98fece8b50Scgd }
99fece8b50Scgd 
100fece8b50Scgd /*
101fece8b50Scgd  * Elide any full active rows.
102fece8b50Scgd  */
103fece8b50Scgd static void
elide(void)104bbc67e0fSdholland elide(void)
105fece8b50Scgd {
10610b0b6bcSwiz 	int i, j, base;
10710b0b6bcSwiz 	cell *p;
108fece8b50Scgd 
109fece8b50Scgd 	for (i = A_FIRST; i < A_LAST; i++) {
110fece8b50Scgd 		base = i * B_COLS + 1;
111fece8b50Scgd 		p = &board[base];
112fece8b50Scgd 		for (j = B_COLS - 2; *p++ != 0;) {
113fece8b50Scgd 			if (--j <= 0) {
114fece8b50Scgd 				/* this row is to be elided */
115e31adf91Sperry 				memset(&board[base], 0, B_COLS - 2);
116fece8b50Scgd 				scr_update();
117fece8b50Scgd 				tsleep();
118fece8b50Scgd 				while (--base != 0)
119fece8b50Scgd 					board[base + B_COLS] = board[base];
120099f0359Schristos 				/* don't forget to clear 0th row */
121099f0359Schristos 				memset(&board[1], 0, B_COLS - 2);
122fece8b50Scgd 				scr_update();
123fece8b50Scgd 				tsleep();
124fece8b50Scgd 				break;
125fece8b50Scgd 			}
126fece8b50Scgd 		}
127fece8b50Scgd 	}
128fece8b50Scgd }
129fece8b50Scgd 
130fece8b50Scgd int
main(int argc,char * argv[])131bbc67e0fSdholland main(int argc, char *argv[])
132fece8b50Scgd {
13310b0b6bcSwiz 	int pos, c;
13410b0b6bcSwiz 	const char *keys;
13510b0b6bcSwiz 	int level = 2;
1362343aabbSmrg #define NUMKEYS 7
1372343aabbSmrg 	char key_write[NUMKEYS][10];
138*d92552c3Snia 	char *nocolor_env;
139fece8b50Scgd 	int ch, i, j;
1405367f340Sjsm 	int fd;
1415367f340Sjsm 
1425367f340Sjsm 	gid = getgid();
1435367f340Sjsm 	egid = getegid();
1445367f340Sjsm 	setegid(gid);
1455367f340Sjsm 
1465367f340Sjsm 	fd = open("/dev/null", O_RDONLY);
1475367f340Sjsm 	if (fd < 3)
1485367f340Sjsm 		exit(1);
1495367f340Sjsm 	close(fd);
150fece8b50Scgd 
1512343aabbSmrg 	keys = "jkl pqn";
152fece8b50Scgd 
153990812d6Spgoyette 	while ((ch = getopt(argc, argv, "bk:l:ps")) != -1)
154fece8b50Scgd 		switch(ch) {
155990812d6Spgoyette 		case 'b':
15668006fcaSpgoyette 			nocolor = 1;
15768006fcaSpgoyette 			break;
158fece8b50Scgd 		case 'k':
1592343aabbSmrg 			if (strlen(keys = optarg) != NUMKEYS)
160fece8b50Scgd 				usage();
161fece8b50Scgd 			break;
162fece8b50Scgd 		case 'l':
163fece8b50Scgd 			level = atoi(optarg);
164fece8b50Scgd 			if (level < MINLEVEL || level > MAXLEVEL) {
165233f519dSjsm 				errx(1, "level must be from %d to %d",
166fece8b50Scgd 				     MINLEVEL, MAXLEVEL);
167fece8b50Scgd 			}
168fece8b50Scgd 			break;
1696cfbee59Shubertf 		case 'p':
1706cfbee59Shubertf 			showpreview = 1;
1716cfbee59Shubertf 			break;
172fece8b50Scgd 		case 's':
173fece8b50Scgd 			showscores(0);
174fece8b50Scgd 			exit(0);
175fece8b50Scgd 		case '?':
176fece8b50Scgd 		default:
177fece8b50Scgd 			usage();
178fece8b50Scgd 		}
179fece8b50Scgd 
180fece8b50Scgd 	argc -= optind;
181fece8b50Scgd 	argv += optind;
182fece8b50Scgd 
183fece8b50Scgd 	if (argc)
184fece8b50Scgd 		usage();
185fece8b50Scgd 
186*d92552c3Snia 	nocolor_env = getenv("NO_COLOR");
187*d92552c3Snia 
188*d92552c3Snia 	if (nocolor_env != NULL && nocolor_env[0] != '\0')
189*d92552c3Snia 		nocolor = 1;
190*d92552c3Snia 
191fece8b50Scgd 	fallrate = 1000000 / level;
192fece8b50Scgd 
1932343aabbSmrg 	for (i = 0; i <= (NUMKEYS-1); i++) {
1942343aabbSmrg 		for (j = i+1; j <= (NUMKEYS-1); j++) {
195fece8b50Scgd 			if (keys[i] == keys[j]) {
196233f519dSjsm 				errx(1, "duplicate command keys specified.");
197fece8b50Scgd 			}
198fece8b50Scgd 		}
199fece8b50Scgd 		if (keys[i] == ' ')
200fece8b50Scgd 			strcpy(key_write[i], "<space>");
201fece8b50Scgd 		else {
202fece8b50Scgd 			key_write[i][0] = keys[i];
203fece8b50Scgd 			key_write[i][1] = '\0';
204fece8b50Scgd 		}
205fece8b50Scgd 	}
206fece8b50Scgd 
207b3988369Sdholland 	snprintf(key_msg, sizeof(key_msg),
2082343aabbSmrg "%s - left  %s - rotate  %s - right  %s - drop  %s - pause  %s - quit  %s - down",
209fece8b50Scgd 		key_write[0], key_write[1], key_write[2], key_write[3],
2102343aabbSmrg 		key_write[4], key_write[5], key_write[6]);
211fece8b50Scgd 
212fece8b50Scgd 	(void)signal(SIGINT, onintr);
213fece8b50Scgd 	scr_init();
214fece8b50Scgd 	setup_board();
215fece8b50Scgd 
216fece8b50Scgd 	scr_set();
217fece8b50Scgd 
218fece8b50Scgd 	pos = A_FIRST*B_COLS + (B_COLS/2)-1;
219f42c52beShubertf 	nextshape = randshape();
220fece8b50Scgd 	curshape = randshape();
221fece8b50Scgd 
222fece8b50Scgd 	scr_msg(key_msg, 1);
223fece8b50Scgd 
224fece8b50Scgd 	for (;;) {
225fece8b50Scgd 		place(curshape, pos, 1);
226fece8b50Scgd 		scr_update();
227fece8b50Scgd 		place(curshape, pos, 0);
228fece8b50Scgd 		c = tgetchar();
229fece8b50Scgd 		if (c < 0) {
230fece8b50Scgd 			/*
231fece8b50Scgd 			 * Timeout.  Move down if possible.
232fece8b50Scgd 			 */
233fece8b50Scgd 			if (fits_in(curshape, pos + B_COLS)) {
234fece8b50Scgd 				pos += B_COLS;
235fece8b50Scgd 				continue;
236fece8b50Scgd 			}
237fece8b50Scgd 
238fece8b50Scgd 			/*
239fece8b50Scgd 			 * Put up the current shape `permanently',
240fece8b50Scgd 			 * bump score, and elide any full rows.
241fece8b50Scgd 			 */
242fece8b50Scgd 			place(curshape, pos, 1);
243fece8b50Scgd 			score++;
244fece8b50Scgd 			elide();
245fece8b50Scgd 
246fece8b50Scgd 			/*
247fece8b50Scgd 			 * Choose a new shape.  If it does not fit,
248fece8b50Scgd 			 * the game is over.
249fece8b50Scgd 			 */
250f42c52beShubertf 			curshape = nextshape;
251f42c52beShubertf 			nextshape = randshape();
252fece8b50Scgd 			pos = A_FIRST*B_COLS + (B_COLS/2)-1;
253fece8b50Scgd 			if (!fits_in(curshape, pos))
254fece8b50Scgd 				break;
255fece8b50Scgd 			continue;
256fece8b50Scgd 		}
257fece8b50Scgd 
258fece8b50Scgd 		/*
259fece8b50Scgd 		 * Handle command keys.
260fece8b50Scgd 		 */
261fece8b50Scgd 		if (c == keys[5]) {
262fece8b50Scgd 			/* quit */
263fece8b50Scgd 			break;
264fece8b50Scgd 		}
265fece8b50Scgd 		if (c == keys[4]) {
266fece8b50Scgd 			static char msg[] =
267fece8b50Scgd 			    "paused - press RETURN to continue";
268fece8b50Scgd 
269fece8b50Scgd 			place(curshape, pos, 1);
270fece8b50Scgd 			do {
271fece8b50Scgd 				scr_update();
272fece8b50Scgd 				scr_msg(key_msg, 0);
273fece8b50Scgd 				scr_msg(msg, 1);
274fece8b50Scgd 				(void) fflush(stdout);
2759f61b804Splunky 			} while (rwait(NULL) == -1);
276fece8b50Scgd 			scr_msg(msg, 0);
277fece8b50Scgd 			scr_msg(key_msg, 1);
278fece8b50Scgd 			place(curshape, pos, 0);
279fece8b50Scgd 			continue;
280fece8b50Scgd 		}
281fece8b50Scgd 		if (c == keys[0]) {
282fece8b50Scgd 			/* move left */
283fece8b50Scgd 			if (fits_in(curshape, pos - 1))
284fece8b50Scgd 				pos--;
285fece8b50Scgd 			continue;
286fece8b50Scgd 		}
287fece8b50Scgd 		if (c == keys[1]) {
288fece8b50Scgd 			/* turn */
289092d3130Sjsm 			const struct shape *new = &shapes[curshape->rot];
290fece8b50Scgd 
291fece8b50Scgd 			if (fits_in(new, pos))
292fece8b50Scgd 				curshape = new;
293fece8b50Scgd 			continue;
294fece8b50Scgd 		}
295fece8b50Scgd 		if (c == keys[2]) {
296fece8b50Scgd 			/* move right */
297fece8b50Scgd 			if (fits_in(curshape, pos + 1))
298fece8b50Scgd 				pos++;
299fece8b50Scgd 			continue;
300fece8b50Scgd 		}
301fece8b50Scgd 		if (c == keys[3]) {
302fece8b50Scgd 			/* move to bottom */
303fece8b50Scgd 			while (fits_in(curshape, pos + B_COLS)) {
304fece8b50Scgd 				pos += B_COLS;
305fece8b50Scgd 				score++;
306fece8b50Scgd 			}
307fece8b50Scgd 			continue;
308fece8b50Scgd 		}
3092343aabbSmrg 		if (c == keys[6]) {
3102343aabbSmrg 			/* move down */
3112343aabbSmrg 			if (fits_in(curshape, pos + B_COLS)) {
3122343aabbSmrg 				pos += B_COLS;
3132343aabbSmrg 				score++;
3142343aabbSmrg 			}
3152343aabbSmrg 			continue;
3162343aabbSmrg 		}
317f42c52beShubertf 		if (c == '\f') {
318fece8b50Scgd 			scr_clear();
319f42c52beShubertf 			scr_msg(key_msg, 1);
320f42c52beShubertf 		}
321fece8b50Scgd 	}
322fece8b50Scgd 
323fece8b50Scgd 	scr_clear();
324fece8b50Scgd 	scr_end();
325fece8b50Scgd 
326fece8b50Scgd 	(void)printf("Your score:  %d point%s  x  level %d  =  %d\n",
327fece8b50Scgd 	    score, score == 1 ? "" : "s", level, score * level);
328fece8b50Scgd 	savescore(level);
329fece8b50Scgd 
330fece8b50Scgd 	printf("\nHit RETURN to see high scores, ^C to skip.\n");
331fece8b50Scgd 
332fece8b50Scgd 	while ((i = getchar()) != '\n')
333fece8b50Scgd 		if (i == EOF)
334fece8b50Scgd 			break;
335fece8b50Scgd 
336fece8b50Scgd 	showscores(level);
337fece8b50Scgd 
338fece8b50Scgd 	exit(0);
339fece8b50Scgd }
340fece8b50Scgd 
3411e99780eSdholland static void
onintr(int signo __unused)342bbc67e0fSdholland onintr(int signo __unused)
343fece8b50Scgd {
344fece8b50Scgd 	scr_clear();
345fece8b50Scgd 	scr_end();
346fece8b50Scgd 	exit(0);
347fece8b50Scgd }
348fece8b50Scgd 
3491e99780eSdholland static void
usage(void)350bbc67e0fSdholland usage(void)
351fece8b50Scgd {
352cd1ecdeaSdholland 	(void)fprintf(stderr, "usage: %s [-bps] [-k keys] [-l level]\n",
35366880467Spgoyette 	    getprogname());
354fece8b50Scgd 	exit(1);
355fece8b50Scgd }
356