/*
 * hextris Copyright 1990 David Markley, dm3e@+andrew.cmu.edu, dam@cs.cmu.edu
 *
 * Permission to use, copy, modify, and distribute, this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of the copyright holders be used in
 * advertising or publicity pertaining to distribution of the software with
 * specific, written prior permission, and that no fee is charged for further
 * distribution of this software, or any modifications thereof.  The copyright
 * holder make no representations about the suitability of this software for
 * any purpose.  It is provided "as is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDER DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA, PROFITS, QPA OR GPA, 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.
 */

/* This file contains the heart of hextris. It contains absolutely no
 * direct I/O calls, for maximum portability. All I/O that is performed is
 * done so by either the I/O handler directly, calls from the I/O handler
 * to functions in this file that call functions in the I/O handler, or by
 * functions in this file that call functions in the I/O handler.
 */

#include "header.h"

/* This places the piece on the board in its starting position. All the
 * hexes in the piece are displayed.
 */
init_piece(piece)
piece_t *piece;
{
    int base, form, i;
    base = (piece->column % 2) ? 0 : 8;
    form = piece->type * 6 + piece->rotation;

    for (i = 0; i < 7; i += 2)
      draw_hex(piece->row+shape[form][base+i],
		 piece->column+shape[form][base+1+i],1,piece->type);
}

/* This places the piece in its new position. This is done by comparing
 * the current piece's position (piece) to the new position (tpiece),
 * removing any hexes that will no longer be covered, and placing
 * any hexes that will be covered. In this way, any piece that is already
 * covered, and will be covered, is not redrawn.
 */
place_piece(piece,tpiece)
piece_t *piece, *tpiece;
{
    int base, form, tbase, tform, i, j, diff;
    base = (piece->column % 2) ? 0 : 8;
    form = piece->type * 6 + piece->rotation;
    tbase = (tpiece->column % 2) ? 0 : 8;
    tform = tpiece->type * 6 + tpiece->rotation;
    
    for (i = 0; i < 7; i += 2) {
	for (j = 0; j < 7; j += 2) {
	    diff = 1;
	    if ((tpiece->row+shape[tform][tbase+i] == 
		 piece->row+shape[form][base+j])
		&& (tpiece->column+shape[tform][tbase+1+i] == 
		    piece->column+shape[form][base+1+j])) {
		diff = 0;
		break;
		    }
	}
	if (diff)
	  draw_hex(tpiece->row+shape[tform][tbase+i],
		     tpiece->column+shape[tform][tbase+1+i],1,tpiece->type);
    }

    for (i = 0; i < 7; i += 2) {
	for (j = 0; j < 7; j += 2) {
	    diff = 1;
	    if ((piece->row+shape[form][base+i] == 
		    tpiece->row+shape[tform][tbase+j])
		   && (piece->column+shape[form][base+1+i] == 
		       tpiece->column+shape[tform][tbase+1+j])) {
		diff = 0;
		break;
		       }
	}
	if (diff)
	  draw_hex(piece->row+shape[form][base+i],
		     piece->column+shape[form][base+1+i],0,piece->type);
    }
}

/* This checks to see if the proposed position of the piece (tpiece) will
 * legally fit in the grid.
 */
check_piece(tpiece,grid)
piece_t *tpiece;
position_t grid[MAXROW][MAXCOLUMN];
{
    int base, form, i;
    base = (tpiece->column % 2) ? 0 : 8;
    form = tpiece->type * 6 + tpiece->rotation;


    for (i = 0; i < 7; i += 2)
      if (grid[tpiece->row+shape[form][base+i]]
	  [tpiece->column+shape[form][base+1+i]].filled)
	return 1;

    if ((tpiece->row+3) >= MAXROW) /* If the piece is near the bottom */
      for (i = 0; i < 7; i += 2)
	if (((tpiece->row)+shape[form][base+i]) >= MAXROW)
	  return 1;

    if ((tpiece->column+3) >= MAXCOLUMN) /* If the piece is near the right */
      for (i = 1; i < 8; i += 2)
	if (tpiece->column+shape[form][base+i] >= MAXCOLUMN)
	  return 1;

    if ((tpiece->column-2) < 1) /* If the piece is near the left */
      for (i = 1; i < 8; i += 2)
	if (tpiece->column + shape[form][base+i] < 0)
	  return 1;

    return 0;
}

/* This drops the piece into the grid, in its final resting place.
 */
drop_piece(piece,grid)
piece_t *piece;
position_t grid[MAXROW][MAXCOLUMN];
{
    int base, form, i;
    base = (piece->column & 1) ? 0 : 8;
    form = piece->type * 6 + piece->rotation;

    for (i = 0; i < 7; i += 2) {
	grid[piece->row+shape[form][base+i]]
	  [piece->column+shape[form][base+1+i]].type = piece->type;
	grid[piece->row+shape[form][base+i]]
	  [piece->column+shape[form][base+1+i]].filled = 1;
    }
}

/* This redraws the entire grid.
 */
redraw_grid(grid)
position_t grid[MAXROW][MAXCOLUMN];
{
    int row, column;

    for (row = MAXROW - 1; row >= 0; row--)
	for (column = 0; column < MAXCOLUMN; column++)
	    draw_hex(row,column,grid[row][column].filled,
		       grid[row][column].type);
}

/* This clears the current, normal row, and moves the rest of the
 * grid down.
 */
shift_redraw_grid(start_row,grid)
int start_row;
position_t grid[MAXROW][MAXCOLUMN];
{
    int row, column;

    start_row = (start_row < MAXROW) ? start_row : MAXROW - 1;

    for (row = start_row; row > 0; row--)
	for (column = 0; column < MAXCOLUMN; column++) {
	    if (grid[row][column].filled)
	      draw_hex(row,column,0,0);
	    grid[row][column].type = grid[row-1][column].type;
	    grid[row][column].filled = grid[row-1][column].filled;
	    if (grid[row][column].filled)
	      draw_hex(row,column,grid[row][column].filled,
			 grid[row][column].type);
	}
    row = 0;
    for (column = 0; column < MAXCOLUMN; column++) {
	grid[row][column].filled = 0;
	draw_hex(row,column,grid[row][column].filled,grid[row][column].type);
    }
}

/* This clears the current, offset row, and moves the rest of the
 * grid down.
 */
shift_offset_redraw_grid(start_row,grid)
int start_row;
position_t grid[MAXROW][MAXCOLUMN];
{
    int row, column;

    start_row = (start_row < MAXROW) ? start_row : MAXROW - 1;

    for (row = start_row; row > 1; row--)
	for (column = 0; column < MAXCOLUMN; column++) {
	    if (grid[row-(column & 1)][column].filled)
	      draw_hex(row-(column & 1),column,0,0);
	    grid[row-(column & 1)][column].type =
	      grid[row-1-(column & 1)][column].type;
	    grid[row-(column & 1)][column].filled =
	      grid[row-1-(column & 1)][column].filled;
	    if (grid[row-(column & 1)][column].filled)
	      draw_hex(row-(column & 1),column,
			 grid[row-(column & 1)][column].filled,
			 grid[row-(column & 1)][column].type);
	}
    row = 1;
    for (column = 0; column < MAXCOLUMN; column++) {
	grid[row-(column & 1)][column].filled = 0;
	draw_hex(row-(column & 1),column,
		   grid[row-(column & 1)][column].filled,
		   grid[row-(column & 1)][column].type);
    }
}

/* This checks for any cleared rows, be they normal or offset.
 */
check_rows(grid)
position_t grid[MAXROW][MAXCOLUMN];
{
    int row, column, clear_row, clear_offset_row, total_clear;

    total_clear = 0;
    for (row = MAXROW-1; row >= 0; row--) {
	clear_row = 1;
	for (column = 0; column < MAXCOLUMN; column++)
	  if (! grid[row][column].filled) {
	      clear_row = 0;
	      break;
	  }
	if (clear_row) {
	    shift_redraw_grid(row,grid);
	    total_clear++;
	}
	clear_offset_row = 1;
	for (column = 0; column < MAXCOLUMN; column++)
	  if (! grid[row-(column & 1)][column].filled) {
	      clear_offset_row = 0;
	      break;
	  }
	if (clear_offset_row) {
	    shift_offset_redraw_grid(row,grid);
	    total_clear++;
	}
	if (clear_row || clear_offset_row)
	  row++;
    }
    return total_clear;
}

/* This handles the users choices.
 *
 * Choices:
 * 0 - Left
 * 1 - Right
 * 2 - Rotate CCW
 * 3 - Rotate CW
 * 4 - Drop
 * 6 - Quit
 */
update(choice,grid,npiece,piece,score,rows)
int choice;
position_t grid[MAXROW][MAXCOLUMN];
piece_t *npiece,*piece;
int *score, *rows;
{
    piece_t tpiece;

    tpiece.row = piece->row;
    tpiece.column = piece->column;
    tpiece.type = piece->type;
    tpiece.rotation = piece->rotation;
    
    switch (choice) {
    case 0:
	tpiece.column--;
	if (! check_piece(&tpiece,grid)) {
	    place_piece(piece,&tpiece);
	    piece->column = tpiece.column;
	    piece->row = tpiece.row;
	} else {
	    tpiece.row -= (1-((piece->column & 1)*2));
	    if (! check_piece(&tpiece,grid)) {
		place_piece(piece,&tpiece);
		piece->column = tpiece.column;
		piece->row = tpiece.row;
	    }
	}
	break;
    case 1:
	tpiece.column++;
	if (! check_piece(&tpiece,grid)) {
	    place_piece(piece,&tpiece);
	    piece->column = tpiece.column;
	    piece->row = tpiece.row;
	} else {
	    tpiece.row -= (1-((piece->column & 1)*2));
	    if (! check_piece(&tpiece,grid)) {
		place_piece(piece,&tpiece);
		piece->column = tpiece.column;
		piece->row = tpiece.row;
	    }
	}
	break;
    case 2:
	tpiece.rotation++;
	tpiece.rotation = (tpiece.rotation > 5) ? 0 : tpiece.rotation;
	if (! check_piece(&tpiece,grid)) {
	    place_piece(piece,&tpiece);
	    piece->rotation = tpiece.rotation;
	    piece->column = tpiece.column;
	    piece->row = tpiece.row;
	}
	break;
    case 3:
	tpiece.rotation--;
	tpiece.rotation = (tpiece.rotation < 0) ? 5 : tpiece.rotation;
	if (! check_piece(&tpiece,grid)) {
	    place_piece(piece,&tpiece);
	    piece->rotation = tpiece.rotation;
	    piece->column = tpiece.column;
	    piece->row = tpiece.row;
	}
	break;
    case 4:
	tpiece.row++;
	while (! check_piece(&tpiece,grid)) {
	    place_piece(piece,&tpiece);
	    piece->row++;
	    tpiece.row++;
	    *score += 10;
	}
	return update_drop(grid,npiece,piece,score,rows);
	break;
    case 6:
	return 1;
	break;
    }
    return 0;
}

/* This process the normal dropping caused by the passage of time. The io
 * handler calls this, when it is time to drop the piece.
 */
update_drop(grid,npiece,piece,score,rows)
position_t grid[MAXROW][MAXCOLUMN];
piece_t *npiece,*piece;
int *score, *rows;
{
    int cleared_rows, game_over;
    piece_t tpiece;

    tpiece.row = piece->row;
    tpiece.column = piece->column;
    tpiece.type = piece->type;
    tpiece.rotation = piece->rotation;

    tpiece.row++;
    if (check_piece(&tpiece,grid)) {
	*score += 25;
	drop_piece(piece,grid);
	cleared_rows = check_rows(grid);
	*score += (cleared_rows * 5000);
	*rows += cleared_rows;
	display_scores(score,rows);
	game_over =  (piece->row < 2) ? 1 : 0;
	new_piece(npiece,piece);
	init_piece(piece);
	show_next_piece(npiece);
    } else {
	tpiece.row--;
	piece->row++;
	place_piece(&tpiece,piece);
	game_over = 0;
    }
    return game_over;
}

/* This sets up things for a new game.
 */
new_game(grid,npiece,piece,score,rows)
position_t grid[MAXROW][MAXCOLUMN];
piece_t *npiece,*piece;
int *score, *rows;
{
    int row, column;

    for (row = 0; row < MAXROW; row++)
      for (column = 0; column < MAXCOLUMN; column++)
	grid[row][column].filled = 0;

    npiece->rotation = -1;
    new_piece(npiece,piece);
    *score = 0;
    *rows = 0;
    init_piece(piece);
    show_next_piece(npiece);
}

/* This draws the borders of the game.
 */
draw_borders()
{
    int i;

    for (i = 0; i <= MAXROW; i++) {
	draw_hex(i,-1,1,10); 
	draw_hex(i,MAXCOLUMN,1,10); 
    }
    for (i = 0; i < MAXCOLUMN; i++)
      draw_hex(MAXROW,i,1,10);
}

/* This checks to see if the score received from a game is high enough
 * to be in the high scores. If the user already has his limit of
 * high scores, it also makes sure he beat one of his own. If he did beat
 * one of his own, the lowest of hist scores is removed, and the new one
 * is placed.
 */
is_high_score(name,userid,score,rows,high_scores)
char name[MAXNAMELENGTH];
char userid[MAXUSERIDLENGTH];
int score;
int rows;
high_score_t high_scores[MAXHIGHSCORES];
{
    int i,j, user_highs, added, equal, over;

    user_highs = added = over = equal = 0;
    for (i = 0; i < MAXHIGHSCORES; i++) {
	equal = (! strcmp(userid,high_scores[i].userid));
	if (equal)
	    over = (++user_highs > MAXUSERHIGHS) ? 1 : 0;
	if (over && equal) {
	    for (j = i; j < (MAXHIGHSCORES - 1); j++) {
		strcpy(high_scores[j].name, high_scores[j+1].name);
		strcpy(high_scores[j].userid, high_scores[j+1].userid);
		high_scores[j].score = high_scores[j+1].score;
		high_scores[j].rows = high_scores[j+1].rows;
	    }
	    strcpy(high_scores[MAXHIGHSCORES-1].name, "David Markley");
	    strcpy(high_scores[MAXHIGHSCORES-1].userid, "CMU");
	    high_scores[MAXHIGHSCORES-1].score = 0;
	    high_scores[MAXHIGHSCORES-1].rows = 0;
	    i--;
	    continue;
	}
	if ((high_scores[i].score <= score)
	    && ((equal && (user_highs == MAXUSERHIGHS))
	    || (user_highs < MAXUSERHIGHS)) && (! added))  {
	    if (! equal)
	      over = (++user_highs > MAXUSERHIGHS);
	    for (j = MAXHIGHSCORES - 1; j > i; j--) {
		strcpy(high_scores[j].name, high_scores[j-1].name);
		strcpy(high_scores[j].userid, high_scores[j-1].userid);
		high_scores[j].score = high_scores[j-1].score;
		high_scores[j].rows = high_scores[j-1].rows;
	    }
	    strcpy(high_scores[i].name, name);
	    strcpy(high_scores[i].userid, userid);
	    high_scores[i].score = score;
	    high_scores[i].rows = rows;
	    added = 1;
	    }
    }
    return added;
}

/* This takes the raw information from the I/O handler, and breaks down the
 * key stroke into a choice. Notice: It also transfers the variable the
 * I/O handler supplies it.
 */
do_choice(choice,grid,npiece,piece,score,rows,game_over,game_view,high_scores)
char choice;
position_t grid[MAXROW][MAXCOLUMN];
piece_t *npiece,*piece;
int *score, *rows, *game_over, *game_view;
high_score_t high_scores[MAXHIGHSCORES];
{
    switch (choice) {
    case 'j': case 'J': case '4':
	if (! *game_over)
	  update(0,grid,npiece,piece,score,rows);
	break;
    case 'l': case 'L': case '6':
	if (! *game_over)
	  update(1,grid,npiece,piece,score,rows);
	break;
    case 'k': case 'K': case '5':
	if (! *game_over)
	  update(2,grid,npiece,piece,score,rows);
	break;
    case 'i': case 'I': case '8':
	if (! *game_over)
	  update(3,grid,npiece,piece,score,rows);
	break;
    case ' ': case '0':
	if (! *game_over)
	  *game_over = update(4,grid,npiece,piece,score,rows);
	break;
    case 'r': case 'R':
	clear_display();
	redraw_game(grid,npiece,piece,score,rows,*game_view,high_scores);
	break;
    case 'q': case 'Q':
	update(6,grid,npiece,piece,score,rows);
	end_game();
	break;
    case 'p': case 'P':
	if (! *game_over) {
	    *game_over = 2;
	    display_high_scores(high_scores);
	    display_help();
	}
	break;
    case 'u': case 'U':
	if (*game_over == 2) {
	    *game_over = *game_view = 0;	
	    clear_display();
	    redraw_game(grid,npiece,piece,score,rows,*game_view,high_scores);
	}
	break;
    case 'H': case 'h':
	if (*game_over) {
	    *game_view = 1;
	    clear_display();
	    redraw_game(grid,npiece,piece,score,rows,*game_view,high_scores);
	}
	break;
    case 'G': case 'g':
	if (*game_over) {
	    *game_view = 0;
	    clear_display();
	    redraw_game(grid,npiece,piece,score,rows,*game_view,high_scores);
	}
	break;
    case 'n': case 'N':
	*game_over = *game_view = 0;
	new_game(grid, npiece, piece, score, rows);
	clear_display();
	redraw_game(grid,npiece,piece,score,rows,*game_view,high_scores);
#ifdef LOG
	loguse(LOGHOST,"xhexlog",log_message);
#endif
    }
}

/* This redraws all the parts of the game through the I/O handler.
 */
redraw_game(grid,npiece,piece,score,rows,game_view,high_scores)
position_t grid[MAXROW][MAXCOLUMN];
piece_t *npiece,*piece;
int *score, *rows, game_view;
high_score_t high_scores[MAXHIGHSCORES];
{
    if (! game_view) {
	redraw_grid(grid);
	draw_borders();
	init_piece(piece);
	display_scores(score,rows);
	display_help();
	show_next_piece(npiece);
    } else {
	if (read_high_scores(high_scores))
	  display_high_scores(high_scores);
	display_help();
	display_scores(score,rows);
    }
}
