/* Micro Tetris, based on an obfuscated tetris, 1989 IOCCC Best Game * * Copyright (c) 1989 John Tromp <john.tromp@gmail.com> * Copyright (c) 2009-2021 Joachim Wiberg <troglobit@gmail.com> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * See the following URLs for more information, first John Tromp's page about * the game http://homepages.cwi.nl/~tromp/tetris.html then there's the entry * page at IOCCC http://www.ioccc.org/1989/tromp.hint */ #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <sys/wait.h> #include <termios.h> #include <time.h> #include <unistd.h> #define clrscr() puts ("\e[2J\e[1;1H") #define gotoxy(x,y) printf("\e[%d;%dH", y, x) #define hidecursor() puts ("\e[?25l") #define showcursor() puts ("\e[?25h") #define bgcolor(c,s) printf("\e[%dm" s, c ? c + 40 : 0); #define SIGNAL(signo, cb) \ sigemptyset(&sa.sa_mask); \ sigaddset(&sa.sa_mask, signo); \ sa.sa_flags = 0; \ sa.sa_handler = cb; \ sigaction(signo, &sa, NULL); /* the board */ #define B_COLS 12 #define B_ROWS 23 #define B_SIZE (B_ROWS * B_COLS) #define TL -B_COLS-1 /* top left */ #define TC -B_COLS /* top center */ #define TR -B_COLS+1 /* top right */ #define ML -1 /* middle left */ #define MR 1 /* middle right */ #define BL B_COLS-1 /* bottom left */ #define BC B_COLS /* bottom center */ #define BR B_COLS+1 /* bottom right */ /* These can be overridden by the user. */ #define DEFAULT_KEYS "jkl pq" #define KEY_LEFT 0 #define KEY_RIGHT 2 #define KEY_ROTATE 1 #define KEY_DROP 3 #define KEY_PAUSE 4 #define KEY_QUIT 5 #define HIGH_SCORE_FILE "/var/games/tetris.scores" #define TEMP_SCORE_FILE "/tmp/tetris-tmp.scores" static struct termios savemodes; static int havemodes = 0; char *keys = DEFAULT_KEYS; int level = 1; int points = 0; int lines_cleared = 0; int board[B_SIZE], shadow[B_SIZE]; int *peek_shape; /* peek preview of next shape */ int pcolor; int *shape; int color; int shapes[] = { 7, TL, TC, MR, 2, /* ""__ */ 8, TR, TC, ML, 3, /* __"" */ 9, ML, MR, BC, 1, /* "|" */ 3, TL, TC, ML, 4, /* square */ 12, ML, BL, MR, 5, /* |""" */ 15, ML, BR, MR, 6, /* """| */ 18, ML, MR, 2, 7, /* ---- sticks out */ 0, TC, ML, BL, 2, /* / */ 1, TC, MR, BR, 3, /* \ */ 10, TC, MR, BC, 1, /* |- */ 11, TC, ML, MR, 1, /* _|_ */ 2, TC, ML, BC, 1, /* -| */ 13, TC, BC, BR, 5, /* |_ */ 14, TR, ML, MR, 5, /* ___| */ 4, TL, TC, BC, 5, /* "| */ 16, TR, TC, BC, 6, /* |" */ 17, TL, MR, ML, 6, /* |___ */ 5, TC, BC, BL, 6, /* _| */ 6, TC, BC, 2 * B_COLS, 7, /* | sticks out */ }; void draw(int x, int y, int color) { gotoxy(x, y); bgcolor(color, " "); } int update(void) { int x, y; #ifdef ENABLE_PREVIEW static int shadow_preview[B_COLS * 10] = { 0 }; int preview[B_COLS * 10] = { 0 }; const int start = 5; preview[2 * B_COLS + 1] = pcolor; preview[2 * B_COLS + 1 + peek_shape[1]] = pcolor; preview[2 * B_COLS + 1 + peek_shape[2]] = pcolor; preview[2 * B_COLS + 1 + peek_shape[3]] = pcolor; for (y = 0; y < 4; y++) { for (x = 0; x < B_COLS; x++) { if (preview[y * B_COLS + x] - shadow_preview[y * B_COLS + x]) { int color = preview[y * B_COLS + x]; shadow_preview[y * B_COLS + x] = color; draw(x * 2 + 26 + 28, start + y, color); } } } #endif /* Display board. */ for (y = 1; y < B_ROWS - 1; y++) { for (x = 0; x < B_COLS; x++) { if (board[y * B_COLS + x] - shadow[y * B_COLS + x]) { int color = board[y * B_COLS + x]; shadow[y * B_COLS + x] = color; draw(x * 2 + 28, y, color); } } } /* Update points and level */ while (lines_cleared >= 10) { lines_cleared -= 10; level++; } #ifdef ENABLE_SCORE /* Presenta nivel actual y puntaje */ gotoxy(26 + 28, 2); printf("\e[0mNivel : %d", level); gotoxy(26 + 28, 3); printf("Puntos : %d", points); #endif #ifdef ENABLE_PREVIEW gotoxy(26 + 28, 5); printf("Prever:"); #endif gotoxy(26 + 28, 10); printf("Teclas:"); fflush(stdout); return getchar(); } int fits_in(int *shape, int pos) { if (board[pos] || board[pos + shape[1]] || board[pos + shape[2]] || board[pos + shape[3]]) return 0; return 1; } void place(int *shape, int pos, int color) { board[pos] = color; board[pos + shape[1]] = color; board[pos + shape[2]] = color; board[pos + shape[3]] = color; } int *next_shape(void) { int pos = rand() % 7 * 5; int *next = peek_shape; peek_shape = &shapes[pos]; pcolor = peek_shape[4]; if (!next) return next_shape(); color = next[4]; return next; } void show_high_score(void) { #ifdef ENABLE_HIGH_SCORE FILE *tmpscore; if ((tmpscore = fopen(HIGH_SCORE_FILE, "a"))) { char *name = getenv("LOGNAME"); if (!name) name = "anonimo"; fprintf(tmpscore, "%7d\t %5d\t %3d\t%s\n", points * level, points, level, name); fclose(tmpscore); system("cat " HIGH_SCORE_FILE "| sort -rn | head -10 >" TEMP_SCORE_FILE "&& cp " TEMP_SCORE_FILE " " HIGH_SCORE_FILE); remove(TEMP_SCORE_FILE); } if (!access(HIGH_SCORE_FILE, R_OK)) { // puts("\nPresione INTRO para ver puntajes, ^C pasa saltear."); fprintf(stderr, " Score\tPoints\tLevel\tName\n"); system("cat " HIGH_SCORE_FILE); } #endif /* ENABLE_HIGH_SCORE */ } void show_online_help(void) { const int start = 11; gotoxy(26 + 28, start); puts("\e[0mj - izquierda"); gotoxy(26 + 28, start + 1); puts("k - rotar"); gotoxy(26 + 28, start + 2); puts("l - derecha"); gotoxy(26 + 28, start + 3); puts("spacb - soltar"); gotoxy(26 + 28, start + 4); puts("p - pausa"); gotoxy(26 + 28, start + 5); puts("q - Salir"); } /* Code stolen from http://c-faq.com/osdep/cbreak.html */ int tty_init(void) { struct termios modmodes; if (tcgetattr(fileno(stdin), &savemodes) < 0) return -1; havemodes = 1; hidecursor(); /* "stty cbreak -echo" */ modmodes = savemodes; modmodes.c_lflag &= ~ICANON; modmodes.c_lflag &= ~ECHO; modmodes.c_cc[VMIN] = 1; modmodes.c_cc[VTIME] = 0; return tcsetattr(fileno(stdin), TCSANOW, &modmodes); } int tty_exit(void) { if (!havemodes) return 0; showcursor(); /* "stty sane" */ return tcsetattr(fileno(stdin), TCSANOW, &savemodes); } void freeze(int enable) { sigset_t set; sigemptyset(&set); sigaddset(&set, SIGALRM); sigprocmask(enable ? SIG_BLOCK : SIG_UNBLOCK, &set, NULL); } void alarm_handler(int signo) { static long h[4]; /* On init from main() */ if (!signo) h[3] = 500000; h[3] -= h[3] / (3000 - 10 * level); setitimer(0, (struct itimerval *)h, 0); } void exit_handler(int signo) { clrscr(); tty_exit(); exit(0); } int sig_init(void) { struct sigaction sa; SIGNAL(SIGINT, exit_handler); SIGNAL(SIGTERM, exit_handler); SIGNAL(SIGALRM, alarm_handler); /* Start update timer. */ alarm_handler(0); } int main(int argc, char *argv[]) { int c = 0, i, j, *ptr; int pos = 17; int *backup; /* Initialize board, grey border, used to be white(7) */ ptr = board; for (i = B_SIZE; i; i--) *ptr++ = i < 25 || i % B_COLS < 2 ? 60 : 0; srand((unsigned int)time(NULL)); if (tty_init() == -1) return 1; /* Set up signals */ sig_init(); clrscr(); show_online_help(); shape = next_shape(); while (1) { if (c < 0) { if (fits_in(shape, pos + B_COLS)) { pos += B_COLS; } else { place(shape, pos, color); ++points; for (j = 0; j < 252; j = B_COLS * (j / B_COLS + 1)) { for (; board[++j];) { if (j % B_COLS == 10) { lines_cleared++; for (; j % B_COLS; board[j--] = 0) ; c = update(); for (; --j; board[j + B_COLS] = board[j]) ; c = update(); } } } shape = next_shape(); if (!fits_in(shape, pos = 17)) c = keys[KEY_QUIT]; } } if (c == keys[KEY_LEFT]) { if (!fits_in(shape, --pos)) ++pos; } if (c == keys[KEY_ROTATE]) { backup = shape; shape = &shapes[5 * *shape]; /* Rotate */ /* Check if it fits, if not restore shape from backup */ if (!fits_in(shape, pos)) shape = backup; } if (c == keys[KEY_RIGHT]) { if (!fits_in(shape, ++pos)) --pos; } if (c == keys[KEY_DROP]) { for (; fits_in(shape, pos + B_COLS); ++points) pos += B_COLS; } if (c == keys[KEY_PAUSE] || c == keys[KEY_QUIT]) { freeze(1); if (c == keys[KEY_QUIT]) { clrscr(); gotoxy(0, 0); printf("\e[0mSu puntaje: %d puntos x nivel %d = %d\n\n", points, level, points * level); show_high_score(); break; } for (j = B_SIZE; j--; shadow[j] = 0) ; while (getchar() - keys[KEY_PAUSE]) ; // puts("\e[H\e[J\e[7m"); freeze(0); } place(shape, pos, color); c = update(); place(shape, pos, 0); } if (tty_exit() == -1) return 1; return 0; } /** * Local Variables: * indent-tabs-mode: t * c-file-style: "linux" * End: */