/***************************************************************************\
|*									   *|
|*  screen.c:	A version of Tetris to run on ordinary terminals,	   *|
|*		(ie., not needing a workstation, so should available	   *|
|*		to peasant Newwords+ users.  This module handles all	   *|
|*		the icky curses(3x) bits.				   *|
|*									   *|
|*  Author:	Mike Taylor (mirk@uk.ac.warwick.cs)			   *|
|*  Started:	Fri May 26 12:26:05 BST 1989				   *|
|*									   *|
\***************************************************************************/

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

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

/***************************************************************************\
|*									   *|
|*  The function myrefresh() calls the curses(3x) function refresh()	   *|
|*  after first moving the cursor out of harm's way at the bottom of	   *|
|*  the screen.								   *|
|*									   *|
\***************************************************************************/

void myrefresh ()
{
  int x;
  
  if ((x = screen_depth-2) < GAME_DEPTH+1)
    x = GAME_DEPTH+1;
  
  move (x, 0);
  refresh ();
}

/***************************************************************************\
|*									   *|
|*  The function hoopy_refresh() touches the screen, then refreshes it,	   *|
|*  so curses(3X) doesn't get any chance to phlegm about with only doing   *|
|*  the bits that it thinks are OK.  So this should fix up screens that	   *|
|*  have been interfered with by biff(1), mesg(1) etc.			   *|
|*									   *|
\***************************************************************************/

void hoopy_refresh ()
{
  clear ();
  setup_screen ();
  draw_board ();
  update_scores ();
  myrefresh ();
}

/***************************************************************************\
|*									   *|
|*  The function print_msg() prints a short message centered on the	   *|
|*  floor of the playing area, with a space before and after it.	   *|
|*  If the message is NULL (ie. (char*)0), or null, (ie. ""), then	   *|
|*  no message is printed.						   *|
|*									   *|
\***************************************************************************/

void print_msg (line)
  char *line;
{
  int i;

  move (GAME_DEPTH, 2);
  for (i = 0; i < 2*GAME_WIDTH; i++)
    addch (FLOOR_CHAR);

  if ((line != NULL) && (*line != '\0'))
    mvaddstr (GAME_DEPTH, GAME_WIDTH+1-(strlen (line))/2,
	      form (" %s ", line));
  myrefresh ();
}

/***************************************************************************\
|*									   *|
|*  The function clear_area() fills the playing area with BLANK_CHARs.	   *|
|*  It is used to clear the screen before each game, and also clearing	   *|
|*  the screen while displaying pieces one at a time when in debug mode.   *|
|*									   *|
\***************************************************************************/

void clear_area ()
{
  int i, j, status, fastflag = 0;
  fd_set fds;
  struct timeval timeout;
  static char buffer[LINELEN];

  for (i = 0; i < GAME_DEPTH; i++) {
    move (i, 2);
    for (j = 0; j < 2*GAME_WIDTH; j++)
      addch (BLANK_CHAR);
    
    if (fastflag == 0) {
      move (i+1, 2);
      for (j = 0; j < 2*GAME_WIDTH; j++)
	addch (FLOOR_CHAR);
      myrefresh ();

      FD_ZERO (&fds);
      FD_SET (0, &fds);
      timeout.tv_sec = 0L;	/* Future implementations of select(2) */
      timeout.tv_usec = 50000L; /* might change this value on return */
      if ((status = select (1, &fds, (fd_set*) NULL, (fd_set*) NULL,
			    &timeout)) == -1)
	if (errno != EINTR)
	  die (LE_SELECT, "select(2) failed in clear_area()");
      
      if (status != 0) {
	fastflag = 1;
	(void) read (0, buffer, LINELEN);
      }
    }
  }
  
  move (GAME_DEPTH, 2);
  for (j = 0; j < 2*GAME_WIDTH; j++)
    addch (FLOOR_CHAR);
  
  mvaddch (GAME_DEPTH, 0, CORNER_CHAR);
  addch (CORNER_CHAR);
  mvaddch (GAME_DEPTH, 2*GAME_WIDTH+2, CORNER_CHAR);
  addch (CORNER_CHAR);
  
  myrefresh ();
}

/***************************************************************************\
|*									   *|
|*  The function setup_screen should be called exactly once, near the	   *|
|*  beginning of execution.  It initialises curses(3x), and prints the	   *|
|*  game titles, the walls and the floor of the game area, and clears	   *|
|*  this area using clear_area() (perhaps unsurprisingly)		   *|
|*									   *|
|*  STOP PRESS: It no longer calls clear_area(), since play_game()	   *|
|*	does that fairly immediately after this function returns.	   *|
|*									   *|
|*  STOP PRESS^2: It now does do it again, since play_game() does the	   *|
|*	sneaky-but-slow clear that it fine at times, but cruddy for	   *|
|*	initialisation.							   *|
|*									   *|
\***************************************************************************/

void setup_screen ()
{
  int i;

  for (i = 0; i < GAME_DEPTH; i++) {
    mvaddch (i, 0, WALL_CHAR);
    addch (WALL_CHAR);
    mvaddch (i, 2*GAME_WIDTH+2, WALL_CHAR);
    addch (WALL_CHAR);
  }

  move (GAME_DEPTH, 2);
  for (i = 0; i < 2*GAME_WIDTH; i++)
    addch (FLOOR_CHAR);
  
  mvaddch (GAME_DEPTH, 0, CORNER_CHAR);
  addch (CORNER_CHAR);
  mvaddch (GAME_DEPTH, 2*GAME_WIDTH+2, CORNER_CHAR);
  addch (CORNER_CHAR);
  
  mvaddstr (0, 2*GAME_WIDTH+6, form ("%sTETRIS FOR TERMINALS%*s%s",
				     so_str, so_gunk, "", se_str));
  mvaddstr (2, 2*GAME_WIDTH+6, "Written by Mike Taylor");
  mvaddstr (3, 2*GAME_WIDTH+6, "Email: mirk@uk.ac.warwick.cs");
  mvaddstr (4, 2*GAME_WIDTH+6, "Started: Fri May 26 12:26:05 BST 1989");
  mvaddstr (6, 2*GAME_WIDTH+6, form ("Game level: %d", game_level));
  mvaddstr (8, 2*GAME_WIDTH+6, "Score:");
  mvaddstr (9, 2*GAME_WIDTH+6, "Pieces:");
  mvaddstr (10, 2*GAME_WIDTH+6, "Levels:");

  mvaddstr (12, 2*GAME_WIDTH+8, "Use keys:");
  mvaddstr (13, 2*GAME_WIDTH+8, "=========");
  mvaddstr (14, 2*GAME_WIDTH+8, form ("Move left:  '%c'", left_key));
  mvaddstr (15, 2*GAME_WIDTH+8, form ("Move right: '%c'", right_key));
  mvaddstr (16, 2*GAME_WIDTH+8, form ("Rotate:     '%c'", rotate_key));
  mvaddstr (17, 2*GAME_WIDTH+8, form ("Drop:       '%c'", drop_key));
  mvaddstr (18, 2*GAME_WIDTH+8, form ("Pause:      '%c'", susp_key));
  mvaddstr (19, 2*GAME_WIDTH+8, form ("Quit:       '%c'", quit_key));
  mvaddstr (20, 2*GAME_WIDTH+8, "Refresh:    '^L'");
}

/***************************************************************************\
|*									   *|
|*  The function setup_curses should be called exactly once, near the	   *|
|*  beginning of execution.  It initialises curses(3x), and notes that	   *|
|*  this has been done, by setting the global variable in_curses.	   *|
|*									   *|
\***************************************************************************/

void setup_curses ()
{
  initscr ();
  clear ();
#ifndef LINT
  noecho ();
  cbreak ();
#endif /* LINT */
  in_curses = 1;
}

/***************************************************************************\
|*									   *|
|*  The function update_scores() puts the sepecified values of score,	   *|
|*  no_pieces and no_levels on the screen in the specified positions.	   *|
|*									   *|
\***************************************************************************/

void update_scores ()
{
  move (8, 34);
  clrtoeol ();
  addstr (form ("%d", score));

  move (9, 34);
  clrtoeol ();
  addstr (form ("%d", no_pieces));

  move (10, 34);
  clrtoeol ();
  addstr (form ("%d", no_levels));
}

/***************************************************************************\
|*									   *|
|*  The function draw_board() puts the current state of the global array   *|
|*  board[] ontop the curses(3x) screen, then refresh()es it.		   *|
|*									   *|
\***************************************************************************/

void draw_board ()
{
  int i, j;

  for (i = 0; i < GAME_DEPTH; i++)
    for (j = 0; j < GAME_WIDTH; j++)
      if (board[i+4][j] == PI_EMPTY) {
	mvaddch (i, 2*j+2, BLANK_CHAR);
	addch (BLANK_CHAR);
      }
      else
	mvaddstr (i, 2*j+2, pieces[board[i+4][j]].app);
}

/***************************************************************************\
|*									   *|
|*  The function draw_piece draws or erases one of the tetris pieces on	   *|
|*  the screen in a specified orientation and position.	 The form of the   *|
|*  function is:							   *|
|*									   *|
|*	draw_piece (piece_no, orientation, y, x, flag)			   *|
|*									   *|
|*  All the arrguments are integers.  Flag is either PD_DRAW or		   *|
|*  PD_ERASE, specifying the effect of the function.  Piece_no is	   *|
|*  between 0 and 6 inclusive, and specifies what sort of piece is	   *|
|*  required.  Orientation is between 0 and 3 inclusive, and states	   *|
|*  which way up the piece is to be drawn, and y and x express the	   *|
|*  position as an index into the GAME_DEPTH by GAME_WIDTH array	   *|
|*  that is the board.							   *|
|*									   *|
\***************************************************************************/

void draw_piece (piece_no, orient, y, x, flag)
  int piece_no;
  int orient;
  int y;
  int x;
  int flag;
{
  int i;
  extern WINDOW *stdscr;

  for (i = 0; i < NO_SQUARES; i++)
    if (y+pieces[piece_no].index[orient][i][0] >= 0)
      if (flag == PD_ERASE) {
	mvaddch (y+pieces[piece_no].index[orient][i][0],
		 (2*(x+pieces[piece_no].index[orient][i][1]))+2,
		 BLANK_CHAR);
	addch (BLANK_CHAR);
      }
      else
	mvaddstr (y+pieces[piece_no].index[orient][i][0],
		  (2*(x+pieces[piece_no].index[orient][i][1]))+2,
		  pieces[piece_no].app);
}

/***************************************************************************\
|*									   *|
|*  The function place_piece takes the same parameters as draw_piece,	   *|
|*  except for the flag, and does not draw the piece, but places it	   *|
|*  on the board.  No checking is done to see if it will fit, since	   *|
|*  should already have been done by can_place().			   *|
|*									   *|
\***************************************************************************/

void place_piece (piece_no, orient, y, x)
  int piece_no;
  int orient;
  int y;
  int x;
{
  int i;

  for (i = 0; i < NO_SQUARES; i++)
    board[y+4+pieces[piece_no].index[orient][i][0]]
	 [x+pieces[piece_no].index[orient][i][1]] = piece_no;
}

/***************************************************************************\
|*									   *|
|*  The function can_place takes the same parameters as place_piece,	   *|
|*  It does not draw the piece, nor place it on the board, but returns	   *|
|*  an integer value -- 0 if the piece will not fit on the board, 1 if	   *|
|*  it will (with the specified orientation, position, etc.)		   *|
|*									   *|
\***************************************************************************/

#define HERE(x) pieces[piece_no].index[orient][i][x]

int can_place (piece_no, orient, y, x)
  int piece_no;
  int orient;
  int y;
  int x;
{
  int i;

  for (i = 0; i < NO_SQUARES; i++)
    if (((x+HERE(1) >= GAME_WIDTH) ||	/* Off right of screen or */
	 (x+HERE(1) < 0)) ||		/* Off left of screen */
	(y+HERE(0) >= GAME_DEPTH) ||	/* Off bottom of screen */
	(board[y+4+HERE(0)][x+HERE(1)] != PI_EMPTY))
					/* Board position not empty */
      return (0);
  
  return (1);
}

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