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