/* 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:
*/