/* gameproc.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
   Dave Herscovici		95/11/26	Split into two files;
   						Second is 'obsproc.c'.
   Markus Uhlin			23/12/16	Fixed compiler warnings
   Markus Uhlin			24/01/04	Fixed pprintf_prompt() calls
   Markus Uhlin			24/03/30	Refactored and reformatted all
						functions.
   Markus Uhlin			24/03/30	Size-bounded string handling.
   Markus Uhlin			24/04/13	Added usage of msnprintf(),
						mstrlcpy() and mstrlcat().
   Markus Uhlin			24/05/05	Added usage of reallocarray().
*/

#include "stdinclude.h"
#include "common.h"

#include <err.h>

#include "command.h"
#include "comproc.h"
#include "config.h"
#include "eco.h"
#include "ficsmain.h"
#include "gamedb.h"
#include "gameproc.h"
#include "lists.h"
#include "matchproc.h"
#include "maxxes-utils.h"
#include "movecheck.h"
#include "network.h"
#include "obsproc.h"
#include "playerdb.h"
#include "ratings.h"
#include "rmalloc.h"
#include "utils.h"

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

PUBLIC void
game_ended(int g, int winner, int why)
{
	char	*NameOfWinner, *NameOfLoser;
	char	 EndSymbol[10] = { '\0' };
	char	 outstr[200] = { '\0' };
	char	 tmp[200] = { '\0' };
	char	 winSymbol[10] = { '\0' };
	int	 beingplayed = 0; // i.e. it wasn't loaded for adjudication
	int	 gl = garray[g].link;
	int	 isDraw = 0;
	int	 p;
	int	 rate_change = 0;
	int	 whiteResult;

	beingplayed = (parray[garray[g].black].game == g);

	msnprintf(outstr, sizeof outstr, "\n{Game %d (%s vs. %s) ",
	    (g + 1),
	    parray[garray[g].white].name,
	    parray[garray[g].black].name);

	garray[g].result = why;
	garray[g].winner = winner;

	if (winner == WHITE) {
		whiteResult = RESULT_WIN;
		mstrlcpy(winSymbol, "1-0", sizeof winSymbol);
		NameOfWinner = parray[garray[g].white].name;
		NameOfLoser = parray[garray[g].black].name;
	} else {
		whiteResult = RESULT_LOSS;
		mstrlcpy(winSymbol, "0-1", sizeof winSymbol);
		NameOfWinner = parray[garray[g].black].name;
		NameOfLoser = parray[garray[g].white].name;
	}

	switch (why) {
	case END_CHECKMATE:
		msnprintf(tmp, sizeof tmp, "%s checkmated} %s\n",
		    NameOfLoser,
		    winSymbol);
		mstrlcpy(EndSymbol, "Mat", sizeof EndSymbol);
		rate_change = 1;
		break;
	case END_RESIGN:
		msnprintf(tmp, sizeof tmp, "%s resigns} %s\n",
		    NameOfLoser,
		    winSymbol);
		mstrlcpy(EndSymbol, "Res", sizeof EndSymbol);
		rate_change = 1;
		break;
	case END_FLAG:
		msnprintf(tmp, sizeof tmp, "%s forfeits on time} %s\n",
		    NameOfLoser,
		    winSymbol);
		mstrlcpy(EndSymbol, "Fla", sizeof EndSymbol);
		rate_change = 1;
		break;
	case END_STALEMATE:
		mstrlcpy(tmp, "Game drawn by stalemate} 1/2-1/2\n", sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "Sta", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_AGREEDDRAW:
		mstrlcpy(tmp, "Game drawn by mutual agreement} 1/2-1/2\n",
		    sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "Agr", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_BOTHFLAG:
		mstrlcpy(tmp, "Game drawn because both players ran out of "
		    "time} 1/2-1/2\n", sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "Fla", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_REPETITION:
		mstrlcpy(tmp, "Game drawn by repetition} 1/2-1/2\n", sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "Rep", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_50MOVERULE:
		mstrlcpy(tmp, "Game drawn by the 50 move rule} 1/2-1/2\n",
		    sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "50", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_ADJOURN:
		if (gl >= 0) {
			mstrlcpy(tmp, "Bughouse game aborted.} *\n",
			    sizeof tmp);
			whiteResult = RESULT_ABORT;
		} else {
			mstrlcpy(tmp, "Game adjourned by mutual agreement} *\n",
			    sizeof tmp);
			game_save(g);
		}
		break;
	case END_LOSTCONNECTION:
		msnprintf(tmp, sizeof tmp, "%s lost connection; game ",
		    NameOfWinner);

		if (parray[garray[g].white].registered &&
		    parray[garray[g].black].registered &&
		    gl < 0) {
			mstrlcpy(tmp, "adjourned} *\n", sizeof tmp);
			game_save(g);
		} else
			mstrlcpy(tmp, "aborted} *\n", sizeof tmp);
		whiteResult = RESULT_ABORT;
		break;
	case END_ABORT:
		mstrlcpy(tmp, "Game aborted by mutual agreement} *\n",
		    sizeof tmp);
		whiteResult = RESULT_ABORT;
		break;
	case END_COURTESY:
		msnprintf(tmp, sizeof tmp, "Game courtesyaborted by %s} *\n",
		    NameOfWinner);
		whiteResult = RESULT_ABORT;
		break;
	case END_COURTESYADJOURN:
		if (gl >= 0) {
			msnprintf(tmp, sizeof tmp, "Bughouse game "
			    "courtesyaborted by %s.} *\n",
			    NameOfWinner);
			whiteResult = RESULT_ABORT;
		} else {
			msnprintf(tmp, sizeof tmp, "Game courtesyadjourned by "
			    "%s} *\n",
			    NameOfWinner);
			game_save(g);
		}
		break;
	case END_NOMATERIAL:
		// Draw by insufficient material (e.g., lone K vs. lone K)
		mstrlcpy(tmp, "Neither player has mating material} 1/2-1/2\n",
		    sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "NM ", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_FLAGNOMATERIAL:
		msnprintf(tmp, sizeof tmp, "%s ran out of time and %s has no "
		    "material to mate} 1/2-1/2\n",
		    NameOfLoser,
		    NameOfWinner);
		isDraw = 1;
		mstrlcpy(EndSymbol, "TM ", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_ADJWIN:
		msnprintf(tmp, sizeof tmp, "%s wins by adjudication} %s\n",
		    NameOfWinner, winSymbol);
		mstrlcpy(EndSymbol, "Adj", sizeof EndSymbol);
		rate_change = 1;
		break;
	case END_ADJDRAW:
		mstrlcpy(tmp, "Game drawn by adjudication} 1/2-1/2\n",
		    sizeof tmp);
		isDraw = 1;
		mstrlcpy(EndSymbol, "Adj", sizeof EndSymbol);
		rate_change = 1;
		whiteResult = RESULT_DRAW;
		break;
	case END_ADJABORT:
		mstrlcpy(tmp, "Game aborted by adjudication} *\n", sizeof tmp);
		whiteResult = RESULT_ABORT;
		break;
	default:
		mstrlcpy(tmp, "Hmm, the game ended and I don't know why} *\n",
		    sizeof tmp);
		break;
	}

	mstrlcat(outstr, tmp, sizeof outstr);

	if (beingplayed) {
		pprintf_noformat(garray[g].white, "%s", outstr);
		pprintf_noformat(garray[g].black, "%s", outstr);

		if (parray[garray[g].white].bell)
			pprintf(garray[g].white, "\007");
		if (parray[garray[g].black].bell)
			pprintf(garray[g].black, "\007");

		garray[g].link = -1;	// IanO: avoids recursion

		if (gl >= 0 && garray[gl].link >= 0) {
			pprintf_noformat(garray[gl].white, "%s", outstr);
			pprintf_noformat(garray[gl].black, "%s", outstr);

			game_ended(gl, CToggle(winner), why);
		}

		for (p = 0; p < p_num; p++) {
			if (p == garray[g].white || p == garray[g].black)
				continue;
			if (parray[p].status != PLAYER_PROMPT)
				continue;
			if (!parray[p].i_game && !player_is_observe(p, g))
				continue;

			pprintf_noformat(p, "%s", outstr);
			pprintf_prompt(p, "%s", "");
		}
	}

	if (garray[g].rated && rate_change) {
		/* Adjust ratings */
		rating_update(g);
	} else {
		if (beingplayed) {
			pprintf(garray[g].white, "No ratings adjustment done."
			    "\n");
			pprintf(garray[g].black, "No ratings adjustment done."
			    "\n");
		}
	}

	if (rate_change && gl < 0)
		game_write_complete(g, isDraw, EndSymbol);

	/*
	 * Mail off the moves
	 */
	if (parray[garray[g].white].automail)
		pcommand(garray[g].white, "mailmoves");
	if (parray[garray[g].black].automail)
		pcommand(garray[g].black, "mailmoves");

	parray[garray[g].white].num_white++;
	parray[garray[g].white].lastColor = WHITE;
	parray[garray[g].black].num_black++;
	parray[garray[g].black].lastColor = BLACK;
	parray[garray[g].white].last_opponent = garray[g].black;
	parray[garray[g].black].last_opponent = garray[g].white;

	if (beingplayed) {
		parray[garray[g].white].game = -1;
		parray[garray[g].black].game = -1;
		parray[garray[g].white].opponent = -1;
		parray[garray[g].black].opponent = -1;

		if (garray[g].white != commanding_player)
			pprintf_prompt(garray[g].white, "%s", "");
		if (garray[g].black != commanding_player)
			pprintf_prompt(garray[g].black, "%s", "");

		if (parray[garray[g].white].simul_info.numBoards)
			player_simul_over(garray[g].white, g, whiteResult);
	}

	game_finish(g);
}

PRIVATE int
was_promoted(game *g, int f, int r)
{
#define BUGHOUSE_PAWN_REVERT 1
#if BUGHOUSE_PAWN_REVERT
	for (int i = g->numHalfMoves-2; i > 0; i -= 2) {
		if (g->moveList[i].toFile == f &&
		    g->moveList[i].toRank == r) {
			if (g->moveList[i].piecePromotionTo)
				return 1;
			if (g->moveList[i].fromFile == ALG_DROP)
				return 0;
			f = g->moveList[i].fromFile;
			r = g->moveList[i].fromRank;
		}
	}
#endif
	return 0;
}

PUBLIC int
pIsPlaying(int p)
{
	int	g = parray[p].game;
	int	p1 = parray[p].opponent;

	if (g < 0 || garray[g].status == GAME_EXAMINE) {
		pprintf(p, "You are not playing a game.\n");
		return 0;
	} else if (garray[g].white != p && garray[g].black != p) {
		/*
		 * oh oh; big bad game bug.
		 */

		fprintf(stderr, "BUG: Player %s playing game %d according to "
		    "parray, but not according to garray.\n",
		    parray[p].name, (g + 1));
		pprintf(p, "Disconnecting you from game number %d.\n", (g + 1));
		parray[p].game = -1;

		if (p1 >= 0 &&
		    parray[p1].game == g &&
		    garray[g].white != p1 &&
		    garray[g].black != p1) {
			pprintf(p1, "Disconnecting you from game number %d.\n",
			    (g + 1));
			parray[p1].game = -1;
		}

		return 0;
	} else
		return 1;
}

PUBLIC void
process_move(int p, char *command)
{
	int		 g;
	int		 i;
	int		 len;
	int		 result;
	move_t		 move;
	unsigned int	 now;

	if (parray[p].game < 0) {
		pprintf(p, "You are not playing or examining a game.\n");
		return;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);
	g = parray[p].game;

	if (garray[g].status != GAME_EXAMINE) {
		if (!pIsPlaying(p)) // XXX
			return;
		if (parray[p].side != garray[g].game_state.onMove) {
			pprintf(p, "It is not your move.\n");
			return;
		}
		if (garray[g].clockStopped) {
			pprintf(p, "Game clock is paused, use \"unpause\" "
			    "to resume.\n");
			return;
		}
	}

	if ((len = strlen(command)) > 1) {
		if (command[len - 2] == '=') {
			switch (tolower(command[strlen(command) - 1])) {
			case 'n':
				parray[p].promote = KNIGHT;
				break;
			case 'b':
				parray[p].promote = BISHOP;
				break;
			case 'r':
				parray[p].promote = ROOK;
				break;
			case 'q':
				parray[p].promote = QUEEN;
				break;
			default:
				pprintf(p, "Don't understand that move.\n");
				return;
				break;
			}
		}
	}

	switch (parse_move(command, &garray[g].game_state, &move,
			   parray[p].promote)) {
	case MOVE_ILLEGAL:
		pprintf(p, "Illegal move.\n");
		return;
		break;
	case MOVE_AMBIGUOUS:
		pprintf(p, "Ambiguous move.\n");
		return;
		break;
	default:
		break;
	}

	if (garray[g].status == GAME_EXAMINE) {
		garray[g].numHalfMoves++;

		if (garray[g].numHalfMoves > garray[g].examMoveListSize) {
			garray[g].examMoveListSize += 20;	// Allocate 20
								// moves at a
								// time
			if (!garray[g].examMoveList) {
				garray[g].examMoveList =
				    reallocarray(NULL,
				    sizeof(move_t),
				    garray[g].examMoveListSize);
				if (garray[g].examMoveList == NULL)
					err(1, "%s: reallocarray", __func__);
				else
					malloc_count++;
			} else {
				garray[g].examMoveList =
				    reallocarray(garray[g].examMoveList,
				    sizeof(move_t),
				    garray[g].examMoveListSize);
				if (garray[g].examMoveList == NULL)
					err(1, "%s: reallocarray", __func__);
			}
		}

		now = tenth_secs();

		result		= execute_move(&garray[g].game_state, &move, 1);
		move.atTime	= now; // XXX
		move.tookTime	= 0;
		MakeFENpos(g, (char *)move.FENpos, ARRAY_SIZE(move.FENpos));
		garray[g].examMoveList[garray[g].numHalfMoves - 1] = move;

		/*
		 * roll back time
		 */

		if (garray[g].game_state.onMove == WHITE) {
			garray[g].wTime +=
			    (garray[g].lastDecTime - garray[g].lastMoveTime);
		} else {
			garray[g].bTime +=
			    (garray[g].lastDecTime - garray[g].lastMoveTime);
		}

		// XXX: 'now' was assigned here
		// <--

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

		garray[g].lastMoveTime = now;
		garray[g].lastDecTime = now;
	} else { // real game
		i = parray[p].opponent;

		if (parray[i].simul_info.numBoards &&
		    parray[i].simul_info.boards[parray[i].simul_info.onBoard] !=
		    g) {
			pprintf(p, "It isn't your turn: wait until the simul "
			    "giver is at your board.\n");
			return;
		}

#ifdef TIMESEAL
		if (con[parray[p].socket].timeseal) { // Does he use timeseal?
			if (parray[p].side == WHITE) {
				int diff;

				garray[g].wLastRealTime = garray[g].wRealTime;
				garray[g].wTimeWhenMoved =
				    con[parray[p].socket].time;

				diff = (garray[g].wTimeWhenMoved -
					garray[g].wTimeWhenReceivedMove);

				if (diff < 0 ||
				    garray[g].wTimeWhenReceivedMove == 0) {
					/*
					 * Might seem weird - but
					 * could be caused by a person
					 * moving BEFORE he receives
					 * the board pos (this is
					 * possible due to lag) but
					 * it's safe to say he moved
					 * in 0 secs.
					 */
					garray[g].wTimeWhenReceivedMove =
					    garray[g].wTimeWhenMoved;
				} else {
					garray[g].wRealTime -=
					    (garray[g].wTimeWhenMoved -
					    garray[g].wTimeWhenReceivedMove);
				}
			} else if (parray[p].side == BLACK) {
				int diff;

				garray[g].bLastRealTime = garray[g].bRealTime;
				garray[g].bTimeWhenMoved =
				    con[parray[p].socket].time;

				diff = (garray[g].bTimeWhenMoved -
					garray[g].bTimeWhenReceivedMove);

				if (diff < 0 ||
				    garray[g].bTimeWhenReceivedMove == 0) {
					/*
					 * Might seem weird - but
					 * could be caused by a person
					 * moving BEFORE he receives
					 * the board pos (this is
					 * possible due to lag) but
					 * it's safe to say he moved
					 * in 0 secs.
					 */
					garray[g].bTimeWhenReceivedMove =
					    garray[g].bTimeWhenMoved;
				} else {
					garray[g].bRealTime -=
					    (garray[g].bTimeWhenMoved -
					    garray[g].bTimeWhenReceivedMove);
				}
			}
		}

		/*
		 * We need to reset the opp's time for receiving the
		 * board since the timeseal decoder only alters the
		 * time if it's 0. Otherwise the time would be changed
		 * if the player did a refresh which would screw up
		 * the timings.
		 */
		if (parray[p].side == WHITE)
			garray[g].bTimeWhenReceivedMove = 0;
		else
			garray[g].wTimeWhenReceivedMove = 0;
#endif

		game_update_time(g);

		/*
		 * XXX: Maybe add autoflag here in the future?
		 */

#ifdef TIMESEAL
		if (con[parray[p].socket].timeseal) {  // Does he use timeseal?
			if (parray[p].side == WHITE) {
				garray[g].wRealTime +=
				    (garray[g].wIncrement * 100);
				garray[g].wTime = (garray[g].wRealTime / 100);
			} else if (parray[p].side == BLACK) {
				garray[g].bRealTime +=
				    (garray[g].bIncrement * 100);
				garray[g].bTime = (garray[g].bRealTime / 100);
			}
		} else {
			if (garray[g].game_state.onMove == BLACK)
				garray[g].bTime += garray[g].bIncrement;
			if (garray[g].game_state.onMove == WHITE)
				garray[g].wTime += garray[g].wIncrement;
		}
#else
		if (garray[g].game_state.onMove == BLACK)
			garray[g].bTime += garray[g].bIncrement;
		if (garray[g].game_state.onMove == WHITE)
			garray[g].wTime += garray[g].wIncrement;
#endif

		/*
		 * Do the move.
		 */

		garray[g].numHalfMoves++;

		if (garray[g].numHalfMoves > garray[g].moveListSize) {
			garray[g].moveListSize += 20;	// Allocate 20 moves at
							// a time

			if (!garray[g].moveList) {
				garray[g].moveList =
				    reallocarray(NULL,
				    sizeof(move_t),
				    garray[g].moveListSize);
				if (garray[g].moveList == NULL)
					err(1, "%s: reallocarray", __func__);
				else
					malloc_count++;
			} else {
				garray[g].moveList =
				    reallocarray(garray[g].moveList,
				    sizeof(move_t),
				    garray[g].moveListSize);
				if (garray[g].moveList == NULL)
					err(1, "%s: reallocarray", __func__);
			}
		}

		result = execute_move(&garray[g].game_state, &move, 1);

		if (result == MOVE_OK &&
		    garray[g].link >= 0 &&
		    move.pieceCaptured != NOPIECE) {
			/*
			 * Transfer captured piece to partner.
			 * Check if piece reverts to a pawn.
			 */
			if (was_promoted(&garray[g], move.toFile, move.toRank)) {
				update_holding(garray[g].link,
				    colorval(move.pieceCaptured) | PAWN);
			} else {
				update_holding(garray[g].link,
				    move.pieceCaptured);
			}
		}

		now = tenth_secs();
		move.atTime = now;

		if (garray[g].numHalfMoves > 1)
			move.tookTime = (move.atTime - garray[g].lastMoveTime);
		else
			move.tookTime = (move.atTime - garray[g].startTime);

		garray[g].lastMoveTime = now;
		garray[g].lastDecTime = now;

#ifdef TIMESEAL
		if (con[parray[p].socket].timeseal) { // Does he use timeseal?
			if (parray[p].side == WHITE) {
				move.tookTime =
				    ((garray[parray[p].game].wTimeWhenMoved -
				    garray[parray[p].game].wTimeWhenReceivedMove) /
				    100);
			} else {
				move.tookTime =
				    ((garray[parray[p].game].bTimeWhenMoved -
				    garray[parray[p].game].bTimeWhenReceivedMove) /
				    100);
			}
		}
#endif

		MakeFENpos(g, (char *)move.FENpos, ARRAY_SIZE(move.FENpos));
		garray[g].moveList[garray[g].numHalfMoves - 1] = move;
	}

	send_boards(g);

	if (result == MOVE_ILLEGAL)
		pprintf(p, "Internal error, illegal move accepted!\n");
	if (result == MOVE_OK && garray[g].status == GAME_EXAMINE) {
		for (int p1 = 0; p1 < p_num; p1++) {
			if (parray[p1].status != PLAYER_PROMPT)
				continue;
			if (player_is_observe(p1, g) || parray[p1].game == g) {
				pprintf_prompt(p1, "%s moves: %s\n",
				    parray[p].name, move.algString);
			}
		}
	}

	if (result == MOVE_CHECKMATE) {
		if (garray[g].status == GAME_EXAMINE) {
			for (int p1 = 0; p1 < p_num; p1++) {
				if (parray[p1].status != PLAYER_PROMPT)
					continue;
				if (player_is_observe(p1, g) ||
				    parray[p1].game == g) {
					pprintf(p1, "%s has been checkmated.\n",
					    (CToggle(garray[g].game_state.onMove)
					    == BLACK ? "White" : "Black"));
				}
			}
		} else {
			game_ended(g, CToggle(garray[g].game_state.onMove),
			    END_CHECKMATE);
		}
	}

	if (result == MOVE_STALEMATE) {
		if (garray[g].status == GAME_EXAMINE) {
			for (int p1 = 0; p1 < p_num; p1++) {
				if (parray[p1].status != PLAYER_PROMPT)
					continue;
				if (player_is_observe(p1, g) ||
				    parray[p1].game == g)
					pprintf(p1, "Stalemate.\n");
			}
		} else {
			game_ended(g, CToggle(garray[g].game_state.onMove),
			    END_STALEMATE);
		}
	}

	if (result == MOVE_NOMATERIAL) {
		if (garray[g].status == GAME_EXAMINE) {
			for (int p1 = 0; p1 < p_num; p1++) {
				if (parray[p1].status != PLAYER_PROMPT)
					continue;
				if (player_is_observe(p1, g) ||
				    parray[p1].game == g)
					pprintf(p1, "No mating material.\n");
			}
		} else {
			game_ended(g, CToggle(garray[g].game_state.onMove),
			    END_NOMATERIAL);
		}
	}
}

PUBLIC int
com_resign(int p, param_list param)
{
	int	g, o, oconnected;

	if (param[0].type == TYPE_NULL) {
		g = parray[p].game;

		if (!pIsPlaying(p))
			return COM_OK;
		else {
			player_decline_offers(p, -1, -1);
			game_ended(g, (garray[g].white == p ? BLACK : WHITE),
			    END_RESIGN);
		}
	} else if (FindPlayer(p, param[0].val.word, &o, &oconnected)) {
		g = game_new();

		if (game_read(g, p, o) < 0) {
			if (game_read(g, o, p) < 0) {
				pprintf(p, "You have no stored game with %s\n",
				    parray[o].name);
				if (!oconnected)
					player_remove(o);
				return COM_OK;
			} else {
				garray[g].white = o;
				garray[g].black = p;
			}
		} else {
			garray[g].white = p;
			garray[g].black = o;
		}

		pprintf(p, "You resign your stored game with %s\n",
		    parray[o].name);

		game_delete(garray[g].white, garray[g].black);
		game_ended(g, (garray[g].white == p ? BLACK : WHITE),
		    END_RESIGN);

		pcommand(p, "message %s I have resigned our stored game "
		    "\"%s vs. %s.\"",
		    parray[o].name,
		    parray[garray[g].white].name,
		    parray[garray[g].black].name);

		if (!oconnected)
			player_remove(o);
	}

	return COM_OK;
}

PRIVATE int
Check50MoveRule(int p, int g)
{
	int	num_reversible = garray[g].numHalfMoves;

	if (garray[g].game_state.lastIrreversable >= 0)
		num_reversible -= garray[g].game_state.lastIrreversable;

	if (num_reversible > 99) {
		game_ended(g, (garray[g].white == p ? BLACK : WHITE),
		    END_50MOVERULE);
		return 1;
	}

	return 0;
}

PRIVATE char *
GetFENpos(int g, int half_move)
{
	if (half_move < 0)
		return ((char *)garray[g].FENstartPos);
	return ((char *)garray[g].moveList[half_move].FENpos);
}

PRIVATE int
CheckRepetition(int p, int g)
{
	char	*pos1 = GetFENpos(g, garray[g].numHalfMoves - 1);
	char	*pos2 = GetFENpos(g, garray[g].numHalfMoves);
	char	*pos;
	int	 flag1 = 1, flag2 = 1;
	int	 move_num;

	if (garray[g].numHalfMoves < 8) // Can't have three repeats any quicker.
		return 0;

	for (move_num = garray[g].game_state.lastIrreversable;
	    move_num < garray[g].numHalfMoves - 1;
	    move_num++) {
		pos = GetFENpos(g, move_num);

		if (strlen(pos1) == strlen(pos) && !strcmp(pos1, pos))
			flag1++;
		if (strlen(pos2) == strlen(pos) && !strcmp(pos2, pos))
			flag2++;
	}

	if (flag1 >= 3 || flag2 >= 3) {
		if (player_find_pendfrom(p, parray[p].opponent, PEND_DRAW)
		    >= 0) {
			player_remove_request(parray[p].opponent, p, PEND_DRAW);
			player_decline_offers(p, -1, -1);
		}

		game_ended(g, (garray[g].white == p ? BLACK : WHITE),
		    END_REPETITION);
		return 1;
	} else
		return 0;
}

PUBLIC int
com_draw(int p, param_list param)
{
	int p1, g = parray[p].game;

	ASSERT(param[0].type == TYPE_NULL);

	if (!pIsPlaying(p))
		return COM_OK;
	if (Check50MoveRule(p, g) || CheckRepetition(p, g))
		return COM_OK;

	p1 = parray[p].opponent;

	if (parray[p1].simul_info.numBoards &&
	    parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
		pprintf(p, "You can only make requests when the simul player "
		    "is at your board.\n");
		return COM_OK;
	}

	if (player_find_pendfrom(p, parray[p].opponent, PEND_DRAW) >= 0) {
		player_remove_request(parray[p].opponent, p, PEND_DRAW);
		player_decline_offers(p, -1, -1);
		game_ended(g, (garray[g].white == p ? BLACK : WHITE),
		    END_AGREEDDRAW);
	} else {
		pprintf(parray[p].opponent, "\n");
		pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
		pprintf_prompt(parray[p].opponent, " offers you a draw.\n");
		pprintf(p, "Draw request sent.\n");
		player_add_request(p, parray[p].opponent, PEND_DRAW, 0);
	}

	return COM_OK;
}

PUBLIC int
com_pause(int p, param_list param)
{
	int	g;
	int	now;

	ASSERT(param[0].type == TYPE_NULL);

	if (!pIsPlaying(p))
		return COM_OK;

	g = parray[p].game;

	if (garray[g].wTime == 0) {
		pprintf(p, "You can't pause untimed games.\n");
		return COM_OK;
	}

	if (garray[g].clockStopped) {
		pprintf(p, "Game is already paused, "
		    "use \"unpause\" to resume.\n");
		return COM_OK;
	}

	if (player_find_pendfrom(p, parray[p].opponent, PEND_PAUSE) >= 0) {
		player_remove_request(parray[p].opponent, p, PEND_PAUSE);
		garray[g].clockStopped = 1;

		// Roll back the time
		if (garray[g].game_state.onMove == WHITE) {
			garray[g].wTime += (garray[g].lastDecTime -
					    garray[g].lastMoveTime);
		} else {
			garray[g].bTime += (garray[g].lastDecTime -
					    garray[g].lastMoveTime);
		}

		now = tenth_secs();

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

		garray[g].lastMoveTime = now;
		garray[g].lastDecTime = now;

		send_boards(g);

		pprintf_prompt(parray[p].opponent, "\n%s accepted pause. "
		    "Game clock paused.\n", parray[p].name);
		pprintf(p, "Game clock paused.\n");
	} else {
		pprintf(parray[p].opponent, "\n");
		pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
		pprintf_prompt(parray[p].opponent, " requests to pause the "
		    "game.\n");
		pprintf(p, "Pause request sent.\n");
		player_add_request(p, parray[p].opponent, PEND_PAUSE, 0);
	}

	return COM_OK;
}

PUBLIC int
com_unpause(int p, param_list param)
{
	int	g;
	int	now;

	ASSERT(param[0].type == TYPE_NULL);

	if (!pIsPlaying(p))
		return COM_OK;

	g = parray[p].game;

	if (!garray[g].clockStopped) {
		pprintf(p, "Game is not paused.\n");
		return COM_OK;
	}

	garray[g].clockStopped = 0;
	now = tenth_secs();

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

	garray[g].lastMoveTime = now;
	garray[g].lastDecTime = now;

	send_boards(g);

	pprintf(p, "Game clock resumed.\n");
	pprintf_prompt(parray[p].opponent, "\nGame clock resumed.\n");

	return COM_OK;
}

PUBLIC int
com_abort(int p, param_list param)
{
	int courtesyOK = 1;
	int p1, g, myColor, yourColor, myGTime, yourGTime;

	ASSERT(param[0].type == TYPE_NULL);

	g = parray[p].game;

	if (!pIsPlaying(p))
		return COM_OK;

	p1 = parray[p].opponent;

	if (p == garray[g].white) {
		myColor		= WHITE;
		yourColor	= BLACK;
		myGTime		= garray[g].wTime;
		yourGTime	= garray[g].bTime;
	} else {
		myColor		= BLACK;
		yourColor	= WHITE;
		myGTime		= garray[g].bTime;
		yourGTime	= garray[g].wTime;
	}

	if (parray[p1].simul_info.numBoards &&
	    parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
		pprintf(p, "You can only make requests when the simul player "
		    "is at your board.\n");
		return COM_OK;
	}

	if (player_find_pendfrom(p, p1, PEND_ABORT) >= 0) {
		player_remove_request(p1, p, PEND_ABORT);
		player_decline_offers(p, -1, -1);
		game_ended(g, yourColor, END_ABORT);
	} else {
		game_update_time(g);

#ifdef TIMESEAL
		if (con[parray[p].socket].timeseal &&
		    garray[g].game_state.onMove == myColor &&
		    garray[g].flag_pending == FLAG_ABORT) {
			/*
			 * It's my move, opponent has asked for abort;
			 * I lagged out, my timeseal prevented
			 * courtesyabort, and I sent an abort request
			 * before acknowledging (and processing) my
			 * opponent's courtesyabort. OK, let's abort
			 * already :-).
			 */

			player_decline_offers(p, -1, -1);
			game_ended(g, yourColor, END_ABORT);
		}

		if (con[parray[p1].socket].timeseal) { // Opp uses timeseal?
			int yourRealTime = (myColor == WHITE ?
			    garray[g].bRealTime : garray[g].wRealTime);

			if (myGTime > 0 && yourGTime <= 0 && yourRealTime > 0) {
				/*
				 * Override courtesyabort; opponent
				 * still has time. Check for lag.
				 */
				courtesyOK = 0;

				if (garray[g].game_state.onMove != myColor &&
				    garray[g].flag_pending != FLAG_CHECKING) {
					// Opponent may be lagging; let's ask.
					garray[g].flag_pending = FLAG_ABORT;
					garray[g].flag_check_time = time(0);

					pprintf(p, "Opponent has timeseal; "
					    "trying to courtesyabort.\n");
					pprintf(p1, "\n[G]\n");

					return COM_OK;
				}
			}
		}
#endif

		if (myGTime > 0 && yourGTime <= 0 && courtesyOK) {
			/*
			 * Player wants to abort + opponent is out of
			 * time = courtesyabort.
			 */

			pprintf(p, "Since you have time, and your opponent "
			    "has none, the game has been aborted.");
			pprintf(p1, "Your opponent has aborted the game "
			    "rather than calling your flag.");
			player_decline_offers(p, -1, -1);
			game_ended(g, myColor, END_COURTESY);
		} else {
			pprintf(p1, "\n");
			pprintf_highlight(p1, "%s", parray[p].name);
			pprintf(p1, " would like to abort the game; ");
			pprintf_prompt(p1, "type \"abort\" to accept.\n");
			pprintf(p, "Abort request sent.\n");
			player_add_request(p, p1, PEND_ABORT, 0);
		}
	}

	return COM_OK;
}

PUBLIC int
com_courtesyabort(int p, param_list param)
{
	pprintf(p, "Courtesyabort is obsolete; use \"abort\" instead.\n");
	return COM_OK;
}

PUBLIC int
com_courtesyadjourn(int p, param_list param)
{
	pprintf(p, "Use \"adjourn\" to courtesyadjourn a game.\n");
	return COM_OK;
}

PRIVATE int
player_has_mating_material(game_state_t *gs, int color)
{
	int	i, j;
	int	minor_pieces = 0;
	int	piece;

	for (i = 0; i < 8; i++) {
		for (j = 0; j < 8; j++) {
			piece = gs->board[i][j];

			switch (piecetype(piece)) {
			case BISHOP:
			case KNIGHT:
				if (iscolor(piece, color))
					minor_pieces++;
				break;
			case KING:
			case NOPIECE:
				break;
			default:
				if (iscolor(piece, color))
					return 1;
			}
		}
	}

	return (minor_pieces > 1 ? 1 : 0);
}

PUBLIC int
com_flag(int p, param_list param)
{
	int	g;
	int	myColor;

	ASSERT(param[0].type == TYPE_NULL);

	if (!pIsPlaying(p))
		return COM_OK;

	g = parray[p].game;
	myColor = (p == garray[g].white ? WHITE : BLACK);

	if (garray[g].type == TYPE_UNTIMED) {
		pprintf(p, "You can't flag an untimed game.\n");
		return COM_OK;
	}

	if (garray[g].numHalfMoves < 2) {
		pprintf(p, "You cannot flag before both players have moved.\n"
		    "Use abort instead.\n");
		return COM_OK;
	}

	game_update_time(g);

#ifdef TIMESEAL
	{
		int	myTime, yourTime, serverTime;
		int	opp = parray[p].opponent;

		if (con[parray[p].socket].timeseal) {	// Does the caller use
							// timeseal?
			myTime = (myColor == WHITE ? garray[g].wRealTime :
			    garray[g].bRealTime);
		} else {
			myTime = (myColor == WHITE ? garray[g].wTime :
			    garray[g].bTime);
		}

		serverTime = (myColor == WHITE ? garray[g].bTime :
		    garray[g].wTime);

		if (con[parray[opp].socket].timeseal) { // Opp uses timeseal?
			yourTime = (myColor == WHITE ? garray[g].bRealTime :
			    garray[g].wRealTime);
		} else {
			yourTime = serverTime;
		}

		// The clocks to compare are now in 'myTime' and 'yourTime'.
		if (myTime <= 0 && yourTime <= 0) {
			player_decline_offers(p, -1, -1);
			game_ended(g, myColor, END_BOTHFLAG);
			return COM_OK;
		}

		if (yourTime > 0) {
			/*
			 * Opponent still has time, but if that's only
			 * because s/he may be lagging, we should ask
			 * for an acknowledgement and then try to call
			 * the flag.
			 */

			if (serverTime <= 0 &&
			    garray[g].game_state.onMove != myColor &&
			    garray[g].flag_pending != FLAG_CHECKING) {
				garray[g].flag_pending = FLAG_CALLED;
				garray[g].flag_check_time = time(0);

				pprintf(p, "Opponent has timeseal; "
				    "checking if (s)he's lagging.\n");
				pprintf(opp, "\n[G]\n");

				return COM_OK;
			}

			/*
			 * If we're here, it means:
			 * 1) The server agrees opponent has time, whether
			 *    lagging or not.
			 * 2) Opponent has timeseal (if yourTime != serverTime),
			 *    had time left after the last move (yourTime > 0),
			 *    and it's still your move.
			 * 3) We're currently checking a flag call after having
			 *    receiving acknowledgement from the other timeseal
			 *    (and would have reset 'yourTime' if the flag were
			 *    down).
			 */
			pprintf(p, "Your opponent is not out of time!\n");
			return COM_OK;
		}
	}
#else	// !defined(TIMESEAL)
	if (garray[g].wTime <= 0 && garray[g].bTime <= 0) {
		player_decline_offers(p, -1, -1);
		game_ended(g, myColor, END_BOTHFLAG);
		return COM_OK;
	}

	if (myColor == WHITE) {
		if (garray[g].bTime > 0) {
			pprintf(p, "Your opponent is not out of time!\n");
			return COM_OK;
		}
	} else {
		if (garray[g].wTime > 0) {
			pprintf(p, "Your opponent is not out of time!\n");
			return COM_OK;
		}
	}
#endif

	player_decline_offers(p, -1, -1);

	if (player_has_mating_material(&garray[g].game_state, myColor))
		game_ended(g, myColor, END_FLAG);
	else
		game_ended(g, myColor, END_FLAGNOMATERIAL);

	return COM_OK;
}

PUBLIC int
com_adjourn(int p, param_list param)
{
	int	p1, g, myColor, yourColor;

	ASSERT(param[0].type == TYPE_NULL);

	if (!pIsPlaying(p))
		return COM_OK;

	p1 = parray[p].opponent;
	g = parray[p].game;

	if (!(parray[p].registered && parray[p1].registered)) {
		pprintf(p, "Both players must be registered to adjorn a game. "
		    "Use \"abort\".\n");
		return COM_OK;
	}

	if (garray[g].link >= 0) {
		pprintf(p, "Bughouse games cannot be adjourned.\n");
		return COM_OK;
	}

	myColor		= (p == garray[g].white ? WHITE : BLACK);
	yourColor	= (myColor == WHITE ? BLACK : WHITE);

	if (player_find_pendfrom(p, p1, PEND_ADJOURN) >= 0) {
		player_remove_request(p1, p, PEND_ADJOURN);
		player_decline_offers(p, -1, -1);
		game_ended(parray[p].game, yourColor, END_ADJOURN);
	} else {
		game_update_time(g);

		if ((myColor == WHITE &&
		    garray[g].wTime > 0 &&
		    garray[g].bTime <= 0) ||
		    (myColor == BLACK &&
		    garray[g].bTime > 0 &&
		    garray[g].wTime <= 0)) {
			/*
			 * Player wants to adjourn + opponent is out
			 * of time = courtesyadjourn.
			 */

			pprintf(p, "Since you have time, and your opponent "
			    "has none, the game has been adjourned.");
			pprintf(p1, "Your opponent has adjourned the game "
			    "rather than calling your flag.");
			player_decline_offers(p, -1, -1);
			game_ended(g, myColor, END_COURTESYADJOURN);
		} else {
			pprintf(p1, "\n");
			pprintf_highlight(p1, "%s", parray[p].name);
			pprintf(p1, " would like to adjourn the game; ");
			pprintf_prompt(p1, "type \"adjourn\" to accept.\n");
			pprintf(p, "Adjourn request sent.\n");
			player_add_request(p, p1, PEND_ADJOURN, 0);
		}
	}

	return COM_OK;
}

PUBLIC int
com_takeback(int p, param_list param)
{
	int	from;
	int	g, i;
	int	nHalfMoves = 1;
	int	p1;

	if (!pIsPlaying(p))
		return COM_OK;

	p1 = parray[p].opponent;

	if (parray[p1].simul_info.numBoards &&
	    parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] !=
	    parray[p].game) {
		pprintf(p, "You can only make requests when the simul player "
		    "is at your board.\n");
		return COM_OK;
	}

	g = parray[p].game;

	if (garray[g].link >= 0) {
		pprintf(p, "Takeback not implemented for bughouse games yet."
		    "\n");
		return COM_OK;
	}

	if (param[0].type == TYPE_INT)
		nHalfMoves = param[0].val.integer;

	if ((from = player_find_pendfrom(p, parray[p].opponent, PEND_TAKEBACK))
	    >= 0) {
		player_remove_request(parray[p].opponent, p, PEND_TAKEBACK);

		if (parray[p].p_from_list[from].param1 == nHalfMoves) {
			// Doing the takeback
			player_decline_offers(p, -1, -PEND_SIMUL);

			for (i = 0; i < nHalfMoves; i++) {
				if (backup_move(g, REL_GAME) != MOVE_OK) {
					pprintf(garray[g].white, "Can only "
					    "backup %d moves\n", i);
					pprintf(garray[g].black, "Can only "
					    "backup %d moves\n", i);
					break;
				}
			}

#ifdef TIMESEAL
			garray[g].wTimeWhenReceivedMove = 0;
			garray[g].bTimeWhenReceivedMove = 0;
#endif

			send_boards(g);
		} else {
			if (garray[g].numHalfMoves < nHalfMoves) {
				pprintf(p, "There are only %d half moves in "
				    "your game.\n", garray[g].numHalfMoves);
				pprintf_prompt(parray[p].opponent, "\n%s has "
				    "declined the takeback request.\n",
				    parray[p].name);
				return COM_OK;
			}

			pprintf(p, "You disagree on the number of half-moves "
			    "to takeback.\n");
			pprintf(p, "Alternate takeback request sent.\n");
			pprintf_prompt(parray[p].opponent, "\n%s proposes a "
			    "different number (%d) of half-move(s).\n",
			    parray[p].name,
			    nHalfMoves);
			player_add_request(p, parray[p].opponent, PEND_TAKEBACK,
			    nHalfMoves);
		}
	} else {
		if (garray[g].numHalfMoves < nHalfMoves) {
			pprintf(p, "There are only %d half moves in your game."
			    "\n", garray[g].numHalfMoves);
			return COM_OK;
		}

		pprintf(parray[p].opponent, "\n");
		pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
		pprintf_prompt(parray[p].opponent, " would like to take back "
		    "%d half move(s).\n", nHalfMoves);
		pprintf(p, "Takeback request sent.\n");
		player_add_request(p, parray[p].opponent, PEND_TAKEBACK,
		    nHalfMoves);
	}

	return COM_OK;
}

PUBLIC int
com_switch(int p, param_list param)
{
	char	*strTmp;
	int	 g = parray[p].game;
	int	 p1;
	int	 tmp, now;

	if (!pIsPlaying(p))
		return COM_OK;

	p1 = parray[p].opponent;

	if (parray[p1].simul_info.numBoards &&
	    parray[p1].simul_info.boards[parray[p1].simul_info.onBoard] != g) {
		pprintf(p, "You can only make requests when the simul player "
		    "is at your board.\n");
		return COM_OK;
	}

	if (garray[g].link >= 0) {
		pprintf(p, "Switch not implemented for bughouse games.\n");
		return COM_OK;
	}

	if (player_find_pendfrom(p, parray[p].opponent, PEND_SWITCH) >= 0) {
		player_remove_request(parray[p].opponent, p, PEND_SWITCH);
		player_decline_offers(p, -1, -PEND_SIMUL);

		tmp = garray[g].white;
		garray[g].white = garray[g].black;
		garray[g].black = tmp;
		parray[p].side = (parray[p].side == WHITE ? BLACK : WHITE);

		strTmp = xstrdup(garray[g].white_name);
		mstrlcpy(garray[g].white_name, garray[g].black_name,
		    sizeof(garray[g].white_name));
		mstrlcpy(garray[g].black_name, strTmp,
		    sizeof(garray[g].black_name));
		strfree(strTmp);

		parray[parray[p].opponent].side =
		    (parray[parray[p].opponent].side == WHITE ? BLACK : WHITE);

		// Roll back the time
		if (garray[g].game_state.onMove == WHITE) {
			garray[g].wTime += (garray[g].lastDecTime -
					    garray[g].lastMoveTime);
		} else {
			garray[g].bTime += (garray[g].lastDecTime -
					    garray[g].lastMoveTime);
		}

		now = tenth_secs();

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

		garray[g].lastMoveTime = now;
		garray[g].lastDecTime = now;

		send_boards(g);
		return COM_OK;
	}

	if (garray[g].rated && garray[g].numHalfMoves > 0) {
		pprintf(p, "You cannot switch sides once a rated game is "
		    "underway.\n");
		return COM_OK;
	}

	pprintf(parray[p].opponent, "\n");
	pprintf_highlight(parray[p].opponent, "%s", parray[p].name);
	pprintf_prompt(parray[p].opponent, " would like to switch sides.\n"
	    "Type \"accept\" to switch sides, or \"decline\" to refuse.\n");
	pprintf(p, "Switch request sent.\n");
	player_add_request(p, parray[p].opponent, PEND_SWITCH, 0);

	return COM_OK;
}

PUBLIC int
com_time(int p, param_list param)
{
	int	p1, g;

	if (param[0].type == TYPE_NULL) {
		g = parray[p].game;

		if (!pIsPlaying(p))
			return COM_OK;
	} else {
		if ((g = GameNumFromParam(p, &p1, &param[0])) < 0)
			return COM_OK;
	}

	if (g < 0 || g >= g_num || garray[g].status != GAME_ACTIVE) {
		pprintf(p, "There is no such game.\n");
		return COM_OK;
	}

	game_update_time(g);

	pprintf(p, "White (%s) : %d mins, %d secs\n",
		parray[garray[g].white].name,
		garray[g].wTime / 600,
		(garray[g].wTime - ((garray[g].wTime / 600) * 600)) / 10);
	pprintf(p, "Black (%s) : %d mins, %d secs\n",
		parray[garray[g].black].name,
		garray[g].bTime / 600,
		(garray[g].bTime - ((garray[g].bTime / 600) * 600)) / 10);

	return COM_OK;
}

PUBLIC int
com_boards(int p, param_list param)
{
	DIR		*dirp;
	char		*category = NULL;
	char		 dname[MAX_FILENAME_SIZE] = { '\0' };
#ifdef USE_DIRENT
	struct dirent	*dp;
#else
	struct direct	*dp;
#endif

	if (param[0].type == TYPE_WORD)
		category = param[0].val.word;

	if (category) {
		pprintf(p, "Boards Available For Category %s:\n", category);
		msnprintf(dname, sizeof dname, "%s/%s", board_dir, category);
	} else {
		pprintf(p, "Categories Available:\n");
		msnprintf(dname, sizeof dname, "%s", board_dir);
	}

	if ((dirp = opendir(dname)) == NULL) {
		pprintf(p, "No such category %s, try \"boards\".\n", category);
		return COM_OK;
	}

	// YUK! What a mess, how about printing an ordered directory? - DAV
	for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
		if (!strcmp(dp->d_name, "."))
			continue;
		if (!strcmp(dp->d_name, ".."))
			continue;
		pprintf(p, "%s\n", dp->d_name);
	}

	closedir(dirp);
	return COM_OK;
}

PUBLIC int
com_simmatch(int p, param_list param)
{
	char	tmp[100] = { '\0' };
	int	num;
	int	p1, g, adjourned;

	if (parray[p].game >= 0 && garray[parray[p].game].status ==
	    GAME_EXAMINE) {
		pprintf(p, "You are still examining a game.\n");
		return COM_OK;
	}

	p1 = player_find_part_login(param[0].val.word);

	if (p1 < 0) {
		pprintf(p, "No user named \"%s\" is logged in.\n",
		    param[0].val.word);
		return COM_OK;
	}

	if (p == p1) {
		pprintf(p, "You can't simmatch yourself!\n");
		return COM_OK;
	}

	if (player_find_pendfrom(p, p1, PEND_SIMUL) >= 0) {
		player_remove_request(p, p1, PEND_MATCH);
		player_remove_request(p1, p, PEND_MATCH);
		player_remove_request(p, p1, PEND_SIMUL);
		player_remove_request(p1, p, PEND_SIMUL);
		player_withdraw_offers(p, -1, PEND_SIMUL);
		player_decline_offers(p1, -1, PEND_SIMUL);
		player_withdraw_offers(p1, -1, PEND_SIMUL);
		player_decline_offers(p, -1, PEND_MATCH);
		player_withdraw_offers(p, -1, PEND_MATCH);
		player_decline_offers(p1, -1, PEND_MATCH);
		player_withdraw_offers(p1, -1, PEND_MATCH);
		player_decline_offers(p, -1, PEND_PARTNER);
		player_withdraw_offers(p, -1, PEND_PARTNER);
		player_decline_offers(p1, -1, PEND_PARTNER);
		player_withdraw_offers(p1, -1, PEND_PARTNER);

		if (parray[p].simul_info.numBoards >= MAX_SIMUL) {
			pprintf(p, "You are already playing the maximum of %d "
			    "boards.\n", MAX_SIMUL);
			pprintf(p1, "Simul request removed, boards filled.\n");
			return COM_OK;
		}

		// stop observing when match starts
		unobserveAll(p);
		unobserveAll(p1);

		g = game_new();
		adjourned = 0;

		if (game_read(g, p, p1) >= 0)
			adjourned = 1;

		if (!adjourned) { // no adjourned game - so begin a new game.
			game_remove(g);

			if (create_new_match(p, p1, 0, 0, 0, 0, 0, "standard",
			    "standard", 1)) {
				pprintf(p, "There was a problem creating the "
				    "new match.\n");
				pprintf_prompt(p1, "There was a problem "
				    "creating the new match.\n");
				return COM_OK;
			}
		} else { // resume adjourned game
			game_delete(p, p1);

			msnprintf(tmp, sizeof tmp, "{Game %d (%s vs. %s) "
			    "Continuing %s %s simul.}\n",
			    (g + 1),
			    parray[p].name,
			    parray[p1].name,
			    rstr[garray[g].rated],
			    bstr[garray[g].type]);

			pprintf(p, "%s", tmp);
			pprintf(p1, "%s", tmp);

			garray[g].white = p;
			garray[g].black = p1;
			garray[g].status = GAME_ACTIVE;
			garray[g].startTime = tenth_secs();
			garray[g].lastMoveTime = garray[g].startTime;
			garray[g].lastDecTime = garray[g].startTime;
			parray[p].game = g;
			parray[p].opponent = p1;
			parray[p].side = WHITE;
			parray[p1].game = g;
			parray[p1].opponent = p;
			parray[p1].side = BLACK;

			send_boards(g);
		}

		num = parray[p].simul_info.numBoards;

		parray[p].simul_info.results[num] = -1;
		parray[p].simul_info.boards[num] = parray[p].game;
		parray[p].simul_info.numBoards++;

		if (parray[p].simul_info.numBoards > 1 &&
		    parray[p].simul_info.onBoard >= 0)
			player_goto_board(p, parray[p].simul_info.onBoard);
		else
			parray[p].simul_info.onBoard = 0;
		return COM_OK;
	}

	if (player_find_pendfrom(p, -1, PEND_SIMUL) >= 0) {
		pprintf(p, "You cannot be the simul giver and request to join "
		    "another simul.\nThat would just be too confusing for me "
		    "and you.\n");
		return COM_OK;
	}

	if (parray[p].simul_info.numBoards) {
		pprintf(p, "You cannot be the simul giver and request to join "
		    "another simul.\nThat would just be too confusing for me "
		    "and you.\n");
		return COM_OK;
	}

	if (parray[p].game >= 0) {
		pprintf(p, "You are already playing a game.\n");
		return COM_OK;
	}

	if (!parray[p1].sopen) {
		pprintf_highlight(p, "%s", parray[p1].name);
		pprintf(p, " is not open to receiving simul requests.\n");
		return COM_OK;
	}

	if (parray[p1].simul_info.numBoards >= MAX_SIMUL) {
		pprintf_highlight(p, "%s", parray[p1].name);
		pprintf(p, " is already playing the maximum of %d boards.\n",
		    MAX_SIMUL);
		return COM_OK;
	}

	// loon: checking for some crazy situations we can't allow :)
	if (parray[p1].game >= 0 && parray[p1].simul_info.numBoards == 0) {
		pprintf_highlight(p, "%s", parray[p1].name);

		if (parray[garray[parray[p1].game].white].simul_info.numBoards) {
			pprintf(p, " is playing in ");
			pprintf_highlight(p, "%s",
			    parray[parray[p1].opponent].name);
			pprintf(p, "'s simul, and can't accept.\n");
		} else {
			pprintf(p, " can't begin a simul while playing a "
			    "non-simul game.\n");
		}

		return COM_OK;
	}

	g = game_new();
	adjourned = ((game_read(g, p, p1) < 0 && game_read(g, p1, p) < 0)
	    ? 0 : 1);

	if (adjourned) {
		if (!(garray[g].type == TYPE_UNTIMED))
			adjourned = 0;
	}

	game_remove(g);

	if (player_add_request(p, p1, PEND_SIMUL, 0)) {
		pprintf(p, "Maximum number of pending actions reached. "
		    "Your request was not sent.\nTry again later.\n");
		return COM_OK;
	} else {

		pprintf(p1, "\n");
		pprintf_highlight(p1, "%s", parray[p].name);

		if (adjourned) {
			pprintf_prompt(p1, " requests to continue an "
			    "adjourned simul game.\n");
			pprintf(p, "Request to resume simul sent. "
			    "Adjourned game found.\n");
		} else {
			pprintf_prompt(p1, " requests to join a simul match "
			    "with you.\n");
			pprintf(p, "Simul match request sent.\n");
		}
	}

	return COM_OK;
}

PUBLIC int
com_goboard(int p, param_list param)
{
	int on, g, p1;

	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	p1 = player_find_part_login(param[0].val.word);

	if (p1 < 0) {
		pprintf(p, "No user named \"%s\" is logged in.\n",
		    param[0].val.word);
		return COM_OK;
	}

	if (p == p1) {
		pprintf(p, "You can't goboard yourself!\n");
		return COM_OK;
	}

	on = parray[p].simul_info.onBoard;
	g = parray[p].simul_info.boards[on];

	if (p1 == garray[g].black) {
		pprintf(p, "You are already at that board!\n");
		return COM_OK;
	}

	if (parray[p].simul_info.numBoards > 1) {
		player_decline_offers(p, -1, -PEND_SIMUL);

		if (player_goto_simulgame_bynum(p, parray[p1].game) != -1) {
			if (g >= 0) {
				pprintf(garray[g].black, "\n");
				pprintf_highlight(garray[g].black, "%s",
				    parray[p].name);
				pprintf_prompt(garray[g].black, " has moved "
				    "away from your board.\n");
			}
		}
	} else
		pprintf(p, "You are only playing one board!\n");
	return COM_OK;
}

PUBLIC int
com_gonum(int p, param_list param)
{
	int on, g, gamenum;

	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	on = parray[p].simul_info.onBoard;
	g = parray[p].simul_info.boards[on];
	gamenum = param[0].val.integer - 1;

	if (gamenum < 0)
		gamenum = 0;

	if (on == gamenum) {
		pprintf(p, "You are already at that board!\n");
		return COM_OK;
	}

	if (parray[p].simul_info.numBoards > 1) {
		player_decline_offers(p, -1, -PEND_SIMUL);

		if (player_goto_simulgame_bynum(p, gamenum) != -1) {
			if (g >= 0) {
				pprintf(garray[g].black, "\n");
				pprintf_highlight(garray[g].black, "%s",
				    parray[p].name);
				pprintf_prompt(garray[g].black, " has moved "
				    "away from your board.\n");
			}
		}
	} else
		pprintf(p, "You are only playing one board!\n");
	return COM_OK;
}

PUBLIC int
com_simnext(int p, param_list param)
{
	int on, g;

	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	if (parray[p].simul_info.numBoards > 1) {
		player_decline_offers(p, -1, -PEND_SIMUL);
		on = parray[p].simul_info.onBoard;
		g = parray[p].simul_info.boards[on];

		if (g >= 0) {
			pprintf(garray[g].black, "\n");
			pprintf_highlight(garray[g].black, "%s",
			    parray[p].name);
			pprintf_prompt(garray[g].black, " is moving away from "
			    "your board.\n");
			player_goto_next_board(p);
		}
	} else
		pprintf(p, "You are only playing one board!\n");
	return COM_OK;
}

PUBLIC int
com_simprev(int p, param_list param)
{
	int on, g;

	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	if (parray[p].simul_info.numBoards > 1) {
		player_decline_offers(p, -1, -PEND_SIMUL);
		on = parray[p].simul_info.onBoard;
		g = parray[p].simul_info.boards[on];

		if (g >= 0) {
			pprintf(garray[g].black, "\n");
			pprintf_highlight(garray[g].black, "%s",
			    parray[p].name);
			pprintf_prompt(garray[g].black, " is moving back to "
			    "the previous board.\n");
		}

		player_goto_prev_board(p);
	} else
		pprintf(p, "You are only playing one board!\n");
	return COM_OK;
}

PUBLIC int
com_simgames(int p, param_list param)
{
	int p1 = p;

	if (param[0].type == TYPE_WORD) {
		if ((p1 = player_find_part_login(param[0].val.word)) < 0) {
			pprintf(p, "No player named %s is logged in.\n",
			    param[0].val.word);
			return COM_OK;
		}
	}

	if (p1 == p) {
		pprintf(p, "You are playing %d simultaneous games.\n",
		    player_num_active_boards(p1));
	} else {
		pprintf(p, "%s is playing %d simultaneous games.\n",
		    parray[p1].name, player_num_active_boards(p1));
	}

	return COM_OK;
}

PUBLIC int
com_simpass(int p, param_list param)
{
	int	g, p1, on;

	if (!pIsPlaying(p))
		return COM_OK;

	g	= parray[p].game;
	p1	= garray[g].white;

	if (!parray[p1].simul_info.numBoards) {
		pprintf(p, "You are not participating in a simul.\n");
		return COM_OK;
	}

	if (p == p1) {
		pprintf(p, "You are the simul holder and cannot pass!\n");
		return COM_OK;
	}

	if (player_num_active_boards(p1) == 1) {
		pprintf(p, "This is the only game, so passing is futile.\n");
		return COM_OK;
	}

	on = parray[p1].simul_info.onBoard;

	if (parray[p1].simul_info.boards[on] != g) {
		pprintf(p, "You cannot pass until the simul holder arrives!\n");
		return COM_OK;
	}

	if (garray[g].passes >= MAX_SIMPASS) {
		if (parray[p].bell)
			pprintf(p, "\a");
		pprintf(p, "You have reached your maximum of %d pass(es).\n",
		    MAX_SIMPASS);
		pprintf(p, "Please move IMMEDIATELY!\n");
		pprintf_highlight(p1, "%s", parray[p].name);
		pprintf_prompt(p1, " tried to pass, but is out of passes.\n");
		return COM_OK;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);
	garray[g].passes++;

	pprintf(p, "You have passed and have %d pass(es) left.\n",
	    (MAX_SIMPASS - garray[g].passes));
	pprintf_highlight(p1, "%s", parray[p].name);
	pprintf_prompt(p1, " has decided to pass and has %d pass(es) left.\n",
	    (MAX_SIMPASS - garray[g].passes));
	player_goto_next_board(p1);

	return COM_OK;
}

PUBLIC int
com_simabort(int p, param_list param)
{
	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);
	game_ended(parray[p].simul_info.boards[parray[p].simul_info.onBoard],
	    WHITE, END_ABORT);

	return COM_OK;
}

PUBLIC int
com_simallabort(int p, param_list param)
{
	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);

	for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
		if (parray[p].simul_info.boards[i] >= 0) {
			game_ended(parray[p].simul_info.boards[i], WHITE,
			    END_ABORT);
		}
	}

	return COM_OK;
}

PUBLIC int
com_simadjourn(int p, param_list param)
{
	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);
	game_ended(parray[p].simul_info.boards[parray[p].simul_info.onBoard],
	    WHITE, END_ADJOURN);

	return COM_OK;
}

PUBLIC int
com_simalladjourn(int p, param_list param)
{
	if (!parray[p].simul_info.numBoards) {
		pprintf(p, "You are not giving a simul.\n");
		return COM_OK;
	}

	player_decline_offers(p, -1, -PEND_SIMUL);

	for (int i = 0; i < parray[p].simul_info.numBoards; i++) {
		if (parray[p].simul_info.boards[i] >= 0) {
			game_ended(parray[p].simul_info.boards[i], WHITE,
			    END_ADJOURN);
		}
	}

	return COM_OK;
}

PUBLIC int
com_moretime(int p, param_list param)
{
	int	g, increment;

	ASSERT(param[0].type == TYPE_INT);

	if (parray[p].game >= 0 && garray[parray[p].game].status ==
	    GAME_EXAMINE) {
		pprintf(p, "You cannot use moretime in an examined game.\n");
		return COM_OK;
	}

	if ((increment = param[0].val.integer) <= 0) {
		pprintf(p, "Moretime requires an integer value greater than "
		    "zero.\n");
		return COM_OK;
	}

	if (!pIsPlaying(p))
		return COM_OK;

	if (increment > 600) {
		pprintf(p, "Moretime has a maximum limit of 600 seconds.\n");
		increment = 600;
	}

	g = parray[p].game;

	if (garray[g].white == p) {
		garray[g].bTime += increment * 10;
#ifdef TIMESEAL
		garray[g].bRealTime += increment * 10 * 100;
#endif
		pprintf(p, "%d seconds were added to your opponents clock\n",
		    increment);
		pprintf_prompt(parray[p].opponent, "\nYour opponent has "
		    "added %d seconds to your clock.\n", increment);
	}

	if (garray[g].black == p) {
		garray[g].wTime += increment * 10;;
#ifdef TIMESEAL
		garray[g].wRealTime += increment * 10 * 100;
#endif
		pprintf(p, "%d seconds were added to your opponents clock\n",
		    increment);
		pprintf_prompt(parray[p].opponent, "\nYour opponent has "
		    "added %d seconds to your clock.\n", increment);
	}

	return COM_OK;
}