xref: /openbsd-src/games/tetris/tetris.c (revision bc5a8259a456844c67e446bf6bd66575acc64837)
1*bc5a8259Sbeck /*	$OpenBSD: tetris.c,v 1.35 2021/07/12 15:09:18 beck Exp $	*/
2df930be7Sderaadt /*	$NetBSD: tetris.c,v 1.2 1995/04/22 07:42:47 cgd Exp $	*/
3df930be7Sderaadt 
4df930be7Sderaadt /*-
5df930be7Sderaadt  * Copyright (c) 1992, 1993
6df930be7Sderaadt  *	The Regents of the University of California.  All rights reserved.
7df930be7Sderaadt  *
8df930be7Sderaadt  * This code is derived from software contributed to Berkeley by
9df930be7Sderaadt  * Chris Torek and Darren F. Provine.
10df930be7Sderaadt  *
11df930be7Sderaadt  * Redistribution and use in source and binary forms, with or without
12df930be7Sderaadt  * modification, are permitted provided that the following conditions
13df930be7Sderaadt  * are met:
14df930be7Sderaadt  * 1. Redistributions of source code must retain the above copyright
15df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer.
16df930be7Sderaadt  * 2. Redistributions in binary form must reproduce the above copyright
17df930be7Sderaadt  *    notice, this list of conditions and the following disclaimer in the
18df930be7Sderaadt  *    documentation and/or other materials provided with the distribution.
197a09557bSmillert  * 3. Neither the name of the University nor the names of its contributors
20df930be7Sderaadt  *    may be used to endorse or promote products derived from this software
21df930be7Sderaadt  *    without specific prior written permission.
22df930be7Sderaadt  *
23df930be7Sderaadt  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24df930be7Sderaadt  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25df930be7Sderaadt  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26df930be7Sderaadt  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27df930be7Sderaadt  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28df930be7Sderaadt  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29df930be7Sderaadt  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30df930be7Sderaadt  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31df930be7Sderaadt  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32df930be7Sderaadt  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33df930be7Sderaadt  * SUCH DAMAGE.
34df930be7Sderaadt  *
35df930be7Sderaadt  *	@(#)tetris.c	8.1 (Berkeley) 5/31/93
36df930be7Sderaadt  */
37df930be7Sderaadt 
38df930be7Sderaadt /*
39df930be7Sderaadt  * Tetris (or however it is spelled).
40df930be7Sderaadt  */
41df930be7Sderaadt 
42dcf934a0Spjanzen #include <err.h>
4341b00738Srob #include <errno.h>
4434278d36Sguenther #include <limits.h>
45df930be7Sderaadt #include <signal.h>
46df930be7Sderaadt #include <stdio.h>
47df930be7Sderaadt #include <stdlib.h>
48df930be7Sderaadt #include <string.h>
49df930be7Sderaadt #include <unistd.h>
50df930be7Sderaadt 
51df930be7Sderaadt #include "input.h"
52df930be7Sderaadt #include "scores.h"
53df930be7Sderaadt #include "screen.h"
54df930be7Sderaadt #include "tetris.h"
55df930be7Sderaadt 
56f26e040bStb #define NUMKEYS 6
57f26e040bStb 
58b124352eSpjanzen cell	board[B_SIZE];
59b124352eSpjanzen int	Rows, Cols;
608e58c664Smickey const struct shape *curshape;
618e58c664Smickey const struct shape *nextshape;
62b124352eSpjanzen long	fallrate;
63b124352eSpjanzen int	score;
64b124352eSpjanzen char	key_msg[100];
6541b00738Srob char	scorepath[PATH_MAX];
6649cea301Smickey int	showpreview, classic;
67b124352eSpjanzen 
68c72b5b24Smillert static void		 elide(void);
69c72b5b24Smillert void			 onintr(int);
702010f3c8Smestre const struct shape	*randshape(void);
712010f3c8Smestre static void		 setup_board(void);
72f0628b46Smestre __dead void		 usage(void);
73df930be7Sderaadt 
74df930be7Sderaadt /*
75df930be7Sderaadt  * Set up the initial board.  The bottom display row is completely set,
76df930be7Sderaadt  * along with another (hidden) row underneath that.  Also, the left and
77df930be7Sderaadt  * right edges are set.
78df930be7Sderaadt  */
79df930be7Sderaadt static void
setup_board(void)80ff8320a7Sderaadt setup_board(void)
81df930be7Sderaadt {
8297419aa0Spjanzen 	int i;
8397419aa0Spjanzen 	cell *p;
84df930be7Sderaadt 
85df930be7Sderaadt 	p = board;
86df930be7Sderaadt 	for (i = B_SIZE; i; i--)
87df930be7Sderaadt 		*p++ = i <= (2 * B_COLS) || (i % B_COLS) < 2;
88df930be7Sderaadt }
89df930be7Sderaadt 
90df930be7Sderaadt /*
91df930be7Sderaadt  * Elide any full active rows.
92df930be7Sderaadt  */
93df930be7Sderaadt static void
elide(void)94ff8320a7Sderaadt elide(void)
95df930be7Sderaadt {
96a7200f5aStedu 	int rows = 0;
9797419aa0Spjanzen 	int i, j, base;
9897419aa0Spjanzen 	cell *p;
99df930be7Sderaadt 
100df930be7Sderaadt 	for (i = A_FIRST; i < A_LAST; i++) {
101df930be7Sderaadt 		base = i * B_COLS + 1;
102df930be7Sderaadt 		p = &board[base];
103df930be7Sderaadt 		for (j = B_COLS - 2; *p++ != 0;) {
104df930be7Sderaadt 			if (--j <= 0) {
105df930be7Sderaadt 				/* this row is to be elided */
106a7200f5aStedu 				rows++;
107dcf934a0Spjanzen 				memset(&board[base], 0, B_COLS - 2);
108df930be7Sderaadt 				scr_update();
109df930be7Sderaadt 				tsleep();
110df930be7Sderaadt 				while (--base != 0)
111df930be7Sderaadt 					board[base + B_COLS] = board[base];
112ca027646Stb 				memset(&board[1], 0, B_COLS - 2);
113df930be7Sderaadt 				scr_update();
114df930be7Sderaadt 				tsleep();
115df930be7Sderaadt 				break;
116df930be7Sderaadt 			}
117df930be7Sderaadt 		}
118df930be7Sderaadt 	}
119a7200f5aStedu 	switch (rows) {
120a7200f5aStedu 	case 1:
121a7200f5aStedu 		score += 10;
122a7200f5aStedu 		break;
123a7200f5aStedu 	case 2:
124a7200f5aStedu 		score += 30;
125a7200f5aStedu 		break;
126a7200f5aStedu 	case 3:
127a7200f5aStedu 		score += 70;
128a7200f5aStedu 		break;
129a7200f5aStedu 	case 4:
130a7200f5aStedu 		score += 150;
131a7200f5aStedu 		break;
132a7200f5aStedu 	default:
133a7200f5aStedu 		break;
134a7200f5aStedu 	}
135df930be7Sderaadt }
136df930be7Sderaadt 
1378e58c664Smickey const struct shape *
randshape(void)138ff8320a7Sderaadt randshape(void)
139f627625dSpjanzen {
1408e58c664Smickey 	const struct shape *tmp;
141f627625dSpjanzen 	int i, j;
142f627625dSpjanzen 
14366e49541Snaddy 	tmp = &shapes[arc4random_uniform(7)];
14466e49541Snaddy 	j = arc4random_uniform(4);
145f627625dSpjanzen 	for (i = 0; i < j; i++)
14649cea301Smickey 		tmp = &shapes[classic? tmp->rotc : tmp->rot];
147f627625dSpjanzen 	return (tmp);
148f627625dSpjanzen }
149f627625dSpjanzen 
150df930be7Sderaadt int
main(int argc,char * argv[])151ff8320a7Sderaadt main(int argc, char *argv[])
152df930be7Sderaadt {
15397419aa0Spjanzen 	int pos, c;
15497419aa0Spjanzen 	char *keys;
15541b00738Srob 	int level = 2, ret;
156f26e040bStb 	char key_write[NUMKEYS][10];
15741b00738Srob 	char *home;
1580b8d5d5cSray 	const char *errstr;
159df930be7Sderaadt 	int ch, i, j;
160df930be7Sderaadt 
16141b00738Srob 	home = getenv("HOME");
16241b00738Srob 	if (home == NULL || *home == '\0')
16341b00738Srob 		err(1, "getenv");
16441b00738Srob 
16541b00738Srob 	ret = snprintf(scorepath, sizeof(scorepath), "%s/%s", home,
16641b00738Srob 	    ".tetris.scores");
16741b00738Srob 	if (ret < 0 || ret >= PATH_MAX)
16841b00738Srob 		errc(1, ENAMETOOLONG, "%s/%s", home, ".tetris.scores");
16941b00738Srob 
17041b00738Srob 	if (pledge("stdio rpath wpath cpath tty unveil", NULL) == -1)
1718f55f2bbStb 		err(1, "pledge");
172df930be7Sderaadt 
1738f55f2bbStb 	keys = "jkl pq";
17475b7a267Stholo 
17549cea301Smickey 	classic = showpreview = 0;
1760b8d5d5cSray 	while ((ch = getopt(argc, argv, "ck:l:ps")) != -1)
177df930be7Sderaadt 		switch(ch) {
17849cea301Smickey 		case 'c':
17949cea301Smickey 			/*
18049cea301Smickey 			 * this means:
18149cea301Smickey 			 *	- rotate the other way;
18249cea301Smickey 			 *	- no reverse video.
18349cea301Smickey 			 */
18449cea301Smickey 			classic = 1;
18549cea301Smickey 			break;
186df930be7Sderaadt 		case 'k':
187f26e040bStb 			if (strlen(keys = optarg) != NUMKEYS)
188df930be7Sderaadt 				usage();
189df930be7Sderaadt 			break;
190df930be7Sderaadt 		case 'l':
1910b8d5d5cSray 			level = (int)strtonum(optarg, MINLEVEL, MAXLEVEL,
1920b8d5d5cSray 			    &errstr);
1930b8d5d5cSray 			if (errstr)
194dcf934a0Spjanzen 				errx(1, "level must be from %d to %d",
195df930be7Sderaadt 				    MINLEVEL, MAXLEVEL);
196df930be7Sderaadt 			break;
197f627625dSpjanzen 		case 'p':
198f627625dSpjanzen 			showpreview = 1;
199f627625dSpjanzen 			break;
200df930be7Sderaadt 		case 's':
201df930be7Sderaadt 			showscores(0);
20217641e31Stb 			return 0;
203df930be7Sderaadt 		default:
204df930be7Sderaadt 			usage();
205df930be7Sderaadt 		}
206df930be7Sderaadt 
207df930be7Sderaadt 	argc -= optind;
208df930be7Sderaadt 	argv += optind;
209df930be7Sderaadt 
210df930be7Sderaadt 	if (argc)
211df930be7Sderaadt 		usage();
212df930be7Sderaadt 
213176f6ce7Stedu 	fallrate = 1000000000L / level;
214df930be7Sderaadt 
215f26e040bStb 	for (i = 0; i < NUMKEYS; i++) {
216f26e040bStb 		for (j = i+1; j < NUMKEYS; j++) {
217dcf934a0Spjanzen 			if (keys[i] == keys[j])
2182cbe7629Spjanzen 				errx(1, "duplicate command keys specified.");
219df930be7Sderaadt 		}
220df930be7Sderaadt 		if (keys[i] == ' ')
221fec224e0Sderaadt 			strlcpy(key_write[i], "<space>", sizeof key_write[i]);
222df930be7Sderaadt 		else {
223df930be7Sderaadt 			key_write[i][0] = keys[i];
224df930be7Sderaadt 			key_write[i][1] = '\0';
225df930be7Sderaadt 		}
226df930be7Sderaadt 	}
227df930be7Sderaadt 
22842ceebb3Sderaadt 	snprintf(key_msg, sizeof key_msg,
229df930be7Sderaadt "%s - left   %s - rotate   %s - right   %s - drop   %s - pause   %s - quit",
230df930be7Sderaadt 		key_write[0], key_write[1], key_write[2], key_write[3],
231df930be7Sderaadt 		key_write[4], key_write[5]);
232df930be7Sderaadt 
233df930be7Sderaadt 	(void)signal(SIGINT, onintr);
234df930be7Sderaadt 	scr_init();
23541b00738Srob 
23641b00738Srob 	if (unveil(scorepath, "rwc") == -1)
237*bc5a8259Sbeck 		err(1, "unveil %s", scorepath);
23841b00738Srob 
23941b00738Srob 	if (pledge("stdio rpath wpath cpath tty", NULL) == -1)
24041b00738Srob 		err(1, "pledge");
24141b00738Srob 
242df930be7Sderaadt 	setup_board();
243df930be7Sderaadt 
244df930be7Sderaadt 	scr_set();
245df930be7Sderaadt 
246df930be7Sderaadt 	pos = A_FIRST*B_COLS + (B_COLS/2)-1;
247f627625dSpjanzen 	nextshape = randshape();
248df930be7Sderaadt 	curshape = randshape();
249df930be7Sderaadt 
250df930be7Sderaadt 	scr_msg(key_msg, 1);
251df930be7Sderaadt 
252df930be7Sderaadt 	for (;;) {
253df930be7Sderaadt 		place(curshape, pos, 1);
254df930be7Sderaadt 		scr_update();
255df930be7Sderaadt 		place(curshape, pos, 0);
256df930be7Sderaadt 		c = tgetchar();
257df930be7Sderaadt 		if (c < 0) {
258df930be7Sderaadt 			/*
259df930be7Sderaadt 			 * Timeout.  Move down if possible.
260df930be7Sderaadt 			 */
261df930be7Sderaadt 			if (fits_in(curshape, pos + B_COLS)) {
262df930be7Sderaadt 				pos += B_COLS;
263df930be7Sderaadt 				continue;
264df930be7Sderaadt 			}
265df930be7Sderaadt 
266df930be7Sderaadt 			/*
267df930be7Sderaadt 			 * Put up the current shape `permanently',
268df930be7Sderaadt 			 * bump score, and elide any full rows.
269df930be7Sderaadt 			 */
270df930be7Sderaadt 			place(curshape, pos, 1);
271df930be7Sderaadt 			score++;
272df930be7Sderaadt 			elide();
273df930be7Sderaadt 
274df930be7Sderaadt 			/*
275df930be7Sderaadt 			 * Choose a new shape.  If it does not fit,
276df930be7Sderaadt 			 * the game is over.
277df930be7Sderaadt 			 */
278f627625dSpjanzen 			curshape = nextshape;
279f627625dSpjanzen 			nextshape = randshape();
280df930be7Sderaadt 			pos = A_FIRST*B_COLS + (B_COLS/2)-1;
281df930be7Sderaadt 			if (!fits_in(curshape, pos))
282df930be7Sderaadt 				break;
283df930be7Sderaadt 			continue;
284df930be7Sderaadt 		}
285df930be7Sderaadt 
286df930be7Sderaadt 		/*
287df930be7Sderaadt 		 * Handle command keys.
288df930be7Sderaadt 		 */
289df930be7Sderaadt 		if (c == keys[5]) {
290df930be7Sderaadt 			/* quit */
291df930be7Sderaadt 			break;
292df930be7Sderaadt 		}
293df930be7Sderaadt 		if (c == keys[4]) {
294df930be7Sderaadt 			static char msg[] =
295df930be7Sderaadt 			    "paused - press RETURN to continue";
296df930be7Sderaadt 
297df930be7Sderaadt 			place(curshape, pos, 1);
298df930be7Sderaadt 			do {
299df930be7Sderaadt 				scr_update();
300df930be7Sderaadt 				scr_msg(key_msg, 0);
301df930be7Sderaadt 				scr_msg(msg, 1);
302df930be7Sderaadt 				(void) fflush(stdout);
303176f6ce7Stedu 			} while (rwait(NULL) == -1);
304df930be7Sderaadt 			scr_msg(msg, 0);
305df930be7Sderaadt 			scr_msg(key_msg, 1);
306df930be7Sderaadt 			place(curshape, pos, 0);
307df930be7Sderaadt 			continue;
308df930be7Sderaadt 		}
309df930be7Sderaadt 		if (c == keys[0]) {
310df930be7Sderaadt 			/* move left */
311df930be7Sderaadt 			if (fits_in(curshape, pos - 1))
312df930be7Sderaadt 				pos--;
313df930be7Sderaadt 			continue;
314df930be7Sderaadt 		}
315df930be7Sderaadt 		if (c == keys[1]) {
316df930be7Sderaadt 			/* turn */
3178e58c664Smickey 			const struct shape *new = &shapes[
31849cea301Smickey 			    classic? curshape->rotc : curshape->rot];
319df930be7Sderaadt 
320df930be7Sderaadt 			if (fits_in(new, pos))
321df930be7Sderaadt 				curshape = new;
322df930be7Sderaadt 			continue;
323df930be7Sderaadt 		}
324df930be7Sderaadt 		if (c == keys[2]) {
325df930be7Sderaadt 			/* move right */
326df930be7Sderaadt 			if (fits_in(curshape, pos + 1))
327df930be7Sderaadt 				pos++;
328df930be7Sderaadt 			continue;
329df930be7Sderaadt 		}
330df930be7Sderaadt 		if (c == keys[3]) {
331df930be7Sderaadt 			/* move to bottom */
332df930be7Sderaadt 			while (fits_in(curshape, pos + B_COLS)) {
333df930be7Sderaadt 				pos += B_COLS;
334df930be7Sderaadt 				score++;
335df930be7Sderaadt 			}
336df930be7Sderaadt 			continue;
337df930be7Sderaadt 		}
338f627625dSpjanzen 		if (c == '\f') {
339df930be7Sderaadt 			scr_clear();
340f627625dSpjanzen 			scr_msg(key_msg, 1);
341f627625dSpjanzen 		}
342df930be7Sderaadt 	}
343df930be7Sderaadt 
344df930be7Sderaadt 	scr_clear();
345df930be7Sderaadt 	scr_end();
346df930be7Sderaadt 
347f627625dSpjanzen 	if (showpreview == 0)
348df930be7Sderaadt 		(void)printf("Your score:  %d point%s  x  level %d  =  %d\n",
349df930be7Sderaadt 		    score, score == 1 ? "" : "s", level, score * level);
350f627625dSpjanzen 	else {
351f627625dSpjanzen 		(void)printf("Your score:  %d point%s x level %d x preview penalty %0.3f = %d\n",
352f627625dSpjanzen 		    score, score == 1 ? "" : "s", level, (double)PRE_PENALTY,
353f627625dSpjanzen 		    (int)(score * level * PRE_PENALTY));
354f627625dSpjanzen 		score = score * PRE_PENALTY;
355f627625dSpjanzen 	}
356df930be7Sderaadt 	savescore(level);
357df930be7Sderaadt 
358df930be7Sderaadt 	printf("\nHit RETURN to see high scores, ^C to skip.\n");
359df930be7Sderaadt 
360df930be7Sderaadt 	while ((i = getchar()) != '\n')
361df930be7Sderaadt 		if (i == EOF)
362df930be7Sderaadt 			break;
363df930be7Sderaadt 
364df930be7Sderaadt 	showscores(level);
365df930be7Sderaadt 
36617641e31Stb 	return 0;
367df930be7Sderaadt }
368df930be7Sderaadt 
369df930be7Sderaadt void
onintr(int signo)370ff8320a7Sderaadt onintr(int signo)
371df930be7Sderaadt {
3727af11270Sderaadt 	scr_clear();		/* XXX signal race */
3737af11270Sderaadt 	scr_end();		/* XXX signal race */
3747af11270Sderaadt 	_exit(0);
375df930be7Sderaadt }
376df930be7Sderaadt 
377df930be7Sderaadt void
usage(void)378ff8320a7Sderaadt usage(void)
379df930be7Sderaadt {
3806fa5e1daSmestre 	(void)fprintf(stderr, "usage: %s [-cps] [-k keys] "
3816fa5e1daSmestre 	    "[-l level]\n", getprogname());
382df930be7Sderaadt 	exit(1);
383df930be7Sderaadt }
384