Minor refactor to simplify signal handler
[tetris.git] / tetris.c
blobd63f4e46a39301e9346a791e3464321b7c85875c
1 /* Micro Tetris, based on an obfuscated tetris, 1989 IOCCC Best Game
3 * Copyright (c) 1989 John Tromp <john.tromp@gmail.com>
4 * Copyright (c) 2009-2021 Joachim Wiberg <troglobit@gmail.com>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 * See the following URLs for more information, first John Tromp's page about
19 * the game http://homepages.cwi.nl/~tromp/tetris.html then there's the entry
20 * page at IOCCC http://www.ioccc.org/1989/tromp.hint
23 #include <signal.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/time.h>
28 #include <sys/wait.h>
29 #include <termios.h>
30 #include <time.h>
31 #include <unistd.h>
33 #define clrscr() puts ("\033[2J\033[1;1H")
34 #define gotoxy(x,y) printf("\033[%d;%dH", y, x)
35 #define hidecursor() puts ("\033[?25l")
36 #define showcursor() puts ("\033[?25h")
37 #define bgcolor(c,s) printf("\033[%dm" s, c ? c + 40 : 0)
39 #define SIGNAL(signo, cb) \
40 sigemptyset(&sa.sa_mask); \
41 sigaddset(&sa.sa_mask, signo); \
42 sa.sa_flags = 0; \
43 sa.sa_handler = cb; \
44 sigaction(signo, &sa, NULL)
46 /* the board */
47 #define B_COLS 12
48 #define B_ROWS 23
49 #define B_SIZE (B_ROWS * B_COLS)
51 #define TL -B_COLS-1 /* top left */
52 #define TC -B_COLS /* top center */
53 #define TR -B_COLS+1 /* top right */
54 #define ML -1 /* middle left */
55 #define MR 1 /* middle right */
56 #define BL B_COLS-1 /* bottom left */
57 #define BC B_COLS /* bottom center */
58 #define BR B_COLS+1 /* bottom right */
60 /* These can be overridden by the user. */
61 #define DEFAULT_KEYS "jkl pq"
62 #define KEY_LEFT 0
63 #define KEY_RIGHT 2
64 #define KEY_ROTATE 1
65 #define KEY_DROP 3
66 #define KEY_PAUSE 4
67 #define KEY_QUIT 5
69 #define HIGH_SCORE_FILE "/var/games/tetris.scores"
70 #define TEMP_SCORE_FILE "/tmp/tetris-tmp.scores"
72 static volatile sig_atomic_t running = 1;
74 static struct termios savemodes;
75 static int havemodes = 0;
77 static char *keys = DEFAULT_KEYS;
78 static int level = 1;
79 static int points = 0;
80 static int lines_cleared = 0;
81 static int board[B_SIZE], shadow[B_SIZE];
83 static int *peek_shape; /* peek preview of next shape */
84 static int pcolor;
85 static int *shape;
86 static int color;
88 static int shapes[] = {
89 7, TL, TC, MR, 2, /* ""__ */
90 8, TR, TC, ML, 3, /* __"" */
91 9, ML, MR, BC, 1, /* "|" */
92 3, TL, TC, ML, 4, /* square */
93 12, ML, BL, MR, 5, /* |""" */
94 15, ML, BR, MR, 6, /* """| */
95 18, ML, MR, 2, 7, /* ---- sticks out */
96 0, TC, ML, BL, 2, /* / */
97 1, TC, MR, BR, 3, /* \ */
98 10, TC, MR, BC, 1, /* |- */
99 11, TC, ML, MR, 1, /* _|_ */
100 2, TC, ML, BC, 1, /* -| */
101 13, TC, BC, BR, 5, /* |_ */
102 14, TR, ML, MR, 5, /* ___| */
103 4, TL, TC, BC, 5, /* "| */
104 16, TR, TC, BC, 6, /* |" */
105 17, TL, MR, ML, 6, /* |___ */
106 5, TC, BC, BL, 6, /* _| */
107 6, TC, BC, 2 * B_COLS, 7, /* | sticks out */
110 static void draw(int x, int y, int c)
112 gotoxy(x, y);
113 bgcolor(c, " ");
116 static int update(void)
118 int x, y;
120 #ifdef ENABLE_PREVIEW
121 static int shadow_preview[B_COLS * 10] = { 0 };
122 int preview[B_COLS * 10] = { 0 };
123 const int start = 5;
125 preview[2 * B_COLS + 1] = pcolor;
126 preview[2 * B_COLS + 1 + peek_shape[1]] = pcolor;
127 preview[2 * B_COLS + 1 + peek_shape[2]] = pcolor;
128 preview[2 * B_COLS + 1 + peek_shape[3]] = pcolor;
130 for (y = 0; y < 4; y++) {
131 for (x = 0; x < B_COLS; x++) {
132 if (preview[y * B_COLS + x] - shadow_preview[y * B_COLS + x]) {
133 int c = preview[y * B_COLS + x]; /* color */
135 shadow_preview[y * B_COLS + x] = c;
136 draw(x * 2 + 26 + 28, start + y, c);
140 #endif
142 /* Display board. */
143 for (y = 1; y < B_ROWS - 1; y++) {
144 for (x = 0; x < B_COLS; x++) {
145 if (board[y * B_COLS + x] - shadow[y * B_COLS + x]) {
146 int c = board[y * B_COLS + x]; /* color */
148 shadow[y * B_COLS + x] = c;
149 draw(x * 2 + 28, y, c);
154 /* Update points and level */
155 while (lines_cleared >= 10) {
156 lines_cleared -= 10;
157 level++;
160 #ifdef ENABLE_SCORE
161 /* Display current level and points */
162 gotoxy(26 + 28, 2);
163 printf("\033[0mLevel : %d", level);
164 gotoxy(26 + 28, 3);
165 printf("Points : %d", points);
166 #endif
167 #ifdef ENABLE_PREVIEW
168 gotoxy(26 + 28, 5);
169 printf("Preview:");
170 #endif
171 gotoxy(26 + 28, 10);
172 printf("Keys:");
173 fflush(stdout);
175 return getchar();
178 /* Check if shape fits in the current position */
179 static int fits_in(int *s, int pos)
181 if (board[pos] || board[pos + s[1]] || board[pos + s[2]] || board[pos + s[3]])
182 return 0;
184 return 1;
187 /* place shape at pos with color */
188 static void place(int *s, int pos, int c)
190 board[pos] = c;
191 board[pos + s[1]] = c;
192 board[pos + s[2]] = c;
193 board[pos + s[3]] = c;
196 static int *next_shape(void)
198 int pos = rand() % 7 * 5;
199 int *next = peek_shape;
201 peek_shape = &shapes[pos];
202 pcolor = peek_shape[4];
203 if (!next)
204 return next_shape();
205 color = next[4];
207 return next;
210 static void show_high_score(void)
212 #ifdef ENABLE_HIGH_SCORE
213 FILE *tmpscore;
215 if ((tmpscore = fopen(HIGH_SCORE_FILE, "a"))) {
216 char *name = getenv("LOGNAME");
218 if (!name)
219 name = "anonymous";
221 fprintf(tmpscore, "%7d\t %5d\t %3d\t%s\n", points * level, points, level, name);
222 fclose(tmpscore);
224 system("cat " HIGH_SCORE_FILE "| sort -rn | head -10 >" TEMP_SCORE_FILE
225 "&& cp " TEMP_SCORE_FILE " " HIGH_SCORE_FILE);
226 remove(TEMP_SCORE_FILE);
229 if (!access(HIGH_SCORE_FILE, R_OK)) {
230 // puts("\nHit RETURN to see high scores, ^C to skip.");
231 fprintf(stderr, " Score\tPoints\tLevel\tName\n");
232 system("cat " HIGH_SCORE_FILE);
234 #endif /* ENABLE_HIGH_SCORE */
237 static void show_online_help(void)
239 const int start = 11;
241 gotoxy(26 + 28, start);
242 puts("\033[0mj - left");
243 gotoxy(26 + 28, start + 1);
244 puts("k - rotate");
245 gotoxy(26 + 28, start + 2);
246 puts("l - right");
247 gotoxy(26 + 28, start + 3);
248 puts("space - drop");
249 gotoxy(26 + 28, start + 4);
250 puts("p - pause");
251 gotoxy(26 + 28, start + 5);
252 puts("q - quit");
255 /* Code stolen from http://c-faq.com/osdep/cbreak.html */
256 static int tty_init(void)
258 struct termios modmodes;
260 if (tcgetattr(fileno(stdin), &savemodes) < 0)
261 return -1;
263 havemodes = 1;
264 hidecursor();
266 /* "stty cbreak -echo" */
267 modmodes = savemodes;
268 modmodes.c_lflag &= ~ICANON;
269 modmodes.c_lflag &= ~ECHO;
270 modmodes.c_cc[VMIN] = 1;
271 modmodes.c_cc[VTIME] = 0;
273 return tcsetattr(fileno(stdin), TCSANOW, &modmodes);
276 static int tty_exit(void)
278 if (!havemodes)
279 return 0;
281 showcursor();
283 /* "stty sane" */
284 return tcsetattr(fileno(stdin), TCSANOW, &savemodes);
287 static void freeze(int enable)
289 sigset_t set;
291 sigemptyset(&set);
292 sigaddset(&set, SIGALRM);
294 sigprocmask(enable ? SIG_BLOCK : SIG_UNBLOCK, &set, NULL);
297 static void alarm_handler(int signo)
299 static long h[4];
301 (void)signo;
303 /* On init from main() */
304 if (!signo)
305 h[3] = 500000;
307 h[3] -= h[3] / (3000 - 10 * level);
308 setitimer(0, (struct itimerval *)h, 0);
311 static void exit_handler(int signo)
313 (void)signo;
314 running = 0;
317 static void sig_init(void)
319 struct sigaction sa;
321 SIGNAL(SIGINT, exit_handler);
322 SIGNAL(SIGTERM, exit_handler);
323 SIGNAL(SIGALRM, alarm_handler);
325 /* Start update timer. */
326 alarm_handler(0);
329 int main(void)
331 int c = 0, i, j, *ptr;
332 int pos = 17;
333 int *backup;
335 /* Initialize board, grey border, used to be white(7) */
336 ptr = board;
337 for (i = B_SIZE; i; i--)
338 *ptr++ = i < 25 || i % B_COLS < 2 ? 60 : 0;
340 srand((unsigned int)time(NULL));
341 if (tty_init() == -1)
342 return 1;
344 /* Set up signals */
345 sig_init();
347 clrscr();
348 show_online_help();
350 shape = next_shape();
351 while (running) {
352 if (c < 0) {
353 if (fits_in(shape, pos + B_COLS)) {
354 pos += B_COLS;
355 } else {
356 place(shape, pos, color);
357 ++points;
358 for (j = 0; j < 252; j = B_COLS * (j / B_COLS + 1)) {
359 for (; board[++j];) {
360 if (j % B_COLS == 10) {
361 lines_cleared++;
363 for (; j % B_COLS; board[j--] = 0)
365 c = update();
367 for (; --j; board[j + B_COLS] = board[j])
369 c = update();
373 shape = next_shape();
374 if (!fits_in(shape, pos = 17))
375 c = keys[KEY_QUIT];
379 if (c == keys[KEY_LEFT]) {
380 if (!fits_in(shape, --pos))
381 ++pos;
384 if (c == keys[KEY_ROTATE]) {
385 backup = shape;
386 shape = &shapes[5 * *shape]; /* Rotate */
387 /* Check if it fits, if not restore shape from backup */
388 if (!fits_in(shape, pos))
389 shape = backup;
392 if (c == keys[KEY_RIGHT]) {
393 if (!fits_in(shape, ++pos))
394 --pos;
397 if (c == keys[KEY_DROP]) {
398 for (; fits_in(shape, pos + B_COLS); ++points)
399 pos += B_COLS;
402 if (c == keys[KEY_PAUSE] || c == keys[KEY_QUIT]) {
403 freeze(1);
405 if (c == keys[KEY_QUIT]) {
406 clrscr();
407 gotoxy(0, 0);
409 printf("\033[0mYour score: %d points x level %d = %d\n\n", points, level, points * level);
410 show_high_score();
411 break;
414 for (j = B_SIZE; j--; shadow[j] = 0)
417 while (getchar() - keys[KEY_PAUSE])
420 // puts("\033[H\033[J\033[7m");
421 freeze(0);
424 place(shape, pos, color);
425 c = update();
426 place(shape, pos, 0);
429 clrscr();
430 if (tty_exit() == -1)
431 return 1;
433 return 0;
437 * Local Variables:
438 * indent-tabs-mode: t
439 * c-file-style: "linux"
440 * End: