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