/* Tetrinet for Linux, by Andrew Church <achurch@achurch.org>
 * This program is public domain.
 *
 * Tetris core.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "tetrinet.h"
#include "tetris.h"
#include "io.h"
#include "sockets.h"

/*************************************************************************/

int piecefreq[7];	/* Frequency (percentage) for each block type */
int specialfreq[9];	/* Frequency for each special type */
int old_mode;		/* Old mode? (i.e. Gameboy-style) */
int initial_level;	/* Initial level */
int lines_per_level;	/* Number of lines per level-up */
int level_inc;		/* Levels to increase at each level-up */
int level_average;	/* Average all players' levels */
int special_lines;	/* Number of lines needed for a special block */
int special_count;	/* Number of special blocks added each time */
int special_capacity;	/* Capacity of special block inventory */

Field fields[6];	/* Current field states */
int levels[6];		/* Current levels */
int lines;		/* Lines completed (by us) */
char specials[MAX_SPECIALS] = {-1}; /* Special block inventory */
int next_piece;		/* Next piece to fall */

static struct timeval timeout;	/* Time of next action */
int current_piece;	/* Current piece number */
int current_rotation;	/* Current rotation value */
int current_x;		/* Current X position */
int current_y;		/* Current Y position */
static int piece_waiting;	/* Are we waiting for a new piece to start? */

static int last_special;	/* Last line for which we added a special */

/*************************************************************************/

#ifndef SERVER_ONLY

/*************************************************************************/

/* The array of piece shapes.  It is organized as:
 *	- 7 pieces
 *	  - 4 rows
 *	    - 4 rotations (ordered clockwise)
 *	      - 4 points
 * A . is an empty point, a # is a full one.  An X (upper-case) represents
 * the "hot-spot" of the piece; this is where the coordinates are fastened
 * to, and is used to determine the piece's new position after rotation.
 * If the location for an X empty, use a lowercase letter instead.
 *
 * This is all parsed by init_shapes, which should be called at startup.
 */

static const char shapes[7][4][4][4] = {
    { { "##X#", "..X.", "##X#", "..X." },
      { "....", "..#.", "....", "..#." },
      { "....", "..#.", "....", "..#." },
      { "....", "..#.", "....", "..#." } },

    { { "....", "....", "....", "...." },
      { ".X#.", ".X#.", ".X#.", ".X#." },
      { ".##.", ".##.", ".##.", ".##." },
      { "....", "....", "....", "...." } },

    { { "....", ".#..", "#...", ".##." },
      { "#X#.", ".X..", "#X#.", ".X.." },
      { "..#.", "##..", "....", ".#.." },
      { "....", "....", "....", "...." } },

    { { "....", "##..", "..#.", ".#.." },
      { "#X#.", ".X..", "#X#.", ".X.." },
      { "#...", ".#..", "....", ".##." },
      { "....", "....", "....", "...." } },

    { { "....", ".#..", "....", ".#.." },
      { "#X..", "#X..", "#X..", "#X.." },
      { ".##.", "#...", ".##.", "#..." },
      { "....", "....", "....", "...." } },

    { { "....", "#...", "....", "#..." },
      { ".X#.", "#X..", ".X#.", "#X.." },
      { "##..", ".#..", "##..", ".#.." },
      { "....", "....", "....", "...." } },

    { { "....", ".#..", ".#..", ".#.." },
      { "#X#.", "#X..", "#X#.", ".X#." },
      { ".#..", ".#..", "....", ".#.." },
      { "....", "....", "....", "...." } }
};

/* piecedata[piece][rot]; filled in by init_shapes() */
PieceData piecedata[7][4];

/*************************************************************************/

/* Parse the shapes array and fill in the piece data. */

void init_shapes(void)
{
    int i, x, y, r;

    for (i = 0; i < 7; i++) {
	for (r = 0; r < 4; r++) {
	    piecedata[i][r].hot_x  = -1;
	    piecedata[i][r].hot_y  = -1;
	    piecedata[i][r].top    =  3;
	    piecedata[i][r].left   =  3;
	    piecedata[i][r].bottom =  0;
	    piecedata[i][r].right  =  0;
	    for (y = 0; y < 4; y++) {
		for (x = 0; x < 4; x++) {
		    switch (shapes[i][y][r][x]) {
		      case '.':
			piecedata[i][r].shape[y][x] = 0;
			break;
		      case '#':
			piecedata[i][r].shape[y][x] = 1;
			if (piecedata[i][r].top     > y)
			    piecedata[i][r].top     = y;
			if (piecedata[i][r].left    > x)
			    piecedata[i][r].left    = x;
			if (piecedata[i][r].bottom  < y)
			    piecedata[i][r].bottom  = y;
			if (piecedata[i][r].right   < x)
			    piecedata[i][r].right   = x;
			break;
		      case 'x':
			piecedata[i][r].shape[y][x] = 0;
			piecedata[i][r].hot_x       = x;
			piecedata[i][r].hot_y       = y;
			break;
		      case 'X':
			piecedata[i][r].shape[y][x] = 1;
			if (piecedata[i][r].top     > y)
			    piecedata[i][r].top     = y;
			if (piecedata[i][r].left    > x)
			    piecedata[i][r].left    = x;
			if (piecedata[i][r].bottom  < y)
			    piecedata[i][r].bottom  = y;
			if (piecedata[i][r].right   < x)
			    piecedata[i][r].right   = x;
			piecedata[i][r].hot_x       = x;
			piecedata[i][r].hot_y       = y;
			break;
		      default :
			fprintf(stderr, "Piece %d rotation %d: "
					"weird character `%c' at (%d,%d)\n",
				i, r, shapes[i][y][r][x], x, y);
			exit(1);
		    }
		}
	    }
	    if (piecedata[i][r].hot_x < 0 || piecedata[i][r].hot_y < 0) {
		fprintf(stderr, "Piece %d rotation %d missing hot spot!\n",
			i, r);
		exit(1);
	    }
	}
    }
}

/*************************************************************************/

/* Retrieve the shape for the given piece and rotation.  Return -1 if piece
 * or rotation is invalid, else 0.
 */

int get_shape(int piece, int rotation, char buf[4][4])
{
    int x, y;
    char *shape;

    if (piece < 0 || piece > 6 || rotation < 0 || rotation > 3)
	return -1;
    shape = (char *) piecedata[piece][rotation].shape;
    for (y = 0; y < 4; y++) {
	for (x = 0; x < 4; x++) {
	    buf[y][x] = *shape++ ? piece%5 + 1 : 0;
	}
    }
    return 0;
}

/*************************************************************************/
/*************************************************************************/

/* Return the number of milliseconds of delay between piece drops for the
 * current level.
 */

static int level_delay()
{
    int level = levels[my_playernum-1];
    int delay = 1000;

    while (--level)
	delay = (delay*69+35)/70;   /* multiply by 69/70 and round */
    return delay;
}

/*************************************************************************/

/* Return whether the piece in the position given by the x, y, and rot
 * variables (interpreted the same way as current_*) would overlap any
 * other blocks in the field.  A value of -1 means use the current_* value.
 */

static int piece_overlaps(int x, int y, int rot)
{
    Field *f = &fields[my_playernum-1];
    PieceData *pd;
    int i, j, ok;

    if (x < 0)
	x = current_x;
    if (y < 0)
	y = current_y;
    if (rot < 0)
	rot = current_rotation;
    pd = &piecedata[current_piece][rot];
    x -= pd->hot_x;
    y -= pd->hot_y;
    ok = 1;
    for (j = 0; ok && j < 4; j++) {
	if (y+j < 0)
	    continue;
	for (i = 0; ok && i < 4; i++) {
	    if (pd->shape[j][i] && (y+j >= FIELD_HEIGHT || x+i < 0
				 || x+i >= FIELD_WIDTH || (*f)[y+j][x+i]))
		ok = 0;
	}
    }
    return !ok;
}

/*************************************************************************/

/* Draw the piece in its current position on the board.  If draw == 0, then
 * erase the piece rather than drawing it.
 */

static void draw_piece(int draw)
{
    Field *f = &fields[my_playernum-1];
    char c = draw ? current_piece % 5 + 1 : 0;
    int x = current_x - piecedata[current_piece][current_rotation].hot_x;
    int y = current_y - piecedata[current_piece][current_rotation].hot_y;
    char *shape = (char *) piecedata[current_piece][current_rotation].shape;
    int i, j;

    for (j = 0; j < 4; j++) {
	if (y+j < 0) {
	    shape += 4;
	    continue;
	}
	for (i = 0; i < 4; i++) {
	    if (*shape++)
		(*f)[y+j][x+i] = c;
	}
    }
}

/*************************************************************************/

/* Clear any full lines on the field; return the number of lines cleared. */

static int clear_lines(int add_specials)
{
    Field *f = &fields[my_playernum-1];
    int x, y, count = 0, i, j, k;
    int new_specials[9];

    for (y = 0; y < FIELD_HEIGHT; y++) {
	int full = 1;
	for (x = 0; x < FIELD_WIDTH; x++) {
	    if ((*f)[y][x] == 0) {
		full = 0;
		break;
	    }
	}
	if (full)
	    count++;
    }

    memset(new_specials, 0, sizeof(new_specials));
    for (y = 0; y < FIELD_HEIGHT; y++) {
	int full = 1;
	for (x = 0; x < FIELD_WIDTH; x++) {
	    if ((*f)[y][x] == 0) {
		full = 0;
		break;
	    }
	}
	if (full) {
	    for (x = 0; x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] > 5)
		    new_specials[(*f)[y][x]-6]++;
	    }
	    if (y > 0)
		memmove((*f)[1], (*f)[0], FIELD_WIDTH*y);
	    memset((*f)[0], 0, FIELD_WIDTH);
	}
    }

    if (add_specials) {
	int pos = 0;
	while (pos < special_capacity && specials[pos] >= 0)
	    pos++;
	for (i = 0; i < count && pos < special_capacity; i++) {
	    for (j = 0; j < 9 && pos < special_capacity; j++) {
		for (k = 0; k < new_specials[j] && pos < special_capacity; k++){
		    if (windows_mode && rand()%2) {
			memmove(specials+1, specials, pos);
			specials[0] = j;
			pos++;
		    } else
			specials[pos++] = j;
		}
	    }
	}
	if (pos < special_capacity)
	    specials[pos] = -1;
	io->draw_specials();
    }

    return count;
}

/*************************************************************************/

/* Place the given number of specials on the field.  If there aren't enough
 * blocks to replace, replace all of the blocks and drop the rest of the
 * specials.
 */

static void place_specials(int num)
{
    Field *f = &fields[my_playernum-1];
    int nblocks = 0, left;
    int x, y, tries;

    for (y = 0; y < FIELD_HEIGHT; y++) {
	for (x = 0; x < FIELD_WIDTH; x++) {
	    if ((*f)[y][x])
		nblocks++;
	}
    }
    if (num > nblocks)
	num = nblocks;
    left = num;
    tries = 10;
    while (left > 0 && tries > 0) {
	for (y = 0; left > 0 && y < FIELD_HEIGHT; y++) {
	    for (x = 0; left > 0 && x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] > 5 || (*f)[y][x] == 0)
		    continue;
		if (rand() % nblocks < num) {
		    int which = 0, n = rand() % 100;
		    while (n >= specialfreq[which]) {
			n -= specialfreq[which];
			which++;
		    }
		    (*f)[y][x] = 6 + which;
		    left--;
		}
	    }
	}
	tries--;
    }
}

/*************************************************************************/

/* Send the new field, either as differences from the given old field or
 * (if more efficient) as a complete field.  If oldfield is NULL, always
 * send the complete field.
 */

static void send_field(Field *oldfield)
{
    Field *f = &fields[my_playernum-1];
    int i, x, y, diff = 0;
    char buf[512], *s;

    if (oldfield) {
	for (y = 0; y < FIELD_HEIGHT; y++) {
	    for (x = 0; x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] != (*oldfield)[y][x])
		    diff++;
	    }
	}
    } else {
	diff = FIELD_WIDTH * FIELD_HEIGHT;
    }
    if (diff < (FIELD_WIDTH*FIELD_HEIGHT)/2) {
	s = buf + sprintf(buf, "f %d ", my_playernum);
	for (i = 0; i < 15; i++) {
	    int seen = 0;   /* Have we seen a difference of this block? */
	    for (y = 0; y < FIELD_HEIGHT; y++) {
		for (x = 0; x < FIELD_WIDTH; x++) {
		    if ((*f)[y][x] == i && (*f)[y][x] != (*oldfield)[y][x]) {
			if (!seen) {
			    *s++ = i + '!';
			    seen = 1;
			}
			*s++ = x + '3';
			*s++ = y + '3';
		    }
		} /* for x */
	    } /* for y */
	} /* for i (each tile type) */
    } /* difference check */
    /* -4 below is to adjust for "f %d " */
    if (diff >= (FIELD_WIDTH*FIELD_HEIGHT)/2
				|| strlen(buf)-4 > FIELD_WIDTH*FIELD_HEIGHT) {
	static const char specials[] = "acnrsbgqo";
	s = buf + sprintf(buf, "f %d ", my_playernum);
	for (y = 0; y < FIELD_HEIGHT; y++) {
	    for (x = 0; x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] > 5)
		    *s++ = specials[(*f)[y][x]-6];
		else
		    *s++ = (*f)[y][x] + '0';
	    }
	}
    }
    *s = 0;
    sputs(buf, server_sock);
}

/*************************************************************************/
/*************************************************************************/

/* Generate a new piece and set up the timer. */

void new_piece(void)
{
    int n;
    PieceData *pd;

    current_piece = next_piece;
    n = rand() % 100;
    next_piece = 0;
    while (n >= piecefreq[next_piece] && next_piece < 6) {
	n -= piecefreq[next_piece];
	next_piece++;
    }
    current_rotation = 0;
    pd = &piecedata[current_piece][current_rotation];
    current_x = 6;
    current_y = pd->hot_y - pd->top;
    if (piece_overlaps(-1, -1, -1)) {
	current_x--;
	if (piece_overlaps(-1, -1, -1)) {
	    current_x += 2;
	    if (piece_overlaps(-1, -1, -1)) {
		Field *f = &fields[my_playernum-1];
		int x, y;
		for (y = 0; y < FIELD_HEIGHT; y++) {
		    for (x = 0; x < FIELD_WIDTH; x++)
			(*f)[y][x] = rand()%5 + 1;
		}
		send_field(NULL);
		sockprintf(server_sock, "playerlost %d", my_playernum);
		playing_game = 0;
		not_playing_game = 1;
	    }
	}
    }
    draw_piece(1);
    io->draw_status();
    io->draw_own_field();
    gettimeofday(&timeout, NULL);
    timeout.tv_usec += level_delay() * 1000;
    timeout.tv_sec += timeout.tv_usec / 1000000;
    timeout.tv_usec %= 1000000;
    piece_waiting = 0;
}

/*************************************************************************/

/* Step the current piece down one space.  If it's already as far as it can
 * go, solidify it, check for completed lines, send the new field state,
 * and start a new piece.
 */

void step_down(void)
{
    Field *f = &fields[my_playernum-1];
    PieceData *pd = &piecedata[current_piece][current_rotation];
    int y = current_y - pd->hot_y;
    int ynew;

    draw_piece(0);
    ynew = current_y+1;
    if (y+1 + pd->bottom < FIELD_HEIGHT && !piece_overlaps(-1, ynew, -1)) {
	current_y++;
	draw_piece(1);
	io->draw_own_field();
	gettimeofday(&timeout, NULL);
	timeout.tv_usec += level_delay() * 1000;
	timeout.tv_sec += timeout.tv_usec / 1000000;
	timeout.tv_usec %= 1000000;
    } else {
	int completed, level, nspecials;
	Field oldfield;
	char buf[16];

	memcpy(&oldfield, f, sizeof(oldfield));
	draw_piece(1);
	if (last_special > lines)	/* i.e. from a previous game */
	    last_special = 0;
	completed = clear_lines(1);
	lines += completed;
	if (old_mode && completed > 1) {
	    if (completed < 4)
		completed--;
	    sockprintf(server_sock, "sb 0 cs%d %d", completed, my_playernum);
	    sprintf(buf, "cs%d", completed);
	    io->draw_attdef(buf, my_playernum, 0);
	}
	level = initial_level + (lines / lines_per_level) * level_inc;
	if (level > 100)
	    level = 100;
	levels[my_playernum] = level;
	if (completed > 0) {
	    sockprintf(server_sock, "lvl %d %d", my_playernum, level);
	    io->draw_status();
	}
	nspecials = (lines - last_special) / special_lines;
	last_special += nspecials * special_lines;
	nspecials *= special_count;
	place_specials(nspecials);
	io->draw_own_field();
	send_field(&oldfield);
	piece_waiting = 1;
	gettimeofday(&timeout, NULL);
	timeout.tv_usec += tetrifast ? 0 : 600000;
	timeout.tv_sec += timeout.tv_usec / 1000000;
	timeout.tv_usec %= 1000000;
    }
}

/*************************************************************************/

/* Do something for a special block. */

void do_special(const char *type, int from, int to)
{
    Field *f = &fields[my_playernum-1];
    Field oldfield;
    int x, y;

    io->draw_attdef(type, from, to);

    if (!playing_game)
	return;
    if (to != 0 && to != my_playernum && !(from==my_playernum && *type=='s'))
	return;

    if (!piece_waiting)
	draw_piece(0);

    memcpy(&oldfield, f, sizeof(Field));

    if (strncmp(type, "cs", 2) == 0) {
	int nlines = atoi(type+2);

	/* Don't add lines from a team member */
	if (!teams[my_playernum-1]
	 || !teams[from-1]
	 || strcmp(teams[my_playernum-1],teams[from-1]) != 0
	) {
	    while (nlines--) {
		memmove((*f)[0], (*f)[1], FIELD_WIDTH*(FIELD_HEIGHT-1));
		for (x = 0; x < FIELD_WIDTH; x++)
		    (*f)[21][x] = 1 + rand()%5;
		(*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0;
	    }
	}

    } else if (*type == 'a') {
	memmove((*f)[0], (*f)[1], FIELD_WIDTH*(FIELD_HEIGHT-1));
	for (x = 0; x < FIELD_WIDTH; x++)
	    (*f)[21][x] = 1 + rand()%5;
	(*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0;
	(*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0;
	(*f)[FIELD_HEIGHT-1][rand()%FIELD_WIDTH] = 0;

    } else if (*type == 'b') {
	for (y = 0; y < FIELD_HEIGHT; y++) {
	    for (x = 0; x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] > 5)
		    (*f)[y][x] = rand()%5 + 1;
	    }
	}

    } else if (*type == 'c') {
	memmove((*f)[1], (*f)[0], FIELD_WIDTH*(FIELD_HEIGHT-1));
	memset((*f)[0], 0, FIELD_WIDTH);

    } else if (*type == 'g') {
	for (x = 0; x < FIELD_WIDTH; x++) {
	    y = FIELD_HEIGHT-1;
	    while (y > 0) {
		if ((*f)[y][x] == 0) {
		    int y2, allclear = 1;
		    for (y2 = y-1; allclear && y2 >= 0; y2--) {
			if ((*f)[y2][x])
			    allclear = 0;
		    }
		    if (allclear)
			break;
		    for (y2 = y-1; y2 >= 0; y2--)
			(*f)[y2+1][x] = (*f)[y2][x];
		    (*f)[0][x] = 0;
		} else
		    y--;
	    }
	}
	clear_lines(0);

    } else if (*type == 'n') {
	memset(*f, 0, FIELD_WIDTH*FIELD_HEIGHT);

    } else if (*type == 'o') {
	int tries, x2, y2, xnew, ynew;

	for (y = 0; y < FIELD_HEIGHT; y++) {
	    for (x = 0; x < FIELD_WIDTH; x++) {
		if ((*f)[y][x] != 6 + SPECIAL_O)
		    continue;
		(*f)[y][x] = 0;
		for (y2 = y-1; y2 <= y+1; y2++) {
		    if (y2 < 0 || y2 >= FIELD_HEIGHT)
			continue;
		    for (x2 = x-1; x2 <= x+1; x2++) {
			if (x2 < 0 || x2 >= FIELD_WIDTH)
			    continue;
			if (!windows_mode && !(*f)[y2][x2])
			    continue;
			tries = 10;
			while (tries--) {
			    xnew = random() % FIELD_WIDTH;
			    ynew = FIELD_HEIGHT-1 - random()%16;
			    if (windows_mode || !(*f)[ynew][xnew]) {
				(*f)[ynew][xnew] = (*f)[y2][x2];
				break;
			    }
			}
			(*f)[y2][x2] = 0;
		    }
		}
	    }
	}
	clear_lines(0);

    } else if (*type == 'q') {
	for (y = 0; y < FIELD_HEIGHT; y++) {
	    int r = rand()%3 - 1;
	    if (r < 0) {
		int save = (*f)[y][0];
		memmove((*f)[y], (*f)[y]+1, FIELD_WIDTH-1);
		if (windows_mode)
		    (*f)[y][FIELD_WIDTH-1] = 0;
		else
		    (*f)[y][FIELD_WIDTH-1] = save;
	    } else if (r > 0) {
		int save = (*f)[y][FIELD_WIDTH-1];
		memmove((*f)[y]+1, (*f)[y], FIELD_WIDTH-1);
		if (windows_mode)
		    (*f)[y][0] = 0;
		else
		    (*f)[y][0] = save;
	    }
	}

    } else if (*type == 'r') {
	int i;

	for (i = 0; i < 10; i++) {
	    x = rand() % FIELD_WIDTH;
	    y = rand() % FIELD_HEIGHT;
	    if ((*f)[y][x] != 0) {
		(*f)[y][x] = 0;
		break;
	    }
	}

    } else if (*type == 's') {
	Field temp;

	memcpy(temp, fields[from-1], sizeof(Field));
	memcpy(fields[from-1], fields[to-1], sizeof(Field));
	memcpy(fields[to-1], temp, sizeof(Field));
	if (from == my_playernum || to == my_playernum)
	    memset(fields[my_playernum-1], 0, 6*FIELD_WIDTH);
	if (from != my_playernum)
	    io->draw_other_field(from);
	if (to != my_playernum)
	    io->draw_other_field(to);

    }

    send_field(&oldfield);

    if (!piece_waiting) {
	while (piece_overlaps(-1, -1, -1))
	    current_y--;
	draw_piece(1);
    }
    io->draw_own_field();
}

/*************************************************************************/
/*************************************************************************/

/* Deal with the in-game message input buffer. */

static char gmsg_buffer[512];
static int gmsg_pos;

#define curpos	(gmsg_buffer+gmsg_pos)

/*************************************************************************/

static void gmsg_input(int c)
{
    if (gmsg_pos < sizeof(gmsg_buffer) - 1) {
	memmove(curpos+1, curpos, strlen(curpos)+1);
	gmsg_buffer[gmsg_pos++] = c;
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    }
}

/*************************************************************************/

static void gmsg_delete(void)
{
    if (gmsg_buffer[gmsg_pos]) {
	memmove(curpos, curpos+1, strlen(curpos)-1+1);
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    }
}

/*************************************************************************/

static void gmsg_backspace(void)
{
    if (gmsg_pos > 0) {
	gmsg_pos--;
	gmsg_delete();
    }
}

/*************************************************************************/

static void gmsg_kill(void)
{
    gmsg_pos = 0;
    *gmsg_buffer = 0;
    io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
}

/*************************************************************************/

static void gmsg_move(int how)
{
    if (how == -2) {
	gmsg_pos = 0;
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    } else if (how == -1 && gmsg_pos > 0) {
	gmsg_pos--;
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    } else if (how == 1 && gmsg_buffer[gmsg_pos]) {
	gmsg_pos++;
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    } else if (how == 2) {
	gmsg_pos = strlen(gmsg_buffer);
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
    }
}

/*************************************************************************/

static void gmsg_enter(void)
{
    if (*gmsg_buffer) {
	if (strncasecmp(gmsg_buffer, "/me ", 4) == 0)
	    sockprintf(server_sock, "gmsg * %s %s", players[my_playernum-1], gmsg_buffer+4);
	else
	    sockprintf(server_sock, "gmsg <%s> %s", players[my_playernum-1], gmsg_buffer);
	gmsg_pos = 0;
	*gmsg_buffer = 0;
	io->clear_gmsg_input();
    }
}

#undef curpos

/*************************************************************************/
/*************************************************************************/

/* Set up for a new game. */

void new_game(void)
{
    int n;

    gettimeofday(&timeout, NULL);
    timeout.tv_usec += 1200000;
    timeout.tv_sec += timeout.tv_usec / 1000000;
    timeout.tv_usec %= 1000000;
    piece_waiting = 1;
    n = rand() % 100;
    next_piece = 0;
    while (n >= piecefreq[next_piece] && next_piece < 6) {
	n -= piecefreq[next_piece];
	next_piece++;
    }
}

/*************************************************************************/

/* Return the number of milliseconds until we want to do something. */

int tetris_timeout(void)
{
    struct timeval tv;
    int t;

    gettimeofday(&tv, NULL);
    t = (timeout.tv_sec - tv.tv_sec) * 1000
      + (timeout.tv_usec-tv.tv_usec) / 1000;
    return t<0 ? 0 : t;
}

/*************************************************************************/

/* Do something when we hit a timeout. */

void tetris_timeout_action(void)
{
    if (piece_waiting)
	new_piece();
    else
	step_down();
}

/*************************************************************************/

/* Do something with a character of input. */

static const char special_chars[] = "acnrsbgqo";

void tetris_input(int c)
{
    PieceData *pd = &piecedata[current_piece][current_rotation];
    int x = current_x - pd->hot_x;
    int y = current_y - pd->hot_y;
    int rnew, ynew;
    static int gmsg_active = 0;

    if (gmsg_active) {
	if (c == 8 || c == 127)   /* Backspace or Delete */
	    gmsg_backspace();
	else if (c == 4)    /* Ctrl-D */
	    gmsg_delete();
	else if (c == 21)   /* Ctrl-U */
	    gmsg_kill();
	else if (c == K_LEFT)
	    gmsg_move(-1);
	else if (c == K_RIGHT)
	    gmsg_move(1);
	else if (c == 1)    /* Ctrl-A */
	    gmsg_move(-2);
	else if (c == 5)    /* Ctrl-E */
	    gmsg_move(2);
	else if (c == '\r' || c == '\n') {
	    gmsg_enter();
	    gmsg_active = 0;
	} else if (c == 27) {  /* Escape */
	    io->clear_gmsg_input();
	    gmsg_active = 0;
	} else if (c >= 1 && c <= 0xFF)
	    gmsg_input(c);
	return;
    }

    if (c != 't' && (!playing_game || game_paused))
	return;

    switch (c) {
      case K_UP:	/* Rotate clockwise */
      case 'x':
	if (piece_waiting)
	    break;
	rnew = (current_rotation+1) % 4;
	pd = &piecedata[current_piece][current_rotation];
	x = current_x - pd->hot_x;
	y = current_y - pd->hot_y;
	if (x + pd->left < 0 || x + pd->right >= FIELD_WIDTH
	 || y + pd->bottom >= FIELD_HEIGHT)
	    break;
	draw_piece(0);
	if (!piece_overlaps(-1, -1, rnew)) {
	    current_rotation = rnew;
	    draw_piece(1);
	    io->draw_own_field();
	} else {
	    draw_piece(1);
	}
	break;

      case 'z':		/* Rotate counterclockwise */
	if (piece_waiting)
	    break;
	rnew = (current_rotation+3) % 4;
	pd = &piecedata[current_piece][current_rotation];
	x = current_x - pd->hot_x;
	y = current_y - pd->hot_y;
	if (x + pd->left < 0 || x + pd->right >= FIELD_WIDTH
	 || y + pd->bottom >= FIELD_HEIGHT)
	    break;
	draw_piece(0);
	if (!piece_overlaps(-1, -1, rnew)) {
	    current_rotation = rnew;
	    draw_piece(1);
	    io->draw_own_field();
	} else {
	    draw_piece(1);
	}
	break;

      case K_LEFT:	/* Move left */
	if (piece_waiting)
	    break;
	if (x + pd->left > 0) {
	    draw_piece(0);
	    if (!piece_overlaps(current_x-1, -1, -1)) {
		current_x--;
		draw_piece(1);
		io->draw_own_field();
	    } else {
		draw_piece(1);
	    }
	}
	break;

      case K_RIGHT:	/* Move right */
	if (piece_waiting)
	    break;
	if (x + pd->right < FIELD_WIDTH-1) {
	    draw_piece(0);
	    if (!piece_overlaps(current_x+1, -1, -1)) {
		current_x++;
		draw_piece(1);
		io->draw_own_field();
	    } else {
		draw_piece(1);
	    }
	}
	break;

      case K_DOWN:	/* Down one space */
	if (piece_waiting)
	    break;
	step_down();
	break;

      case ' ':		/* Down until the piece hits something */
	if (piece_waiting)
	    break;
	draw_piece(0);
	ynew = current_y+1;
	while (y + pd->bottom < FIELD_HEIGHT && !piece_overlaps(-1,ynew,-1)) {
	    ynew++;
	    y++;
	}
	ynew--;
	if (ynew != current_y) {
	    current_y = ynew-1;
	    if (noslide)
		current_y++;	/* Don't allow sliding */
	    step_down();
	} else {
	    draw_piece(1);
	}
	break;

      case 'd':
	if (specials[0] == -1)
	    break;
	if (special_capacity > 1)
	    memmove(specials, specials+1, special_capacity-1);
	specials[special_capacity-1] = -1;
	io->draw_specials();
	break;

      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
      case '6': {
	char buf[2];

	c -= '0';
	if (!players[c-1])
	    break;
	if (specials[0] == -1)
	    break;
	sockprintf(server_sock, "sb %d %c %d",
			c, special_chars[(int) specials[0]], my_playernum);
	buf[0] = special_chars[(int) specials[0]];
	buf[1] = 0;
	do_special(buf, my_playernum, c);
	if (special_capacity > 1)
	    memmove(specials, specials+1, special_capacity-1);
	specials[special_capacity-1] = -1;
	io->draw_specials();
	break;
      }

      case 't':
	gmsg_active = 1;
	io->draw_gmsg_input(gmsg_buffer, gmsg_pos);
	break;

    } /* switch (c) */
}

/*************************************************************************/

#endif	/* !SERVER_ONLY */

/*************************************************************************/
