/**********************************************************************/
/*                                                                    */
/*	CRISP - Programmable editor                                   */
/*	===========================                                   */
/*                                                                    */
/*  File:          tetris.cr                                          */
/*  Author:        P. D. Fox                                          */
/*  Created:       11 Mar 1991                     		      */
/*                                                                    */
/*  Copyright (c) 1990, 1991 Paul Fox                                 */
/*                All Rights Reserved.                                */
/*                                                                    */
/*                                                                    */
/*--------------------------------------------------------------------*/
/*  Description:  Macro to play the Tetris game.                      */
/*                                                                    */
/*   This  implementation  is based on the X11 version by Rob Mayoff  */
/*   and the various HP-48SX versions.				      */
/**********************************************************************/

/* SCCS ID: %Z% %M% %R%.%L% */
# include	"crisp.h"

# define	DEBUG	0

# define	HEIGHT	20
# define	WIDTH	10
# define	PWIDTH	2	/* Width of a square.		*/

# define	NUM_PIECES	7
# define	NUM_MOVES	4	/* Number of moves before piece */
					/* moves down a line.		*/
					
# define	LEVEL_LINE	2
# define	LEVEL_COL	PWIDTH * WIDTH + 8
# define	SCORE_LINE	4
# define	SCORE_COL	PWIDTH * WIDTH + 8
# define	HISCORE_LINE	6
# define	HISCORE_COL	PWIDTH * WIDTH + 8
/**********************************************************************/
/*   The  delay  between blocks falling if no key-stroke detected at  */
/*   the lowest level. Measured in milliseconds.		      */
/**********************************************************************/
# define	STARTING_DELAY	350

/**********************************************************************/
/*   Minimum time delay at the highest game level.		      */
/**********************************************************************/
# define	MIN_DELAY	(STARTING_DELAY / 10)

/**********************************************************************/
/*   Enable  following  to  debug  the code. This allows the user to  */
/*   have  a  20 second delay between piece moves (use Down-arrow to  */
/*   drop  one  square),  and  have  99  goes  before  automatically  */
/*   dropping. Useful for cheating as well !			      */
/**********************************************************************/
# if DEBUG
# 	undef NUM_MOVES
# 	define NUM_MOVES 99
# 	undef STARTING_DELAY
# 	define	STARTING_DELAY	20000
# endif

/**********************************************************************/
/*   The  following  is  the score table. This table is used to give  */
/*   the  user  a  score  depending on the piece and the orientation  */
/*   of  the  object  at  the  point  it lands. This table is in the  */
/*   same order as the pieces description following.		      */
/**********************************************************************/
list score_tbl = {
	{10, 5, 5, 8},
	{10, 8, 5, 5},
	{10, 5},
	{10, 5},
	{10, 5},
	{10},
	{10, 5, 8, 5}
	};
/**********************************************************************/
/*   The  following  list is a list of pieces. Each atom in the list  */
/*   is  another  list  containing the characters forming the actual  */
/*   block  in  all of its four orientations, except for those where  */
/*   symmettry doesn't require it.				      */
/**********************************************************************/
list pieces = {
/*******************************/
	{  {"",
	    "######", 
	    "##"},

	   {"  ##", 
	    "  ##", 
	    "  ####"},

	   {"    ##", 
	    "######"},
	
	   {"####", 
	    "  ##", 
	    "  ##"}

	},
/*******************************/
	{  {"",
	    "######", 
	    "    ##"},
	    
	   {"  ####", 
	    "  ##", 
	    "  ##"},

	   {"##", 
	    "######"},

	   {"  ##", 
	    "  ##", 
	    "####"}
	},
/*******************************/
	{  {"",
	    "####",
	    "  ####"},
	 
	   {"  ##",
	    "####",
	    "##"}
	},
/*******************************/
	{  {"",
	    "  ####",
	    "####"},
	 
	   {"##",
	    "####",
	    "  ##"}
	},
/*******************************/
	{  {"########"},
	
	   {"    ##", 
	    "    ##", 
	    "    ##", 
	    "    ##"}
	},
/*******************************/
	{  {"####", 
	    "####"}
	},
/*******************************/
	{  {"      ",
	    "######", 
	    "  ##"},
	    
	   {"  ##", 
	    "  ####", 
	    "  ##"},
	    
	   {"  ##", 
	    "######"},
	    
	   {"  ##", 
	    "####", 
	    "  ##"}
	}
};

/**********************************************************************/
/*   List for the Help popup.					      */
/**********************************************************************/
list tetris_help = {
	"Welcome to the Game of Tetris. The following keys",
	"are used to manipulate the blocks as they fall:",
	"",
	"<Left-Arrow> or j	 	Move Left",
	"<Right-Arrow> or l		Move Right",
	"<Up-Arrow> or i			Rotate block",
	"<Keypad-5>			Drop block",
	"<Down-Arrow>			Move down",
	"<Alt-X> or q			Terminate Tetris",
	"<Space>				Pause game"
	};

void
tetris()
{	int	curbuf = inq_buffer();
	int	curwin = inq_window();
	int	buf, twin;
	int	score = 0;
	int	level = 0;
	int	hiscore = 0;

	/***********************************************/
	/*   See  if  we  can  access  the high score  */
	/*   file in the users home directory.	       */
	/***********************************************/
	if (inq_environment("HOME") != "") {
		edit_file(inq_environment("HOME") + "/.tetris");
		hiscore = atoi(read());
		delete_buffer(inq_buffer());
		}
# if DEBUG
	/***********************************************/
	/*   If   we're  debugging,  start  off  with  */
	/*   same random number each time.	       */
	/***********************************************/
	srand(1);
# endif
	buf = create_buffer("Tetris", NULL, TRUE);
# define	LEFT_X	20
	twin = create_window(LEFT_X, HEIGHT + 1, LEFT_X + PWIDTH * WIDTH + 20, 1);
	set_buffer(buf);
	attach_buffer(buf);

	message("Press <Alt-H> for help.");
	
	play_tetris();
       
	message("Press any key to clear window.");
	read_char();
	
	/***********************************************/
	/*   See if we write back the new hi-score.    */
	/***********************************************/
	if (score > hiscore && inq_environment("HOME") != "") {
		string	tmp;
		edit_file(inq_environment("HOME") + "/.tetris");
		sprintf(tmp, "%d\n", score);
		clear_buffer();
		insert(tmp);
		write_buffer();
		delete_buffer(inq_buffer());
		}
	message("%sScore: %d  Level: %d", score > hiscore ? "Hi-" : "",
		score, level);

	delete_window(twin);
	delete_buffer(buf);
	set_window(curwin);
	set_buffer(curbuf);
	attach_buffer(curbuf);
}
void
play_tetris()
{
	int	i;
	int	piece_num;
	int	dir;
	int	delay = STARTING_DELAY;
	string	blank_line;
	string	complete_line;
	int	lines_completed;
	extern int score, level;
	
	lines_completed = 0;
	/***********************************************/
	/*   Make   sure  first  four  characters  on  */
	/*   each  line  are  non-blank,  so we can't  */
	/*   move  left  into  them. These characters  */
	/*   are not displayed in the window.	       */
	/***********************************************/
	blank_line = "....";
	for (i = PWIDTH * WIDTH; i-- > 0; )
		blank_line += " ";
	sprintf(blank_line, "%s%c\n", blank_line, 241);

	/***********************************************/
	/*   String used to test for a complete line.  */
	/***********************************************/
	for (i = 0, complete_line = ""; i++ < WIDTH; )
		complete_line += "##";

	insert(blank_line, HEIGHT);
	top_of_buffer();

	while (1) {
		i = play_piece();
		if (i < 0)
			break;
		score += score_tbl[piece_num][dir] + i;
		i = check_for_completed_lines();
		if (i) {
			score += i * i * 40;
			if (delay > MIN_DELAY)
				delay = STARTING_DELAY - 
					(level * STARTING_DELAY) / 10;
			}
		}

}
int
play_piece()
{	int	x, y;
	int	new_dir;
	list	ps, p;
	int	num = 0;
	int	incr = 0;
	int	drop_piece = 0;
	int	ch;
	int	num_moves;
	int	num_dropped = 0;	/* Number of levels we drop */
	extern int piece_num, dir;
	extern int score, level, delay;

	x = WIDTH / 2 + 4;
	y = 1;
	piece_num = rand(length_of_list(pieces));
	ps = pieces[piece_num];
	dir = 0;

	num_moves = NUM_MOVES;

	while (1) {
		if (y + length_of_list(p) >= HEIGHT) {
			draw_piece(x, y, p);
			break;
			}
		p = ps[dir];
		draw_score(level, score);
		if (blocked(x, y, p)) {
			break;
			}
			
		draw_piece(x, y, p);
		if (drop_piece == 0) {
			ch = read_char(delay);
			keyboard_flush();
			num_moves--;
			switch (ch) {
			  case key_to_int("<Alt-X>"):
			  case key_to_int("<Esc>"):
			  case 'q':
			  	return -1;
			  case key_to_int("<Left-Arrow>"):
			  case 'j':
				incr = -PWIDTH;
				break;
			  case key_to_int("<Right-Arrow>"):
			  case 'l':
				incr = PWIDTH;
				break;
			  case key_to_int("<Up-Arrow>"):
			  case 'i':
				erase_piece(x, y, p);
			  	new_dir = (dir + 1) % length_of_list(ps);
				if (!blocked(x, y, ps[new_dir]))
					dir = new_dir;
				break;
			  case ' ':
			  	message("Game paused - any key to continue:");
				read_char();
				break;
			  case key_to_int("<Keypad-5>"):
			  	drop_piece = TRUE;
				break;
			  case key_to_int("<Down-Arrow>"):
			  case -1:
			  	num_moves = 0;
				break;
			  case key_to_int("<Alt-H>"):
			  	select_list("Tetris Help", "Press <Esc> to resume play.",
					1, tetris_help, SEL_NORMAL);
			  	break;
			  }
			}
		erase_piece(x, y, p);
		if (num_moves <= 0 || drop_piece) {
			if (blocked(x, y+1, p)) {
				draw_piece(x, y, p);
				break;
				}
			y++;
			if (drop_piece)
				num_dropped++;
			num_moves = NUM_MOVES;
			}
		if (incr < 0 && x + incr >= 1 && !blocked(x + incr, y, p))
			x += incr;
		else if (incr > 0 && !blocked(x + incr, y, p))
			x += incr;
		incr = 0;
		num++;
		}
	if (y >= HEIGHT)
		draw_piece(x, y-1, p);
	return num == 0 ? -1 : num_dropped;

}
void
draw_piece(int x, int y, list piece)
{	int	i, j;
	int	llen = length_of_list(piece);
	string	s;

	for (i = 0; i < llen; i++) {
		s = piece[i];
		if ((j = index(s, "#")) != 0) {
			move_abs(y + i, x + j - 1);
			s = trim(substr(s, j));
			insert(s);
			delete_char(strlen(s));
			}
		}
	set_top_left(1, 4);
	move_abs(1, 5);
	refresh();
}
void
erase_piece(int x, int y, list piece)
{	int	i, j;
	int	len;
	string	s;
	int	llen = length_of_list(piece);

	move_abs(y, x);
	for (i = 0; i < llen; i++) {
		s = piece[i];
		len = strlen(s);
		for (j = 1; j <= len; j++) {
			if (substr(s, j, 1) != " ") {
				delete_char();
				insert(" ");
				}
			else
				move_rel(NULL, 1);
			}
		move_rel(1, -len);
		}
	move_abs(1, 1);
}

/**********************************************************************/
/*   Check to see if we can put the piece at (x,y) in the grid.	      */
/**********************************************************************/
int
blocked(int x, int y, list piece)
{	int	i, j;
	int	llen = length_of_list(piece);
	string	s;

	move_abs(y, x);
	for (i = 0; i < llen; ) {
		s = piece[i++];
		/***********************************************/
		/*   If  we've  got  a non-blank string, then  */
		/*   check  that  for all character positions  */
		/*   containing  a  '#'  that we have a space  */
		/*   in the buffer.			       */
		/***********************************************/
		if (j = index(s, "#")) {
			/***********************************************/
			/*   The  following  statement is as follows:  */
			/*   grab   number  of  strings  from  buffer  */
			/*   corresponding  to  number  of characters  */
			/*   in   this  line.  Ignore  leading  space  */
			/*   characters.   Reduce  all  spaces  to  a  */
			/*   single  space.  We  should  be left with  */
			/*   one  space  only  if  this  line  of the  */
			/*   block will fit.			       */
			/***********************************************/
			if (compress(substr(read(strlen(s)), j)) != " ")
				return TRUE;
			}
		down();
		}
	return FALSE;
}
int
check_for_completed_lines()
{	int	i;
	int	num_completed = 0;
	extern int lines_completed;
	extern string complete_line;
	extern string blank_line;
	extern int level, score;

	for (i = 1; i <= HEIGHT; ) {
		move_abs(i, 5);
		if (read(WIDTH * PWIDTH) != complete_line) {
			i++;
			continue;
			}
		/***********************************************/
		/*   Now  animate  the  disappearance  of the  */
		/*   line.				       */
		/***********************************************/
		move_abs(i, 4 + PWIDTH * WIDTH);
		drop_anchor(MK_NORMAL);
		move_abs(i, 5);
		while (read(1) == "#") {
			delete_char();
			insert(" ");
			refresh();
			}
		raise_anchor();
		if (num_completed++ == 0) {
			/***********************************************/
			/*   Erase  score  and level before trying to  */
			/*   delete  a  line  because  we  may end up  */
			/*   scrolling the buffer.		       */
			/***********************************************/
			move_abs(LEVEL_LINE, LEVEL_COL);
			delete_to_eol();
			move_abs(SCORE_LINE, SCORE_COL);
			delete_to_eol();
			move_abs(HISCORE_LINE, HISCORE_COL);
			delete_to_eol();
			}
		move_abs(i, 1);
		delete_line();
		move_abs(1, 1);
		insert(blank_line);
		move_abs(1, 5);
		draw_score(level, score);
		refresh();
		if ((lines_completed++ & 0x0f) == 0)
			level++;
		}
	return num_completed;
}
void
draw_score(int level, int score)
{	extern int hiscore;

	move_abs(LEVEL_LINE, LEVEL_COL);
	delete_to_eol();
	insert("Level: " + level);
	move_abs(SCORE_LINE, SCORE_COL);
	delete_to_eol();
	insert("Score: " + score);
	move_abs(HISCORE_LINE, HISCORE_COL);
	delete_to_eol();
	insert("Hi-Score: " + hiscore);
}

