/***************************************************************************\
|*									   *|
|*  game.c:	A version of Tetris to run on ordinary terminals,	   *|
|*		(ie., not needing a workstation, so should available	   *|
|*		to peasant Newwords+ users.  This is the module that	   *|
|*		actually plays the game, (ie. moves things down the	   *|
|*		screen, select(2)s on the keyboard, and so on)		   *|
|*									   *|
|*  Author:	Mike Taylor (mirk@uk.ac.warwick.cs)			   *|
|*  Started:	Fri May 26 12:26:05 BST 1989				   *|
|*									   *|
\***************************************************************************/

#include <sys/types.h>
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>

/* Here's what is need for this under Linux */
void l_usleep (unsigned long usec)
{
  struct timeval timeout;

  timeout.tv_sec = usec / 1000000;
  timeout.tv_usec = usec - 1000000 * timeout.tv_sec;
  select (1, NULL, NULL, NULL, &timeout);
}

#include "tt.h"
#include "game.h"
#include "screen.h"
#include "pieces.h"
#include "utils.h"

/*-------------------------------------------------------------------------*/

extern long int random ();

/*-------------------------------------------------------------------------*/

char left_key	= LEFT_KEY;	/* Move piece left */
char right_key	= RIGHT_KEY;	/* Move piece right */
char rotate_key = ROTATE_KEY;	/* Rotate piece anticlockwise */
char drop_key	= DROP_KEY;	/* Drop piece to bottom of screen */
char susp_key	= SUSP_KEY;	/* Suspend.  I'm sorry if its confusing */
char quit_key	= QUIT_KEY;	/* Quit.  I'm sorry if its confusing */
char cheat_key	= CHEAT_KEY;	/* Do whatever I eventaully make it do */

/***************************************************************************\
|*									   *|
|*  Oh good grief, what on earth is the point of putting a huge, wobbly	   *|
|*  box-comment in front of this titchy little self-explanatory function?  *|
|*  The name tells the whole story, it's perfectly strightforward, it's	   *|
|*  not a proposition from Witgenstein.	 I wouldn't bother commenting it   *|
|*  at all if it wasn't for the fact that I know I'd feel guilty in the	   *|
|*  morning.  I mean, be fair, you don't really want a program where all   *|
|*  the functions *except one* have box-comments explaining them, do you?  *|
|*  Ah well, here we go for completion's sake:				   *|
|*									   *|
|*  The function clear_board() takes no parameters and returns no value.   *|
|*  It clears the board.  The end.					   *|
|*									   *|
\***************************************************************************/

void clear_board ()
{
  int i, j;

  for (i = 0; i < GAME_DEPTH+4; i++)
    for (j = 0; j < GAME_WIDTH; j++)
      board[i][j] = PI_EMPTY;
}

/***************************************************************************\
|*									   *|
|*  The function play_game is the main loop in which the game of Tetris	   *|
|*  is implemented.  It takes no parameters, and returns no value.  The	   *|
|*  time at which it returns no value is the end of a game.  The main	   *|
|*  loop-component is a select(2) which polls the keyboard in real-time.   *|
|*  If you use a non-Berkeley UNIX(tm), you're well cheesed.		   *|
|*									   *|
|*  Actually, I have to admit I'm not proud of this one.  It must be one   *|
|*  of the messiest functions I've written in years, in terms of nested	   *|
|*  loops with ad-hoc exit conditions, re-used variables, general	   *|
|*  obfuscation and so on.  I wanna make it quite clear that I make no	   *|
|*  apologies for my use of "goto", which remains a highly desirable	   *|
|*  language feature, and is still the most elegant way of coding many	   *|
|*  things, but I gotta admit, overall this one is bit of a chicken.	   *|
|*									   *|
\***************************************************************************/

void play_game ()
{
  int i;			/* Position of origin down board */
  int j;			/* Position of origin across board */
  int k;			/* Loop variable when i,j are invariant */
  int piece_no;			/* Type of piece currently falling */
  int orient;			/* Which way it is facing */
  int pause_flag = 0;		/* We won't pause unless told to */
  int presses_left;		/* Futher moves possible this drop */
  int free_left = game_level;	/* Number of pieces to drop at start */
  fd_set read_fds;		/* Select must look at stdin */
  long int total_time = 200000; /* Allow 1/4 second before falling */
  struct timeval timeout;	/* Time before piece drops in select(2) */
  char ch;			/* Keystroke (command) */

  score = no_levels = no_pieces = 0;
  update_scores ();
  clear_board ();

  while (1) {
    piece_no = (int) (random () % NO_PIECES);
    orient = (int) (random () % NO_ORIENTS);
    i = -2;			/* Start falling from off-screen */
    if (free_left > 0)		/* If we have random starting-pieces */
      j = 1 + (int) (random () % (GAME_WIDTH-2));
    else			/* places them randomly, otherwise */
      j = GAME_WIDTH/2;		/* put the piece in the middle */

    if (!can_place (piece_no, orient, i, j)) {
      for (k = 0; k < 9; k++) { /* Crude but (hopefully) effective */
	draw_piece (piece_no, orient, i, j, PD_ERASE);
	myrefresh (); l_usleep (80000);
	draw_piece (piece_no, orient, i, j, PD_DRAW);
	myrefresh (); l_usleep (80000);
      }
      break;			/* End of game - piece won't fit */
    }

    if (free_left != 0) {	/* If there are pieces to be dropped, */
      if (free_left > 0)	/* And the number is positiive, */
	free_left--;		/* Then decrement it, otherwise */
      else			/* increment it, in any case, bring */
	free_left++;		/* it closer to zero if it gets to zero */
      if (free_left == 0)	/* set a flag so that the game will */
	pause_flag = 1;		/* pause.  Then go to the bit of code */
      goto DROP_PIECE;		/* that drops it. */
    }

    while (1) {
      presses_left = NO_MOVES;
      draw_piece (piece_no, orient, i, j, PD_DRAW);
      update_scores ();
      myrefresh ();

      while (1) {
	FD_ZERO (&read_fds);
	FD_SET (0, &read_fds);
	timeout.tv_sec = 0;
	timeout.tv_usec = total_time;
	switch (select (((presses_left > 0) && (i >= 0)), &read_fds,
			(fd_set*) NULL, (fd_set*) NULL, &timeout)) {
	case -1:
	  if (errno != EINTR)
	    die (LE_SELECT, "select(2) failed in play_game()");
				/* Otherwise fall through, goto TIMEOUT */
	case 0:
	  goto TIMEOUT;
	default:
	  if (read (0, &ch, 1) == -1)
	    die (LE_READ, "read(2) failed in play_game()");

	  if (ch == REFRESH_KEY)
	    hoopy_refresh ();

	  if ((ch != left_key) && (ch != right_key) && (ch != rotate_key) &&
	      (ch != drop_key) && (ch != quit_key) && (ch != susp_key) &&
	      (ch != cheat_key))
	    break;

	  presses_left--;
	  if (ch == left_key) {
	    if (can_place (piece_no, orient, i, j-1)) {
	      draw_piece (piece_no, orient, i, j, PD_ERASE);
	      j--;
	      draw_piece (piece_no, orient, i, j, PD_DRAW);
	      myrefresh ();
	    }
	  }

	  else if (ch == right_key) {
	    if (can_place (piece_no, orient, i, j+1)) {
	      draw_piece (piece_no, orient, i, j, PD_ERASE);
	      j++;
	      draw_piece (piece_no, orient, i, j, PD_DRAW);
	      myrefresh ();
	    }
	  }

	  else if (ch == rotate_key) {
	    int new_or = ((rotate_backwards == 0) ? ((orient+1)%NO_ORIENTS) :
			  ((orient-1)%NO_ORIENTS+NO_ORIENTS*(orient == 0)));
	    if (can_place (piece_no, new_or, i, j)) {
	      draw_piece (piece_no, orient, i, j, PD_ERASE);
	      orient = new_or;
	      draw_piece (piece_no, orient, i, j, PD_DRAW);
	      myrefresh ();
	    }
	  }

	  else if (ch == drop_key) {
	  DROP_PIECE:
	    while (can_place (piece_no, orient, i+1, j)) {
	      draw_piece (piece_no, orient, i, j, PD_ERASE);
	      i++;
	      draw_piece (piece_no, orient, i, j, PD_DRAW);
	      myrefresh ();
	    }
	    goto TIMEOUT;
	  }

	  else if (ch == quit_key)
	    return;

	  else if (ch == cheat_key) {
	    print_msg ("CHEAT!");
	    total_time = 0L;
	  }

	  else if (ch == susp_key) {
	    print_msg ("Paused");
	    (void) read (0, &ch, 1);
	    print_msg ("");
	  }

	  break;
	}
      }

    TIMEOUT:
      if (!can_place (piece_no, orient, i+1, j)) {
	place_piece (piece_no, orient, i, j);
	score += pieces[piece_no].points;
	no_pieces++;
	update_scores ();
	myrefresh ();

	for (i = 0; i < GAME_DEPTH; i++) {
	  for (j = 0; j < GAME_WIDTH; j++)
	    if (board[i+4][j] == PI_EMPTY)
	      break;

	  if (j == GAME_WIDTH) {
	    no_levels++;
	    score += 10;
	    update_scores ();
	    for (k = i; k > 0; k--)
	      for (j = 0; j < GAME_WIDTH; j++)
		board[k+4][j] = board[k+3][j];
	    for (j = 0; j < GAME_WIDTH; j++)
	      board[4][j] = PI_EMPTY;
	    draw_board ();
	    myrefresh ();
	    i--;		/* Check the new row i */
	  }
	}

	if (pause_flag) {	/* If we are pausing after this drop ... */
	  pause_flag = 0;	/* Ensure we don't do so next time. */
	  flush_keyboard ();
	  print_msg ("Continue");
	  (void) read (0, &ch, 1);
	  print_msg ("");
	}

	break;			/* End of fall - piece has hit floor */
      }
      
      draw_piece (piece_no, orient, i, j, PD_ERASE);
      i++;
    }

    myrefresh ();
    if (total_time != 0)
      total_time -= 100;
  }
}

/*-------------------------------------------------------------------------*/
