/*
 * tetris.c - 
 * $Id: tetris.c,v 1.4 2012/01/26 05:42:19 candy Exp candy $
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>

#include <unistd.h>
#include <termios.h>
#include <sys/time.h>

/*
 *
 */
static struct termios tio_save;
static int ti_fd;

/*
 * 死ぬ前にターミナルの状態を戻す。
 */
static void
restore_local_terminal(void)
{
	tcsetattr(ti_fd, TCSANOW, &tio_save);
}/* restore_local_terminal */

/*
 * ターミナルの状態を保存。
 */
static int
save_local_terminal(int fd)
{
	int err = tcgetattr(fd, &tio_save);
	if (err < 0) {
		perror("tcgetattr");
	}
	else {
		ti_fd = fd;
		atexit(restore_local_terminal);
	}
	return err;
}/* save_local_terminal */

/*
 * cfmakeraw() by blogger323
 * <URL:http://blogger323.blog83.fc2.com/blog-entry-202.html>
 */
#ifdef __CYGWIN__ /* [ */
/* Workaround for Cygwin, which is missing cfmakeraw */
/* Pasted from man page; added in serial.c arbitrarily */
void
cfmakeraw(struct termios *termios_p)
{
	termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP
	|INLCR|IGNCR|ICRNL|IXON);
	termios_p->c_oflag &= ~OPOST;
	termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
	termios_p->c_cflag &= ~(CSIZE|PARENB);
	termios_p->c_cflag |= CS8;
}/* cfmakeraw */

#endif /* ] */

/*
 * ターミナルを raw モードにする。
 */
static int
make_local_raw(int fd)
{
	int err = save_local_terminal(fd);
	if (err == 0) {
		struct termios tio;
		if ((err = tcgetattr(fd, &tio)) < 0)
			perror("tcgetattr");
		else {
			cfmakeraw(&tio);
			if ((err = tcsetattr(fd, TCSANOW, &tio)) < 0)
				perror("tcsetattr");
		}
	}
	return err;
}/* make_local_raw */

static int read1c(int fd)
{
	char c;
	if (read(fd, &c, 1) != 1)
		c = EOF;
	return c;
}/* read1c */

static void msleep(int ms)
{
	struct timeval tv = {ms / 1000, (ms % 1000) * 1000};
	fd_set m;
	FD_ZERO(&m);
	select(0, &m, NULL, NULL, &tv);
}/* msleep */

/*
 * read ready?: -1 = error, 0 = not ready, 1 = ready
 */
static int sel(int fd)
{
	struct timeval tv = {0, 10000};
	fd_set m;
	FD_ZERO(&m);
	FD_SET(fd, &m);
	errno = 0;
	return select(fd + 1, &m, NULL, NULL, &tv);
}/* sel */

static int kbhit(void)
{
	return sel(0) > 0;
}/* kbhit */

static int nbgetch(void)
{
	int c, fd = 0;
	if ((c = sel(fd)) > 0)
		c = read1c(fd);
	return c;
}/* nbgetch */

static int getch(void)
{
	fflush(stdout);
	return read1c(0);
}/* getch */

static void clear_input(void)
{
	while (nbgetch() > 0)
		;
}/* clear_input */

static unsigned long milliclock(void)
{
	static int ok = 0;
	static struct timeval tv0;
	struct timeval tv1;
	if (ok == 0) {
		gettimeofday(&tv0, NULL);
		ok = 1;
	}
	gettimeofday(&tv1, NULL);
	long ds = tv1.tv_sec - tv0.tv_sec;
	long du = tv1.tv_usec - tv0.tv_usec;
	if (du < 0) {
		ds--;
		du += 1000000;
	}
	return ds * 1000 + du / 1000;
}/* milliclock */

static int cmp(unsigned long d, unsigned long s)
{
	return (long)(d - s);
}/* cmp */

#if 0
static unsigned char *_cgstr = "　□東南西北白発中流";
#else
static unsigned char *_cgstr = "  []00112233445566##";
#endif

static void wch(int ch)
{
	int idx = ch * 2;
	putchar(_cgstr[idx]);
	putchar(_cgstr[idx + 1]);
}

static void move0(int y, int x)
{
	printf("\033[%d;%dH", y + 1, x + 1);
}

static void move(int y, int x)
{
	move0(y, x * 2);
}

static void mvwch(int y, int x, int ch)
{
	move(y, x);
	wch(ch);
}

static void mvwstr(int y, int x, const char *str)
{
	move(y, x);
	fputs(str, stdout);
}

static void wstr(const char *str)
{
	fputs(str, stdout);
}

static void cursor(int sw)
{
	if (sw)
		printf("\033[>5l");
	else
		printf("\033[>5h");
}

static void clear(void)
{
	printf("\033[2J");
}

/*
 *
 */
#define NSCORE 8

static char *his_file(void)
{
	static char *scfile = NULL;
	if (scfile == NULL) {
		char *base = "tetris.sc";
		char *home = getenv("HOME");
		if (home == NULL)
			home = "/";
		scfile = malloc(strlen(home) + 1 + strlen(base) + 1);
		if (scfile != NULL) {
			strcpy(scfile, home);
			while (strlen(scfile) > 0 && scfile[strlen(scfile) - 1] == '/')
				scfile[strlen(scfile) - 1] = '\0';
			strcat(scfile, "/");
			strcat(scfile, base);
		}
	}
	return scfile;
}/* his_file */

#define USIZE 32

struct his_t {
	time_t hi_date;
	unsigned long hi_score;
	int hi_level;
	int hi_lines;
	int hi_sigma;
	char hi_user[USIZE];
};

static struct his_t hi[NSCORE];

static char *his_user_copy(struct his_t *hi, const char *user)
{
	strncpy(hi->hi_user, user, sizeof(hi->hi_user));
	hi->hi_user[sizeof(hi->hi_user) - 1] = '\0';
	return hi->hi_user;
}/* his_user_copy */

static time_t mktimev(int Y, int m, int d, int H, int M, int S)
{
	struct tm tm;
	tm.tm_year = Y - 1900;
	tm.tm_mon = m - 1;
	tm.tm_mday = d;
	tm.tm_hour = H;
	tm.tm_min = M;
	tm.tm_sec = S;
	tm.tm_isdst = 0;
	return mktime(&tm);
}/* mktimev */

/* YYYY/mm/dd HH:MM:SS score level lines sigma user */
#define SCFORMAT "%04d/%02d/%02d %02d:%02d:%02d %10lu %2d %5d %5d %-10.10s\n"

static void his_load(void)
{
	if (his_file() != NULL) {
		FILE *fp = fopen(his_file(), "r");
		if (fp != NULL) {
			int i;
			for (i = 0; i < NSCORE; i++) {
				char buf[80];
				if (fgets(buf, sizeof(buf), fp) != NULL) {
					int Y = 1900, m = 1, d = 1, H = 0, M = 0, S = 0;
					char u[USIZE];
					u[0] = '\0';
					sscanf(buf, "%d/%d/%d %d:%d:%d %lu %d %d %d %s", &Y, &m, &d, &H, &M, &S, &hi[i].hi_score, &hi[i].hi_level, &hi[i].hi_lines, &hi[i].hi_sigma, u);
					hi[i].hi_date = mktimev(Y, m, d, H, M, S);
					his_user_copy(&hi[i], u);
				}
			}
			fclose(fp);
		}
	}
}/* his_load */

static void his_save(void)
{
	if (his_file() != NULL) {
		FILE *fp = fopen(his_file(), "w");
		if (fp != NULL) {
			int i;
			for (i = 0; i < NSCORE; i++) {
				struct tm tm = *localtime(&hi[i].hi_date);
				fprintf(fp, SCFORMAT,
					tm.tm_year + 1900,
					tm.tm_mon + 1,
					tm.tm_mday,
					tm.tm_hour,
					tm.tm_min,
					tm.tm_sec,
					hi[i].hi_score,
					hi[i].hi_level,
					hi[i].hi_lines,
					hi[i].hi_sigma,
					hi[i].hi_user);
			}
			fclose(fp);
		}
	}
}/* his_save */

static int his_register(unsigned long score, int level, int lines, int sigma)
{
	int changed = 0, i = 0;
	while (i < NSCORE && hi[i].hi_score >= score)
		i++;
	if (i < NSCORE) {
		char *u;
		int j;
		for (j = NSCORE - 1; j > i; j--)
			hi[j] = hi[j - 1];
		hi[i].hi_date = time(NULL);
		hi[i].hi_score = score;
		hi[i].hi_level = level;
		hi[i].hi_lines = lines;
		hi[i].hi_sigma = sigma;
		u = getenv("USER");
		if (u == NULL || *u == '\0')
			u = "nobody";
		his_user_copy(&hi[i], u);
		his_save();
		changed = 1;
	}
	return changed;
}/* his_register */

static void his_show(void)
{
	int i;
	printf("YYYY/mm/dd HH:MM:SS      SCORE LV LINES SIGMA\n");
	for (i = 0; i < NSCORE; i++) {
		struct tm tm = *localtime(&hi[i].hi_date);
		printf(SCFORMAT,
			tm.tm_year + 1900,
			tm.tm_mon + 1,
			tm.tm_mday,
			tm.tm_hour,
			tm.tm_min,
			tm.tm_sec,
			hi[i].hi_score,
			hi[i].hi_level,
			hi[i].hi_lines,
			hi[i].hi_sigma,
			hi[i].hi_user);
	}
}/* his_show */

/*
 * 箱の幅と高さ
 */
#define WIDTH 12
#define HEIGHT 22

#define SPACE 0
#define WALL 1
#define BLOCK0 2
#define NPAT 7 /* テトロノミノの種類 */
#define NDIR 4 /* 方向 */
#define TET 4 /* tetromino = 4 */

static unsigned long highscore = 0;
static unsigned long score = 0;
static int level = 0;
static int lines = 0;	/* 消したライン数 */
static int sigma = 0;	/* 落ちてきた総数 */
static int stats[NPAT]; /* 落ちてきた総数（種類ごと） */
static int npno;

/*
 * 表示
 */
static unsigned char outbuf[256];

static void print_level(int s)
{
	sprintf(outbuf, "LEVEL %2d", s);
	mvwstr(6, WIDTH + 1, outbuf);
}

static void print_high(int s)
{
	mvwstr(8, WIDTH + 1, " HIGH SCORE");
	sprintf(outbuf, "%11d", s);
	mvwstr(10, WIDTH + 1, outbuf);
}

static void print_score(int s)
{
	mvwstr(12, WIDTH + 1, "      SCORE");
	sprintf(outbuf, "%11d", s);
	mvwstr(14, WIDTH + 1, outbuf);
}

static void print_lines(int s)
{
	sprintf(outbuf, "LINES %5d", s);
	mvwstr(17, WIDTH + 1, outbuf);
}

static void print_sigma(int s)
{
	mvwstr(19, WIDTH + 1, "STATS");
	mvwstr(20, WIDTH + 1, " total");
	sprintf(outbuf, " %5d", s);
	mvwstr(21, WIDTH + 1, outbuf);
}

static void print_stats(int pno, int s)
{
	sprintf(outbuf, "[%d]", pno);
	mvwstr(20, WIDTH + 5 + pno * 2, outbuf);
	sprintf(outbuf, "%3d", s);
	mvwstr(21, WIDTH + 5 + pno * 2, outbuf);
}

/*
 * 箱
 * 左右と底に壁がある。空間は box[0][1] から box[HEIGHT-2][WIDTH-2] まで
 */
static int box[HEIGHT][WIDTH];

static void p(void)
{
	int y, x;
	for (y = 0; y < HEIGHT; y++) {
		for (x = 0; x < WIDTH; x++)
			printf(" %d", box[y][x]);
		printf("\r\n");
	}
}

/*
 * 箱を表示する
 */
static void box_draw(void)
{
	int y, x;
	for (y = 0; y < HEIGHT; y++) {
		move(y, 0);
		for (x = 0; x < WIDTH; x++)
			wch(box[y][x]);
	}
}/* box_draw */

/*
 * 壁を含まず１行表示する
 */
static void box_draw_line(int y)
{
	int x;
	move(y, 1);
	for (x = 1; x < WIDTH - 1; x++)
		wch(box[y][x]);
}/* box_draw_line */

/*
 * １行分白黒反転する
 */
static void box_draw_invert_line(int y)
{
	printf("\033[7m");
	box_draw_line(y);
	printf("\033[0m");
}/* box_draw_invert_line */

/*
 * 箱の中１行クリア（表示する）
 */
static void box_draw_clear_line(int y)
{
	int x;
	move(y, 1);
	for (x = 1; x < WIDTH - 1; x++)
		wch(SPACE);
}/* box_draw_clear_line */

/*
 * 箱の中身をスクロール（表示する）
 * スクロールのレクタングルは、隠れているところは指定しないように。
 */
static void box_draw_scroll(int y)
{
	if (y >= 2) {
		int i;
		for (i = 0; i < y + 1; i++)
			box_draw_line(i);
	}
}/* box_draw_scroll */

/*
 * 壁を作る（表示無し）
 */
static void box_make_wall(void)
{
	int i;
	for (i = 0; i < HEIGHT; i++) {
		box[i][0] = WALL;
		box[i][WIDTH - 1] = WALL;
	}
	for (i = 0; i < WIDTH; i++)
		box[(HEIGHT - 1)][i] = WALL;
}/* box_make_wall */

/*
 * 箱の中１行クリア（表示無し）
 */
static void box_clear_line(int y)
{
	int x;
	for (x = 1; x < WIDTH - 1; x++)
		box[y][x] = SPACE;
}/* box_clear_line */

/*
 * 箱を空っぽにする（表示無し）
 */
static void box_clear(void)
{
	int y, x;
	for (y = 0; y < HEIGHT; y++)
		for (x = 0; x < WIDTH; x++)
			box[y][x] = SPACE;
	box_make_wall();
}/* box_clear */

/*
 * 箱の中身をスクロール（表示無し）
 */
static void box_scroll(int ny)
{
	int y, x;
	for (y = ny - 1; y >= 0; y--)
		for (x = 0; x < WIDTH; x++)
			box[(y + 1)][x] = box[y][x];
}/* box_scroll */

/*
 * テトロミノ
 *
 * pno =   0       1       2     3         4       5       6
 * 	□□□  □□□  □□  □□□□  □□      □□  □□□
 * 	□          □  □□              □□  □□      □
 *
 * dir =   0       1       2       3
 * 	          □        □  □□
 * 	□□□    □    □□□    □
 * 	□        □□            □
 */

static int pats[NPAT][NDIR][TET - 1][2]; /* 2 == {x, y} */
static const char *__p = "653556545765457554565345457556565365653554545745455465455465455465455465457565545756457565545756456465564454456465564454654445566454654445566454457546565366653564545744";

/*
 * 文字列 __p から pats を計算する。
 * pats[pno][dir] が、テトロミノ番号と向きを表し、
 * pats[][][0..2][0] と pats[][][0..2][1] が x 増分と y 増分を表す。
 * pats は __p から直接求めることができ、ASCIIコードから '5' を引けばよい。
 */
static void pats_make(void)
{
	int pno, dir, dv;
	const char *src = __p;
	for (pno = 0; pno < NPAT; pno++) {
		for (dir = 0; dir < NDIR; dir++) {
			for (dv = 0; dv < TET - 1; dv++) {
				pats[pno][dir][dv][0] = src[0] - '5'; /* delta x */
				pats[pno][dir][dv][1] = src[1] - '5';	/* delta y */
				src += 2;
			}
		}
	}
}/* pats_make */

/*
 * 箱にテトロミノを置く。表示無し
 */
static void box_put_trace(int pno, int dir, int cx, int cy, int c)
{
	box[cy][cx] = c;
	box[cy += pats[pno][dir][0][1]][cx += pats[pno][dir][0][0]] = c;
	box[cy += pats[pno][dir][1][1]][cx += pats[pno][dir][1][0]] = c;
	box[cy += pats[pno][dir][2][1]][cx += pats[pno][dir][2][0]] = c;
}/* box_put_trace */
/*
 * 箱にテトロミノを置く。表示無し
 */
static void box_put(int pno, int dir, int cx, int cy)
{
	box_put_trace(pno, dir, cx, cy, pno + BLOCK0);
}/* box_put */

/*
 * 箱にテトロミノを置けるならば non 0 を返す。
 */
static int pat_putable(int pno, int dir, int cx, int cy)
{
	return box[cy][cx] == SPACE
		&& box[cy += pats[pno][dir][0][1]][cx += pats[pno][dir][0][0]] == SPACE
		&& box[cy += pats[pno][dir][1][1]][cx += pats[pno][dir][1][0]] == SPACE
		&& box[cy += pats[pno][dir][2][1]][cx += pats[pno][dir][2][0]] == SPACE;
}/* pat_putable */

/*
 * 増分追跡しながらテトロミノを書く
 */
static void pats_trace(int pno, int dir, int px, int py, int c)
{
	mvwch(py, px, c);
	mvwch(py += pats[pno][dir][0][1], px += pats[pno][dir][0][0], c);
	mvwch(py += pats[pno][dir][1][1], px += pats[pno][dir][1][0], c);
	mvwch(py += pats[pno][dir][2][1], px += pats[pno][dir][2][0], c);
}

/*
 * テトロミノの表示
 */
static void pat_draw(int pno, int dir, int cx, int cy)
{
	pats_trace(pno, dir, cx, cy, pno + BLOCK0);
}/* pat_draw */

/*
 * テトロミノの消去
 */
static void pat_erase(int pno, int dir, int cx, int cy)
{
	pats_trace(pno, dir, cx, cy, SPACE);
}/* pat_erase */

/*
 * テトロミノの左右移動表示
 */
static void pat_move_level(int pno, int dir, int px, int py, int dx)
{
	pat_erase(pno, dir, px, py);
	pat_draw(pno, dir, px + dx, py);
}/* pat_move_level */

/*
 * テトロミノの下移動表示
 */
static void pat_move_down(int pno, int dir, int px, int py)
{
	pat_erase(pno, dir, px, py);
	pat_draw(pno, dir, px, py + 1);
}/* pat_move_down */

/*
 * テトロミノの回転表示
 */
static void pat_round(int pno, int dir, int px, int py)
{
	int ndir = (dir + 1) & 3;
	pat_erase(pno, dir, px, py);
	pat_draw(pno, ndir, px, py);
}/* pat_round */

static void print_next(int pno, int draw)
{
	mvwstr(0, WIDTH + 1, "NEXT");
	if (draw)
		pat_draw(pno, 0, WIDTH + 2, 3);
	else
		pat_erase(pno, 0, WIDTH + 2, 3);
}

/*
 * 画面を再表示
 */
static void refresh(void)
{
	int pno;
	box_draw();
	print_high(highscore);
	print_level(level);
	print_lines(lines);
	print_score(score);
	print_sigma(sigma);
	for (pno = 0; pno < NPAT; pno++)
		print_stats(pno, stats[pno]);
	fflush(stdout);
}

/*
 * レベル == lvl の時の浮いている時間
 */
static int hovering_time_of(int lvl)
{
	return (10 - lvl) * 80;
}

/*
 * レベル == lvl の時に、高さ y から落ちた時のスコア
 */
static int score_of(int lvl, int y)
{
	return lvl * 2 + 25 - y;
}

#define LEFT 'j'
#define RIGHT 'l'
#define TURN 'k'
#define DOWN ' '
#define LVUP 'i'

/*
 * 位置が確定するまでの処理
 */
static int until_down(int pno, int dir, int px, int py)
{
	int kill = 0;
	int touch = 0;
	int more = 1;
	unsigned long t0 = milliclock();
	unsigned long t1;
	long limit = hovering_time_of(level);
	clear_input();
	while (more) {
		int key = 0;
		move(0, 0);
		fflush(stdout);
		while (cmp(t1 = milliclock(), t0) < limit && (key = nbgetch()) == 0)
			;
		switch (key) {
		case LEFT:
		case RIGHT:
			/* 左右移動 */
			{
				int dx = (key == LEFT) ? -1 : 1;
				if (pat_putable(pno, dir, px + dx, py)) {
					pat_move_level(pno, dir, px, py, dx);
					px += dx;
				}
			}
			break;
		case TURN:
			/* 回転 */
			{
				int ndir = (dir + 1) & 3;
				if (pat_putable(pno, ndir, px, py)) {
					pat_round(pno, dir, px, py);
					dir = ndir;
				}
			}
			break;
		case LVUP:
			/* 次レベル */
			if (level < 9) {
				level++;
				limit = hovering_time_of(level);
				print_level(level);
			}
			break;
		case DOWN:
			/* 落下 */
			{
				int ny = py;
				more = 0;
				score += score_of(level, py);
				while (pat_putable(pno, dir, px, ny + 1))
					ny++;
				pat_erase(pno, dir, px, py);
				pat_draw(pno, dir, px, ny);
				py = ny;
			}
			break;
		case 003:
		case 'q':
			/* 強制終了 */
			more = 0;
			kill = 1;
			break;
		case 014:
			/* 再表示 */
			refresh();
			pat_draw(pno, dir, px, py);
			print_next(npno, 1);
			break;
		case 033:
			/* ポーズ */
			clear();
			getch();
			refresh();
			pat_draw(pno, dir, px, py);
			print_next(npno, 1);
			break;
		default:
			break;
		}/* switch */
		if (more && cmp(t1, t0) >= limit) { /* 自動落下 */
			int ny = py + 1;
			if (pat_putable(pno, dir, px, ny)) {
				pat_move_down(pno, dir, px, py);
				py = ny;
				t0 = milliclock();
				limit = hovering_time_of(level);
				touch = 0; /* 一段落ちたらもう一度チャンスありよ。 */
			}
			else {
				if (touch++ == 0) {/* 床に触れてもワンモアチャンス */
					t0 = milliclock();
					limit = hovering_time_of(5);
				}
				else {/* 確定 */
					more = 0;
					score += score_of(level, py);
				}
			}
		}
	}/* while */
	box_put(pno, dir, px, py);
	if (score > highscore)
		print_high(highscore = score);
	return kill;
}/* until_down */

/*
 * 箱を調べ、消えるラインを数える。
 * 戻り値はラインの総数、int argv[] にライン番号をセットする。
 */
static int check_lines(int *argv)
{
	int y, argc = 0;
	for (y = 2; y < HEIGHT - 1; y++) {
		int ok = 1, x = 1;
		while (ok && x < WIDTH - 1)
			ok &= box[y][x++] != 0;
		if (ok)
			argv[argc++] = y;
	}
	return argc;
}/* check_lines */

/*
 * 消えるラインがあるなら処理する。
 * 消したライン数を返す。
 */
static int clear_lines(void)
{
	int argv[HEIGHT];
	int argc = check_lines(argv);
	if (argc > 0) {
		int i;
		/* 消えるラインをピカピカする */
		unsigned long t0 = milliclock();
		while (cmp(milliclock(), t0) < 500) {
			for (i = 0; i < argc; i++)
				box_draw_invert_line(argv[i]);
			fflush(stdout);
			msleep(50);
			for (i = 0; i < argc; i++)
				box_draw_line(argv[i]);
			fflush(stdout);
			msleep(50);
		}/* while */
		/* 消えるラインを消す */
		for (i = 0; i < argc; i++) {
			box_draw_clear_line(argv[i]);
			box_clear_line(argv[i]);
		}
		fflush(stdout);
		msleep(100);
		/* ゴンゴンと落ちてくる */
		for (i = argc - 1; i >= 0; i--) {
			int curline = argv[i] + (argc - 1 - i);
			box_scroll(curline);
			box_draw_scroll(curline);
			fflush(stdout);
			msleep(100);
		}
	}
	return argc;
}/* clear_lines */

/*
 * 消したラインとレベルの関係
 * lines > 10 ならレベルは 1 だ。
 */
static int next_level[10] = {
	10, 20, 30, 40, 55, 70, 85, 100, 115, 2147483647
};

/*
 *
 */
static void over(void)
{
	int y, x;
	for (y = HEIGHT - 2; y >= 0; y--) {
		for (x = 1; x < WIDTH - 1; x++)
			box[y][x] = BLOCK0 + NPAT;
		refresh();
		msleep(30);
	}
	refresh();
}/* over */

static int rand_init(void)
{
	int i, x = 0;
	srand(time(NULL));
	for (i = 0; i < 10000; i++)
		x += rand();
	return x;
}

static int nextpat(void)
{
	return rand() / (RAND_MAX / NPAT + 1);
}

/*
 * １回のプレイ
 */
static int play(void)
{
	int pno, quit = 0, kill = 0;
	int dir = 0;
	int px = 5;
	int py = 1;
	level = 0;
	score = 0;
	lines = 0;
	sigma = 0;
	for (pno = 0; pno < NPAT; pno++)
		stats[pno] = 0;
	npno = nextpat();
	box_clear();
	refresh();
	while (!quit) {
		pno = npno;
		npno = nextpat();
		print_sigma(++sigma);
		stats[pno]++;
		print_stats(pno, stats[pno]);
		print_score(score);
		print_next(npno, 1);
		pat_draw(pno, dir, px, py);
		msleep(50);
		if (pat_putable(pno, dir, px, py)) {
			if (until_down(pno, dir, px, py) != 0) {
				kill = 1;
				quit = 1;
			}
			else {
				int dl = clear_lines(); /* dl = 消したライン数 */
				if (dl > 0) {
					lines += dl;
					print_lines(lines);
					if (lines > next_level[level]) {
						level++;
						print_level(level);
					}
				}
			}
		}
		else
			quit = 1;
		print_next(npno, 0);
	}/* while */
	his_register(score, level, lines, sigma);
	return kill;
}/* play */


/*
 *
 */
int main(void)
{
	int ch;
	rand_init();
	cursor(0);
	clear();
	move(0, 0);
	puts("\t\t\t\t\t\tHow to play\n");
	puts("\t\t\t\t\t\t         [i:LEVEL UP]");
	puts("\t\t\t\t\t\t[j:LEFT] [k:TURN] [l:RIGHT]");
	puts("\t\t\t\t\t\t         [SPACE:DOWN]");
	puts("");
	puts("\t\t\t\t\t\t[ESC]   PAUSE");
	puts("\t\t\t\t\t\t^L      REDRAW");
	puts("\t\t\t\t\t\t[q]     QUIT");
	pats_make();
	make_local_raw(0);
	his_load();
	highscore = hi[0].hi_score;
	ch = 'y';
	while (ch == 'y') {
		if (play() != 0)
			ch = 'n';
		else {
			over();
			mvwstr(HEIGHT / 2, 1, "  PLAY AGAIN (Y/N)? ");
			while ((ch = getch()) != EOF && ch != 'y' && ch != 'n')
				;
		}
	}/* while */
	clear();
	cursor(1);
	move(0, 0);
	fflush(stdout);
	restore_local_terminal();
	his_show();
	return 0;
}/* main */


