/* movecheck.c
 *
 */

/*
    fics - An internet chess server.
    Copyright (C) 1993  Richard V. Nash

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
*/

/* Revision history:
   name		email		yy/mm/dd	Change
   Richard Nash                 93/10/22	Created
   Markus Uhlin                 23/12/14	Fixed compiler warnings
   Markus Uhlin                 23/12/17	Fixed compiler warnings
   Markus Uhlin                 23/12/24	Fixed dead assignment
   Markus Uhlin                 24/05/05	Refactored and reformatted all
						functions.
*/

#include "stdinclude.h"

#include "algcheck.h"
#include "board.h"
#include "common.h"
#include "gamedb.h"
#include "movecheck.h"
#include "network.h"
#include "playerdb.h"
#include "utils.h"

#if __linux__
#include <bsd/string.h>
#endif

/*
 * Simply tests if the input string is a move or not. If it matches
 * patterns below.
 *
 * Add to this list as you improve the move parser:
 *   MS_COMP e2e4
 *   MS_COMPDASH e2-e4
 *   MS_CASTLE o-o, o-o-o
 * Not done yet:
 *   MS_ALG e4, Nd5 Ncd5
 */
PUBLIC int
is_move(char *mstr)
{
	int	len = strlen(mstr);

	if ((len > 3) && (mstr[len - 2] == '='))
		len -= 2;

	if (len == 4) { // Test for e2e4
		if (isfile(mstr[0]) && isrank(mstr[1]) &&
		    isfile(mstr[2]) && isrank(mstr[3])) {
			return MS_COMP;
		}
	}

	if (len == 5) { // Test for e2-e4
		if (isfile(mstr[0]) &&
		    isrank(mstr[1]) &&
		    (mstr[2] == '-') &&
		    isfile(mstr[3]) &&
		    isrank(mstr[4]))
			return MS_COMPDASH;
	}

	if (len == 3) { // Test for o-o
		if ((mstr[0] == 'o') && (mstr[1] == '-') && (mstr[2] == 'o'))
			return MS_KCASTLE;
		if ((mstr[0] == 'O') && (mstr[1] == '-') && (mstr[2] == 'O'))
			return MS_KCASTLE;
		if ((mstr[0] == '0') && (mstr[1] == '-') && (mstr[2] == '0'))
			return MS_KCASTLE;
	}

	if (len == 2) { // Test for oo
		if ((mstr[0] == 'o') && (mstr[1] == 'o'))
			return MS_KCASTLE;
		if ((mstr[0] == 'O') && (mstr[1] == 'O'))
			return MS_KCASTLE;
		if ((mstr[0] == '0') && (mstr[1] == '0'))
			return MS_KCASTLE;
	}

	if (len == 5) { // Test for o-o-o
		if ((mstr[0] == 'o') && (mstr[1] == '-') && (mstr[2] == 'o') &&
		    (mstr[3] == '-') && (mstr[4] == 'o'))
			return MS_QCASTLE;
		if ((mstr[0] == 'O') && (mstr[1] == '-') && (mstr[2] == 'O') &&
		    (mstr[3] == '-') && (mstr[4] == 'O'))
			return MS_QCASTLE;
		if ((mstr[0] == '0') && (mstr[1] == '-') && (mstr[2] == '0') &&
		    (mstr[3] == '-') && (mstr[4] == '0'))
			return MS_QCASTLE;
	}

	if (len == 3) { // Test for ooo
		if ((mstr[0] == 'o') && (mstr[1] == 'o') && (mstr[2] == 'o'))
			return MS_QCASTLE;
		if ((mstr[0] == 'O') && (mstr[1] == 'O') && (mstr[2] == 'O'))
			return MS_QCASTLE;
		if ((mstr[0] == '0') && (mstr[1] == '0') && (mstr[2] == '0'))
			return MS_QCASTLE;
	}

	return alg_is_move(mstr);
}

PUBLIC int
NextPieceLoop(board_t b, int *f, int *r, int color)
{
	while (1) {
		(*r) = (*r) + 1;

		if (*r > 7) {
			*r = 0;
			*f = *f + 1;

			if (*f > 7)
				break;
		}

		if ((b[*f][*r] != NOPIECE) && iscolor(b[*f][*r], color))
			return 1;
	}

	return 0;
}

PUBLIC int
InitPieceLoop(board_t b, int *f, int *r, int color)
{
	*f = 0;
	*r = -1;
	return 1;
}

PRIVATE int
legal_pawn_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	if (ff == tf) {
		if (gs->board[tf][tr] != NOPIECE)
			return 0;

		if (gs->onMove == WHITE) {
			if (tr - fr == 1)
				return 1;
			if ((fr == 1) && (tr - fr == 2) &&
			    gs->board[ff][2] == NOPIECE)
				return 1;
		} else {
			if (fr - tr == 1)
				return 1;
			if ((fr == 6) && (fr - tr == 2) &&
			    gs->board[ff][5] == NOPIECE)
				return 1;
		}

		return 0;
	}

	if (ff != tf) { /* Capture ? */
		if ((ff - tf != 1) && (tf - ff != 1))
			return 0;
		if ((fr - tr != 1) && (tr - fr != 1))
			return 0;

		if (gs->onMove == WHITE) {
			if (fr > tr)
				return 0;
			if ((gs->board[tf][tr] != NOPIECE) &&
			    iscolor(gs->board[tf][tr], BLACK))
				return 1;
			if (gs->ep_possible[0][ff] == 1) {
				if ((tf == ff + 1) &&
				    (gs->board[ff + 1][fr] == B_PAWN))
					return 1;
			} else if (gs->ep_possible[0][ff] == -1) {
				if ((tf == ff - 1) &&
				    (gs->board[ff - 1][fr] == B_PAWN))
					return 1;
			}
		} else {
			if (tr > fr)
				return 0;
			if ((gs->board[tf][tr] != NOPIECE) &&
			    iscolor(gs->board[tf][tr], WHITE))
				return 1;
			if (gs->ep_possible[1][ff] == 1) {
				if ((tf == ff + 1) &&
				    (gs->board[ff + 1][fr] == W_PAWN))
					return 1;
			} else if (gs->ep_possible[1][ff] == -1) {
				if ((tf == ff - 1) &&
				    (gs->board[ff - 1][fr] == W_PAWN))
					return 1;
			}
		}
	}

	return 0;
}

PRIVATE int
legal_knight_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	int	dx, dy;

	dx = ff - tf;
	dy = fr - tr;

	if ((dx == 2) || (dx == -2)) {
		if ((dy == -1) || (dy == 1))
			return 1;
	}
	if ((dy == 2) || (dy == -2)) {
		if ((dx == -1) || (dx == 1))
			return 1;
	}
	return 0;
}

PRIVATE int
legal_bishop_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	int	count;
	int	dx, dy, x, y;
	int	incx, incy;
	int	startx, starty;

	if (ff > tf) {
		dx = ff - tf;
		incx = -1;
	} else {
		dx = tf - ff;
		incx = 1;
	}
	startx = ff + incx;
	if (fr > tr) {
		dy = fr - tr;
		incy = -1;
	} else {
		dy = tr - fr;
		incy = 1;
	}
	starty = fr + incy;
	if (dx != dy)
		return 0;	// Not diagonal
	if (dx == 1)
		return 1;	// One square, ok
	count = dx - 1;
	for (x = startx, y = starty;
	     count;
	     x += incx, y += incy, count--) {
		if (gs->board[x][y] != NOPIECE)
			return 0;
	}
	return 1;
}

PRIVATE int
legal_rook_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	int	i;
	int	start, stop;

	if (ff == tf) {
		if (((fr - tr) == 1) || ((tr - fr) == 1))
			return 1;
		if (fr < tr) {
			start = fr + 1;
			stop = tr - 1;
		} else {
			start = tr + 1;
			stop = fr - 1;
		}
		for (i = start; i <= stop; i++) {
			if (gs->board[ff][i] != NOPIECE)
				return 0;
		}
		return 1;
	} else if (fr == tr) {
		if (((ff - tf) == 1) || ((tf - ff) == 1))
			return 1;
		if (ff < tf) {
			start = ff + 1;
			stop = tf - 1;
		} else {
			start = tf + 1;
			stop = ff - 1;
		}
		for (i = start; i <= stop; i++) {
			if (gs->board[i][fr] != NOPIECE)
				return 0;
		}
		return 1;
	} else {
		return 0;
	}
}

PRIVATE int
legal_queen_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	return (legal_rook_move(gs, ff, fr, tf, tr) ||
		legal_bishop_move(gs, ff, fr, tf, tr));
}

/*
 * New one from soso
 */
PRIVATE int
is_square_attacked(game_state_t *gs, int kf, int kr)
{
	game_state_t	fakeMove;

	fakeMove = *gs;
	fakeMove.board[4][kr] = NOPIECE;
	fakeMove.board[kf][kr] = KING | fakeMove.onMove;
	fakeMove.onMove = CToggle(fakeMove.onMove);

	if (in_check(&fakeMove))
		return 1;
	else
		return 0;
}

#if 0
PRIVATE int
is_square_attacked(game_state_t *gs, int kf, int kr)
{
	int	f, r;

	gs->onMove = CToggle(gs->onMove);

	for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
	     NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
		if (legal_move(gs, f, r, kf, kr)) {
			gs->onMove = CToggle(gs->onMove);
			return 1;
		}
	}

	gs->onMove = CToggle(gs->onMove);
	return 0;
}
#endif

PRIVATE int
legal_king_move(game_state_t *gs, int ff, int fr, int tf, int tr)
{
	if (gs->onMove == WHITE) {
		/* King side castling */
		if ((fr == 0) && (tr == 0) && (ff == 4) && (tf == 6) &&
		    (!gs->wkmoved) &&
		    (!gs->wkrmoved) &&
		    (gs->board[5][0] == NOPIECE) &&
		    (gs->board[6][0] == NOPIECE) &&
		    (gs->board[7][0] == W_ROOK) &&
		    (!is_square_attacked(gs, 4, 0)) &&
		    (!is_square_attacked(gs, 5, 0))) {
			return 1;
		}

		/* Queen side castling */
		if ((fr == 0) && (tr == 0) && (ff == 4) && (tf == 2) &&
		    (!gs->wkmoved) &&
		    (!gs->wqrmoved) &&
		    (gs->board[3][0] == NOPIECE) &&
		    (gs->board[2][0] == NOPIECE) &&
		    (gs->board[1][0] == NOPIECE) &&
		    (gs->board[0][0] == W_ROOK) &&
		    (!is_square_attacked(gs, 4, 0)) &&
		    (!is_square_attacked(gs, 3, 0))) {
			return 1;
		}
	} else {	/* Black */
		/* King side castling */
		if ((fr == 7) && (tr == 7) && (ff == 4) && (tf == 6) &&
		    (!gs->bkmoved) &&
		    (!gs->bkrmoved) &&
		    (gs->board[5][7] == NOPIECE) &&
		    (gs->board[6][7] == NOPIECE) &&
		    (gs->board[7][7] == B_ROOK) &&
		    (!is_square_attacked(gs, 4, 7)) &&
		    (!is_square_attacked(gs, 5, 7))) {
			return 1;
		}

		/* Queen side castling */
		if ((fr == 7) && (tr == 7) && (ff == 4) && (tf == 2) &&
		    (!gs->bkmoved) &&
		    (!gs->bqrmoved) &&
		    (gs->board[3][7] == NOPIECE) &&
		    (gs->board[2][7] == NOPIECE) &&
		    (gs->board[1][7] == NOPIECE) &&
		    (gs->board[0][7] == B_ROOK) &&
		    (!is_square_attacked(gs, 4, 7)) &&
		    (!is_square_attacked(gs, 3, 7))) {
			return 1;
		}
	}

	if (((ff - tf) > 1) || ((tf - ff) > 1))
		return 0;
	if (((fr - tr) > 1) || ((tr - fr) > 1))
		return 0;
	return 1;
}

PRIVATE void
add_pos(int tof, int tor, int *posf, int *posr, int *numpos)
{
	posf[*numpos] = tof;
	posr[*numpos] = tor;
	(*numpos)++;
}

PRIVATE void
possible_pawn_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	if (gs->onMove == WHITE) {
		if (gs->board[onf][onr + 1] == NOPIECE) {
			add_pos(onf, onr + 1, posf, posr, numpos);
			if ((onr == 1) && (gs->board[onf][onr + 2] == NOPIECE))
				add_pos(onf, onr + 2, posf, posr, numpos);
		}
		if ((onf > 0) &&
		    (gs->board[onf - 1][onr + 1] != NOPIECE) &&
		    (iscolor(gs->board[onf - 1][onr + 1], BLACK)))
			add_pos(onf - 1, onr + 1, posf, posr, numpos);
		if ((onf < 7) &&
		    (gs->board[onf + 1][onr + 1] != NOPIECE) &&
		    (iscolor(gs->board[onf + 1][onr + 1], BLACK)))
			add_pos(onf + 1, onr + 1, posf, posr, numpos);
		if (gs->ep_possible[0][onf] == -1)
			add_pos(onf - 1, onr + 1, posf, posr, numpos);
		if (gs->ep_possible[0][onf] == 1)
			add_pos(onf + 1, onr + 1, posf, posr, numpos);
	} else {
		if (gs->board[onf][onr - 1] == NOPIECE) {
			add_pos(onf, onr - 1, posf, posr, numpos);
			if ((onr == 6) && (gs->board[onf][onr - 2] == NOPIECE))
				add_pos(onf, onr - 2, posf, posr, numpos);
		}
		if ((onf > 0) &&
		    (gs->board[onf - 1][onr - 1] != NOPIECE) &&
		    (iscolor(gs->board[onf - 1][onr - 1], WHITE)))
			add_pos(onf - 1, onr - 1, posf, posr, numpos);
		if ((onf < 7) &&
		    (gs->board[onf + 1][onr - 1] != NOPIECE) &&
		    (iscolor(gs->board[onf + 1][onr - 1], WHITE)))
			add_pos(onf + 1, onr - 1, posf, posr, numpos);
		if (gs->ep_possible[1][onf] == -1)
			add_pos(onf - 1, onr - 1, posf, posr, numpos);
		if (gs->ep_possible[1][onf] == 1)
			add_pos(onf + 1, onr - 1, posf, posr, numpos);
	}
}

PRIVATE void
possible_knight_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	int		f, r;
	int		j;
	static int	knightJumps[8][2] = {
		{-1, 2},
		{1, 2},
		{2, -1},
		{2, 1},
		{-1, -2},
		{1, -2},
		{-2, 1},
		{-2, -1}
	};

	for (j = 0; j < 8; j++) {
		f = knightJumps[j][0] + onf;
		r = knightJumps[j][1] + onr;

		if ((f < 0) || (f > 7))
			continue;
		if ((r < 0) || (r > 7))
			continue;
		if ((gs->board[f][r] == NOPIECE) ||
		    (iscolor(gs->board[f][r], CToggle(gs->onMove))))
			add_pos(f, r, posf, posr, numpos);
	}
}

PRIVATE void
possible_bishop_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	int	f, r;

	/* Up Left */
	f = onf;
	r = onr;
	while (1) {
		f--;
		r++;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Up Right */
	f = onf;
	r = onr;
	while (1) {
		f++;
		r++;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Down Left */
	f = onf;
	r = onr;
	while (1) {
		f--;
		r--;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Down Right */
	f = onf;
	r = onr;
	while (1) {
		f++;
		r--;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}
}

PRIVATE void
possible_rook_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	int	f, r;

	/* Left */
	f = onf;
	r = onr;
	while (1) {
		f--;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Right */
	f = onf;
	r = onr;
	while (1) {
		f++;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Up */
	f = onf;
	r = onr;
	while (1) {
		r++;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}

	/* Down */
	f = onf;
	r = onr;
	while (1) {
		r--;
		if ((f < 0) || (f > 7))
			break;
		if ((r < 0) || (r > 7))
			break;
		if ((gs->board[f][r] != NOPIECE) &&
		    (iscolor(gs->board[f][r], gs->onMove)))
			break;
		add_pos(f, r, posf, posr, numpos);
		if (gs->board[f][r] != NOPIECE)
			break;
	}
}

PRIVATE void
possible_queen_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	possible_rook_moves(gs, onf, onr, posf, posr, numpos);
	possible_bishop_moves(gs, onf, onr, posf, posr, numpos);
}

PRIVATE void
possible_king_moves(game_state_t *gs,
    int onf, int onr,
    int *posf, int *posr,
    int *numpos)
{
	int		f, r;
	int		j;
	static int	kingJumps[8][2] = {
		{-1, -1},
		{0, -1},
		{1, -1},
		{-1, 1},
		{0, 1},
		{1, 1},
		{-1, 0},
		{1, 0}
	};

	for (j = 0; j < 8; j++) {
		f = kingJumps[j][0] + onf;
		r = kingJumps[j][1] + onr;

		if ((f < 0) || (f > 7))
			continue;
		if ((r < 0) || (r > 7))
			continue;
		if ((gs->board[f][r] == NOPIECE) ||
		    (iscolor(gs->board[f][r], CToggle(gs->onMove))))
			add_pos(f, r, posf, posr, numpos);
	}
}

/* Doesn't check for check */
PUBLIC int
legal_move(game_state_t *gs,
    int fFile, int fRank,
    int tFile, int tRank)
{
	int	legal;
	int	move_piece;

	if (fFile == ALG_DROP) {
		move_piece = fRank;

		if (move_piece == KING)
			return 0;
		if (gs->holding[gs->onMove == WHITE ? 0 : 1][move_piece - 1] == 0)
			return 0;
		if (gs->board[tFile][tRank] != NOPIECE)
			return 0;
		if (move_piece == PAWN && (tRank == 0 || tRank == 7))
			return 0;

		return 1;
	} else {
		move_piece = piecetype(gs->board[fFile][fRank]);
	}

	if (gs->board[fFile][fRank] == NOPIECE)
		return 0;
	if (!iscolor(gs->board[fFile][fRank], gs->onMove)) // Wrong color
		return 0;
	if ((gs->board[tFile][tRank] != NOPIECE) &&
	    iscolor(gs->board[tFile][tRank], gs->onMove)) // Can't capture own
		return 0;
	if ((fFile == tFile) && (fRank == tRank))	// Same square
		return 0;

	switch (move_piece) {
	case PAWN:
		legal = legal_pawn_move(gs, fFile, fRank, tFile, tRank);
		break;
	case KNIGHT:
		legal = legal_knight_move(gs, fFile, fRank, tFile, tRank);
		break;
	case BISHOP:
		legal = legal_bishop_move(gs, fFile, fRank, tFile, tRank);
		break;
	case ROOK:
		legal = legal_rook_move(gs, fFile, fRank, tFile, tRank);
		break;
	case QUEEN:
		legal = legal_queen_move(gs, fFile, fRank, tFile, tRank);
		break;
	case KING:
		legal = legal_king_move(gs, fFile, fRank, tFile, tRank);
		break;
	default:
		return 0;
		break;
	}

	return legal;
}

/*
 * This fills in the rest of the mt structure once it is determined
 * that. (Returns 'MOVE_ILLEGAL' if move leaves you in check.)
 */
PRIVATE int
move_calculate(game_state_t *gs, move_t *mt, int promote)
{
	game_state_t	fakeMove;
	int		ret, too_long;

	mt->pieceCaptured	= gs->board[mt->toFile][mt->toRank];
	mt->enPassant		= 0;	// Don't know yet,
					// let execute move take care of it

	if (mt->fromFile == ALG_DROP) {
		mt->piecePromotionTo = NOPIECE;
		ret = snprintf(mt->moveString, sizeof mt->moveString,
		    "%s/%c%c-%c%d",
		    wpstring[mt->fromRank],
		    DROP_CHAR,
		    DROP_CHAR,
		    (mt->toFile + 'a'),
		    (mt->toRank + 1));

		too_long = (ret < 0 || (size_t)ret >= sizeof mt->moveString);

		if (too_long) { /* XXX */
			fprintf(stderr, "FICS: %s: warning: "
			    "snprintf truncated\n", __func__);
		}
	} else {
		if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == PAWN &&
		    (mt->toRank == 0 || mt->toRank == 7)) {
			mt->piecePromotionTo = (promote |
			    colorval(gs->board[mt->fromFile][mt->fromRank]));
		} else {
			mt->piecePromotionTo = NOPIECE;
		}

		if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == PAWN &&
		    (mt->fromRank - mt->toRank == 2 ||
		    mt->toRank - mt->fromRank == 2)) {
			mt->doublePawn = mt->fromFile;
		} else {
			mt->doublePawn = -1;
		}

		if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING &&
		    mt->fromFile == 4 &&
		    mt->toFile == 2) {
			strlcpy(mt->moveString, "o-o-o", sizeof mt->moveString);
		} else if (piecetype(gs->board[mt->fromFile][mt->fromRank]) == KING &&
		    mt->fromFile == 4 &&
		    mt->toFile == 6) {
			strlcpy(mt->moveString, "o-o", sizeof mt->moveString);
		} else {
			ret = snprintf(mt->moveString, sizeof mt->moveString,
			    "%s/%c%d-%c%d",
			    wpstring[piecetype(gs->board[mt->fromFile][mt->fromRank])],
			    (mt->fromFile + 'a'),
			    (mt->fromRank + 1),
			    (mt->toFile + 'a'),
			    (mt->toRank + 1));

			too_long = (ret < 0 || (size_t)ret >= sizeof mt->moveString);

			if (too_long) { /* XXX */
				fprintf(stderr, "FICS: %s: warning: "
				    "snprintf truncated\n", __func__);
			}
		}
	}

	// Replace this with an algabraic de-parser
	snprintf(mt->algString, sizeof mt->algString, "%s",
	    alg_unparse(gs, mt));
	fakeMove = *gs;
	execute_move(&fakeMove, mt, 0); // Calculates enPassant also

	// Does making this move leave ME in check?
	if (in_check(&fakeMove))
		return MOVE_ILLEGAL;
	// IanO: bughouse variants: drop cannot be check/checkmate

	return MOVE_OK;
}

PUBLIC int
legal_andcheck_move(game_state_t *gs,
    int fFile, int fRank,
    int tFile, int tRank)
{
	move_t	mt;

	if (!legal_move(gs, fFile, fRank, tFile, tRank))
		return 0;

	mt.color = gs->onMove;
	mt.fromFile = fFile;
	mt.fromRank = fRank;
	mt.toFile = tFile;
	mt.toRank = tRank;

	/*
	 * This should take into account a pawn promoting to another
	 * piece.
	 */
	if (move_calculate(gs, &mt, QUEEN) == MOVE_OK)
		return 1;
	else
		return 0;
}

PUBLIC int
in_check(game_state_t *gs)
{
	int	f, r;
	int	kf = -1, kr = -1;

	/* Find the king */
	if (gs->onMove == WHITE) {
		for (f = 0; f < 8 && kf < 0; f++) {
			for (r = 0; r < 8 && kf < 0; r++) {
				if (gs->board[f][r] == B_KING) {
					kf = f;
					kr = r;
				}
			}
		}
	} else {
		for (f = 0; f < 8 && kf < 0; f++) {
			for (r = 0; r < 8 && kf < 0; r++) {
				if (gs->board[f][r] == W_KING) {
					kf = f;
					kr = r;
				}
			}
		}
	}

	if (kf < 0) {
		fprintf(stderr, "FICS: Error game with no king!\n");
		return 0;
	}

	for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
	     NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
		if (legal_move(gs, f, r, kf, kr)) // In Check?
			return 1;
	}

	return 0;
}

PRIVATE int
has_legal_move(game_state_t *gs)
{
	int	f, r;
	int	i;
	int	kf = 0, kr = -1;
	int	kf_and_kr_set = 0;
	int	numpossible = 0;
	int	possiblef[500];
	int	possibler[500];

	for (InitPieceLoop(gs->board, &f, &r, gs->onMove);
	     NextPieceLoop(gs->board, &f, &r, gs->onMove);) {
		switch (piecetype(gs->board[f][r])) {
		case PAWN:
			possible_pawn_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		case KNIGHT:
			possible_knight_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		case BISHOP:
			possible_bishop_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		case ROOK:
			possible_rook_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		case QUEEN:
			possible_queen_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		case KING:
			kf = f;
			kr = r;
			kf_and_kr_set = 1;
			possible_king_moves(gs, f, r, possiblef, possibler,
			    &numpossible);
			break;
		}
		if (numpossible >= 500)
			fprintf(stderr, "FICS: Possible move overrun\n");
		for (i = 0; i < numpossible; i++) {
			if (legal_andcheck_move(gs, f, r, possiblef[i],
			    possibler[i]))
				return 1;
		}
	}

	if (!kf_and_kr_set && kf == 0 && kr == -1) {
		fprintf(stderr, "FICS: %s: 'kf_and_kr_set' is 0\n", __func__);
		return 0;
	}

	// IanO: if we got here, then kf and kr must be set
	if (gs->gameNum >= 0 && garray[gs->gameNum].link >= 0) {
		// bughouse: potential drops as check interpositions
		gs->holding[gs->onMove == WHITE ? 0 : 1][QUEEN - 1]++;
		for (f = kf - 1; f <= kf + 1; f++) {
			for (r = kr - 1; r <= kr + 1; r++) {
				if (f >= 0 &&
				    f < 8 &&
				    r >= 0 &&
				    r < 8 &&
				    gs->board[f][r] == NOPIECE) {
					// try a drop next to the king
					if (legal_andcheck_move(gs, ALG_DROP,
					    QUEEN, f, r)) {
						gs->holding[gs->onMove == WHITE
						    ? 0 : 1][QUEEN - 1]--;
						return 1;
					}
				}
			}
		}

		gs->holding[gs->onMove == WHITE ? 0 : 1][QUEEN - 1]--;
	}

	fprintf(stderr, "FICS: NO LEGAL MOVE!\n");
	return 0;
}

/* This will end up being a very complicated function */
PUBLIC int
parse_move(char *mstr, game_state_t *gs, move_t *mt, int promote)
{
	int	result;
	int	type = is_move(mstr);

	mt->color = gs->onMove;

	switch (type) {
	case MS_NOTMOVE:
		return MOVE_ILLEGAL;
		break;
	case MS_COMP:
		mt->fromFile = mstr[0] - 'a';
		mt->fromRank = mstr[1] - '1';
		mt->toFile = mstr[2] - 'a';
		mt->toRank = mstr[3] - '1';
		break;
	case MS_COMPDASH:
		mt->fromFile = mstr[0] - 'a';
		mt->fromRank = mstr[1] - '1';
		mt->toFile = mstr[3] - 'a';
		mt->toRank = mstr[4] - '1';
		break;
	case MS_KCASTLE:
		mt->fromFile = 4;
		mt->toFile = 6;
		if (gs->onMove == WHITE) {
			mt->fromRank = 0;
			mt->toRank = 0;
		} else {
			mt->fromRank = 7;
			mt->toRank = 7;
		}
		break;
	case MS_QCASTLE:
		mt->fromFile = 4;
		mt->toFile = 2;
		if (gs->onMove == WHITE) {
			mt->fromRank = 0;
			mt->toRank = 0;
		} else {
			mt->fromRank = 7;
			mt->toRank = 7;
		}
		break;
	case MS_ALG:
		// Fills in the mt structure
		if ((result = alg_parse_move(mstr, gs, mt)) != MOVE_OK)
			return result;
		break;
	default:
		return MOVE_ILLEGAL;
		break;
	}

	if (!legal_move(gs, mt->fromFile, mt->fromRank, mt->toFile, mt->toRank))
		return MOVE_ILLEGAL;
	return move_calculate(gs, mt, promote);
}

/*
 * Returns 'MOVE_OK', 'MOVE_NOMATERIAL', 'MOVE_CHECKMATE', or
 * 'MOVE_STALEMATE'.
 *
 * ('check_game_status' prevents recursion.)
 */
PUBLIC int
execute_move(game_state_t *gs, move_t *mt, int check_game_status)
{
	int	i, j, foobar;
	int	movedPiece;
	int	tookPiece;

	if (mt->fromFile == ALG_DROP) {
		movedPiece	= mt->fromRank;
//		tookPiece	= NOPIECE;

		gs->holding[gs->onMove == WHITE ? 0 : 1][movedPiece-1]--;
		gs->board[mt->toFile][mt->toRank] = (movedPiece | gs->onMove);

		if (gs->gameNum >= 0)
			gs->lastIrreversable = garray[gs->gameNum].numHalfMoves;
	} else {
		movedPiece	= gs->board[mt->fromFile][mt->fromRank];
		tookPiece	= gs->board[mt->toFile][mt->toRank];

		if (mt->piecePromotionTo == NOPIECE) {
			gs->board[mt->toFile][mt->toRank] =
			    gs->board[mt->fromFile][mt->fromRank];
		} else {
			gs->board[mt->toFile][mt->toRank] =
			    (mt->piecePromotionTo | gs->onMove);
		}

		gs->board[mt->fromFile][mt->fromRank] = NOPIECE;

		/*
		 * Check if irreversable
		 */
		if (piecetype(movedPiece) == PAWN || tookPiece != NOPIECE) {
			if (gs->gameNum >= 0) {
				gs->lastIrreversable =
				    garray[gs->gameNum].numHalfMoves;
			}
		}

		/*
		 * Check if this move is en-passant
		 */
		if (piecetype(movedPiece) == PAWN &&
		    mt->fromFile != mt->toFile &&
		    tookPiece == NOPIECE) {
			if (gs->onMove == WHITE)
				mt->pieceCaptured = B_PAWN;
			else
				mt->pieceCaptured = W_PAWN;

			if (mt->fromFile > mt->toFile)
				mt->enPassant = -1;
			else
				mt->enPassant = 1;
			gs->board[mt->toFile][mt->fromRank] = NOPIECE;
		}

		/*
		 * Check en-passant flags for next moves
		 */
		for (i = 0; i < 8; i++) {
			gs->ep_possible[0][i] = 0;
			gs->ep_possible[1][i] = 0;
		}

		if (piecetype(movedPiece) == PAWN &&
		    (mt->fromRank == (mt->toRank + 2) ||
		    (mt->fromRank + 2) == mt->toRank)) {
			/*
			 * Should turn on enpassent flag if possible.
			 */

			if (gs->onMove == WHITE) {
				if (mt->toFile < 7 &&
				    gs->board[mt->toFile + 1][3] == B_PAWN)
					gs->ep_possible[1][mt->toFile + 1] = -1;
				if ((mt->toFile - 1) >= 0 &&
				    gs->board[mt->toFile - 1][3] == B_PAWN)
					gs->ep_possible[1][mt->toFile - 1] = 1;
			} else {
				if (mt->toFile < 7 &&
				    gs->board[mt->toFile + 1][4] == W_PAWN)
					gs->ep_possible[0][mt->toFile + 1] = -1;
				if ((mt->toFile - 1) >= 0 &&
				    gs->board[mt->toFile - 1][4] == W_PAWN)
					gs->ep_possible[0][mt->toFile - 1] = 1;
			}
		}

		if (piecetype(movedPiece) == ROOK && mt->fromFile == 0) {
			if (mt->fromRank == 0 && gs->onMove == WHITE)
				gs->wqrmoved = 1;
			if (mt->fromRank == 7 && gs->onMove == BLACK)
				gs->bqrmoved = 1;
		}

		if (piecetype(movedPiece) == ROOK && mt->fromFile == 7) {
			if (mt->fromRank == 0 && gs->onMove == WHITE)
				gs->wkrmoved = 1;
			if (mt->fromRank == 7 && gs->onMove == BLACK)
				gs->bkrmoved = 1;
		}

		if (piecetype(movedPiece) == KING) {
			if (gs->onMove == WHITE)
				gs->wkmoved = 1;
			else
				gs->bkmoved = 1;
		}

		if (piecetype(movedPiece) == KING &&
		    (mt->fromFile == 4 && mt->toFile == 6)) {	// Check for KS
								// castling
			gs->board[5][mt->toRank] = gs->board[7][mt->toRank];
			gs->board[7][mt->toRank] = NOPIECE;
		}

		if (piecetype(movedPiece) == KING &&
		    (mt->fromFile == 4 && mt->toFile == 2)) {	// Check for QS
								// castling
			gs->board[3][mt->toRank] = gs->board[0][mt->toRank];
			gs->board[0][mt->toRank] = NOPIECE;
		}
	}

	if (gs->onMove == BLACK)
		gs->moveNum++;

	if (check_game_status) {
		/*
		 * Does this move result in check?
		 */

		if (in_check(gs)) {
			/*
			 * Check for checkmate
			 */

			gs->onMove = CToggle(gs->onMove);

			if (!has_legal_move(gs))
				return MOVE_CHECKMATE;
		} else {
			/*
			 * Check for stalemate
			 */

			gs->onMove = CToggle(gs->onMove);

			if (!has_legal_move(gs))
				return MOVE_STALEMATE;

			/*
			 * loon: check for insufficient mating material
			 */

			foobar = 0;

			for (i = 0; i < 8; i++) {
				for (j = 0; j < 8; j++) {
					switch (piecetype(gs->board[i][j])) {
					case KNIGHT:
					case BISHOP:
						foobar++;
						break;
					case KING:
					case NOPIECE:
						break;
					default:
						foobar = 2;
						break;
					}
				}
			}

			if (foobar < 2)
				return MOVE_NOMATERIAL;
		}
	} else {
		gs->onMove = CToggle(gs->onMove);
	}

	return MOVE_OK;
}

#ifdef TIMESEAL
static void
backup_move_timeseal(int g, move_t *m)
{
	if (m->color == WHITE) {
		if (con[parray[garray[g].white].socket].timeseal) {
			garray[g].wRealTime += (m->tookTime * 100);
			garray[g].wRealTime -= (garray[g].wIncrement * 100);
			garray[g].wTime = garray[g].wRealTime / 100;

			if (con[parray[garray[g].black].socket].timeseal) {
				garray[g].bTime = garray[g].bRealTime / 100;
			} else {
				garray[g].bTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			}
		} else { // white has no timeseal
			garray[g].wTime += m->tookTime;
			garray[g].wTime -= garray[g].wIncrement;

			if (con[parray[garray[g].black].socket].timeseal) {
				garray[g].bTime = garray[g].bRealTime / 100;
			} else {
				garray[g].bTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			}
		}
	} else {
		if (con[parray[garray[g].black].socket].timeseal) {
			garray[g].bRealTime += (m->tookTime * 100);
			garray[g].bRealTime -= (garray[g].wIncrement * 100);
			garray[g].bTime = garray[g].bRealTime / 100;

			if (con[parray[garray[g].white].socket].timeseal) {
				garray[g].wTime = garray[g].wRealTime / 100;
			} else {
				garray[g].wTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			}
		} else { // black has no timeseal
			garray[g].bTime += m->tookTime;

			if (!garray[g].bIncrement)
				garray[g].bTime -= garray[g].wIncrement;
			else
				garray[g].bTime -= garray[g].bIncrement;
			if (con[parray[garray[g].white].socket].timeseal) {
				garray[g].wTime = garray[g].wRealTime / 100;
			} else {
				garray[g].wTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			}
		}
	}
}
#endif // TIMESEAL

PUBLIC int
backup_move(int g, int mode)
{
	game_state_t	*gs;
	int		 now, i;
	move_t		*m, *m1;

	if (garray[g].link >= 0)	// IanO: not implemented for bughouse yet
		return MOVE_ILLEGAL;
	if (garray[g].numHalfMoves < 1)
		return MOVE_ILLEGAL;
	gs = &garray[g].game_state;
	m = (mode == REL_GAME)
	    ? &garray[g].moveList[garray[g].numHalfMoves - 1]
	    : &garray[g].examMoveList[garray[g].numHalfMoves - 1];
	if (m->toFile < 0)
		return MOVE_ILLEGAL;

	gs->board[m->fromFile][m->fromRank] = gs->board[m->toFile][m->toRank];

	if (m->piecePromotionTo != NOPIECE) {
		gs->board[m->fromFile][m->fromRank] = PAWN |
		    colorval(gs->board[m->fromFile][m->fromRank]);
	}

	/*
	 * When takeback a _first_ move of rook, the ??rmoved variable
	 * must be cleared.
	 * To check, if the move is first, we should scan moveList.
	 */
	if (piecetype(gs->board[m->fromFile][m->fromRank]) == ROOK) {
		if (m->color == WHITE) {
			if ((m->fromFile == 0) && (m->fromRank == 0)) {
				for (i = 2; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 0) &&
					    (m1->fromRank == 0))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->wqrmoved = 0;
			}

			if ((m->fromFile == 7) && (m->fromRank == 0)) {
				for (i = 2; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 7) &&
					    (m1->fromRank == 0))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->wkrmoved = 0;
			}
		} else {
			if ((m->fromFile == 0) && (m->fromRank == 7)) {
				for (i = 3; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 0) &&
					    (m1->fromRank == 0))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->bqrmoved = 0;
			}

			if ((m->fromFile == 7) && (m->fromRank == 7)) {
				for (i = 3; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 7) &&
					    (m1->fromRank == 0))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->bkrmoved = 0;
			}
		}
	}

	if (piecetype(gs->board[m->fromFile][m->fromRank]) == KING) {
		gs->board[m->toFile][m->toRank] = m->pieceCaptured;

		if (m->toFile - m->fromFile == 2) {
			gs->board[7][m->fromRank] = ROOK |
			    colorval(gs->board[m->fromFile][m->fromRank]);
			gs->board[5][m->fromRank] = NOPIECE;

			/*
			 * If takeback a castling, the appropriates
			 * ??moved variables must be cleared.
			 */
			if (m->color == WHITE) {
				gs->wkmoved = 0;
				gs->wkrmoved = 0;
			} else {
				gs->bkmoved = 0;
				gs->bkrmoved = 0;
			}
			goto cleanupMove;
		}

		if (m->fromFile - m->toFile == 2) {
			gs->board[0][m->fromRank] = ROOK |
			    colorval(gs->board[m->fromFile][m->fromRank]);
			gs->board[3][m->fromRank] = NOPIECE;

			/*
			 * If takeback a castling, the appropriate
			 * ??moved variables must be cleared.
			 */
			if (m->color == WHITE) {
				gs->wkmoved = 0;
				gs->wqrmoved = 0;
			} else {
				gs->bkmoved = 0;
				gs->bqrmoved = 0;
			}
			goto cleanupMove;
		}

		/*
		 * When takeback a _first_ move of king (not the castling),
		 * the ?kmoved variable must be cleared.
		 * To check, if the move is first, we should scan moveList.
		 */
		if (m->color == WHITE) {
			if ((m->fromFile == 4) && (m->fromRank == 0)) {
				for (i = 2; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 4) &&
					    (m1->fromRank == 0))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->wkmoved = 0;
			}
		} else {
			if ((m->fromFile == 4) && (m->fromRank == 7)) {
				for (i = 3; i < garray[g].numHalfMoves - 1;
				     i += 2) {
					m1 = (mode == REL_GAME)
					    ? &garray[g].moveList[i]
					    : &garray[g].examMoveList[i];

					if ((m1->fromFile == 4) &&
					    (m1->fromRank == 7))
						break;
				}

				if (i == garray[g].numHalfMoves - 1)
					gs->bkmoved = 0;
			}
		}
	}

	if (m->enPassant) { // Do enPassant
		gs->board[m->toFile][m->fromRank] = PAWN |
		    (colorval(gs->board[m->fromFile][m->fromRank]) == WHITE
		    ? BLACK
		    : WHITE);
		gs->board[m->toFile][m->toRank] = NOPIECE;
		/*
		 * Should set the enpassant array, but I don't care
		 * right now.
		 */
		goto cleanupMove;
	}

	gs->board[m->toFile][m->toRank] = m->pieceCaptured;

  cleanupMove:

	if (garray[g].status != GAME_EXAMINE)
		game_update_time(g);

	garray[g].numHalfMoves--;

	if (garray[g].status != GAME_EXAMINE) {
		if (garray[g].wInitTime) {	// Don't update times in untimed
						// games
			now = tenth_secs();

#ifdef TIMESEAL
			backup_move_timeseal(g, m);
#else
			if (m->color == WHITE) {
				garray[g].wTime += m->tookTime;
				garray[g].wTime = (garray[g].wTime -
						   garray[g].wIncrement);
				garray[g].bTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			} else {
				garray[g].bTime += m->tookTime;

				if (!garray[g].bIncrement) {
					garray[g].bTime = (garray[g].bTime -
							   garray[g].wIncrement);
				} else {
					garray[g].bTime = (garray[g].bTime -
							   garray[g].bIncrement);
				}

				garray[g].wTime += (garray[g].lastDecTime -
						    garray[g].lastMoveTime);
			}
#endif

			if (garray[g].numHalfMoves == 0)
				garray[g].timeOfStart = now;
			garray[g].lastMoveTime = now;
			garray[g].lastDecTime = now;
		}
	}

	if (gs->onMove == BLACK) {
		gs->onMove = WHITE;
	} else {
		gs->onMove = BLACK;
		gs->moveNum--;
	}

	/*
	 * Takeback of last move is done already, it's time to update
	 * enpassant array.
	 * (Patch from Soso, added by Sparky 3/17/95)
	 */
	if (garray[g].numHalfMoves > 0) {
		m1 = (mode == REL_GAME)
		    ? &garray[g].moveList[garray[g].numHalfMoves - 1]
		    : &garray[g].examMoveList[garray[g].numHalfMoves - 1];

		if (piecetype(gs->board[m1->toFile][m1->toRank]) == PAWN) {
			if ((m1->toRank - m1->fromRank) == 2) {
				if ((m1->toFile < 7) &&
				    gs->board[m1->toFile + 1][3] == B_PAWN) {
					gs->ep_possible[1][m1->toFile + 1] = -1;
				}
				if ((m1->toFile - 1 >= 0) &&
				    gs->board[m1->toFile - 1][3] == B_PAWN) {
					gs->ep_possible[1][m1->toFile - 1] = 1;
				}
			}
			if ((m1->toRank - m1->fromRank) == -2) {
				if ((m1->toFile < 7) &&
				    gs->board[m1->toFile + 1][4] == W_PAWN) {
					gs->ep_possible[0][m1->toFile + 1] = -1;
				}
				if ((m1->toFile - 1 >= 0) &&
				    gs->board[m1->toFile - 1][4] == W_PAWN) {
					gs->ep_possible[0][m1->toFile - 1] = 1;
				}
			}
		}
	}

	return MOVE_OK;
}