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