/* board.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/17	Fixed compiler warnings
   Markus Uhlin                 23/12/23	Reformatted all functions
   Markus Uhlin                 24/04/13	Added usage of the functions
						from 'maxxes-utils.h'.
   Markus Uhlin                 24/06/01	Added and made use of brand().
*/

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

#include <err.h>
#include <limits.h>

#include "board.h"
#include "gamedb.h"
#include "maxxes-utils.h"
#include "playerdb.h"
#include "utils.h"

#define WHITE_SQUARE	1
#define BLACK_SQUARE	0
#define ANY_SQUARE	-1

#define SquareColor(f, r) ((f ^ r) & 1)

#define IsMachineStyle(n) (((1 << (n)) & mach_type) != 0)

PUBLIC char	*wpstring[] = {" ", "P", "N", "B", "R", "Q", "K"};
PUBLIC char	*bpstring[] = {" ", "p", "n", "b", "r", "q", "k"};

PUBLIC int pieceValues[7] = {0, 1, 3, 3, 5, 9, 0};

PRIVATE int (*styleFuncs[MAX_STYLES])(game_state_t *, move_t *) = {
	style1,
	style2,
	style3,
	style4,
	style5,
	style6,
	style7,
	style8,
	style9,
	style10,
	style11,
	style12,
	style13
};

PRIVATE char bstring[MAX_BOARD_STRING_LEGTH];

PRIVATE const int mach_type = ((1 << 7) |
    (1 << 8) |
    (1 << 9) |
    (1 << 10) |
    (1 << 11));

/*
 * Globals used for each board
 */
PRIVATE char	*wName, *bName;
PRIVATE int	 wTime, bTime;
PRIVATE int	 orient;
PRIVATE int	 forPlayer;
PRIVATE int	 myTurn;

PRIVATE int
brand(void)
{
#if RAND_MAX < 32767 || RAND_MAX > INT_MAX
#error RAND_MAX unacceptable
#endif
	return ((int) arc4random_uniform(RAND_MAX));
}

PUBLIC int
board_init(game_state_t *b, char *category, char *board)
{
	int	retval;
	int	wval;

	if (category == NULL || board == NULL ||
	    !category[0] || !board[0]) {
		retval = board_read_file("standard", "standard", b);
	} else {
		if (!strcmp(category, "wild")) {
			if (sscanf(board, "%d", &wval) == 1 &&
			    wval >= 1 &&
			    wval <= 4)
				wild_update(wval);
		}

		retval = board_read_file(category, board, b);
	}

	b->gameNum = -1;
	return retval;
}

PUBLIC void
board_calc_strength(game_state_t *b, int *ws, int *bs)
{
	int	*p;
	int	 r, f;

	*ws = *bs = 0;

	for (f = 0; f < 8; f++) {
		for (r = 0; r < 8; r++) {
			if (colorval(b->board[r][f]) == WHITE)
				p = ws;
			else
				p = bs;
			*p += pieceValues[piecetype(b->board[r][f])];
		}
	}

	for (r = PAWN; r <= QUEEN; r++) {
		*ws += (b->holding[0][r - 1] * pieceValues[r]);
		*bs += (b->holding[1][r - 1] * pieceValues[r]);
	}
}

PRIVATE char *
holding_str(int *holding)
{
	int p, i, j;
	static char tmp[30];

	i = 0;

	for (p = PAWN; p <= QUEEN; p++) {
		for (j = 0; j < holding[p - 1]; j++)
			tmp[i++] = wpstring[p][0];
	}

	tmp[i] = '\0';
	return tmp;
}

PRIVATE char *
append_holding_machine(char *buf, const size_t bufsize, int g, int c, int p)
{
	char		 tmp[50];
	game_state_t	*gs = &garray[g].game_state;

	msnprintf(tmp, sizeof tmp, "<b1> game %d white [%s] black [", (g + 1),
	    holding_str(gs->holding[0]));
	mstrlcat(tmp, holding_str(gs->holding[1]), sizeof tmp);
	mstrlcat(buf, tmp, bufsize);

	if (p) {
		msnprintf(tmp, sizeof tmp, "] <- %c%s\n", "WB"[c], wpstring[p]);
		mstrlcat(buf, tmp, bufsize);
	} else
		mstrlcat(buf, "]\n", bufsize);
	return buf;
}

PRIVATE char *
append_holding_display(char *buf, const size_t bufsize, game_state_t *gs,
    int white)
{
	if (white)
		mstrlcat(buf, "White holding: [", bufsize);
	else
		mstrlcat(buf, "Black holding: [", bufsize);
	mstrlcat(buf, holding_str(gs->holding[white ? 0 : 1]), bufsize);
	mstrlcat(buf, "]\n", bufsize);
	return buf;
}

PUBLIC void
update_holding(int g, int pieceCaptured)
{
	char		 tmp1[80];
	char		 tmp2[80];
	game_state_t	*gs = &garray[g].game_state;
	int		 c = colorval(pieceCaptured);
	int		 p = piecetype(pieceCaptured);
	int		 pp, pl;

	if (c == WHITE) {
		c = 0;
		pp = garray[g].white;
	} else {
		c = 1;
		pp = garray[g].black;
	}

	gs->holding[c][p - 1]++;

	tmp1[0] = '\0';
	append_holding_machine(tmp1, sizeof tmp1, g, c, p);

	msnprintf(tmp2, sizeof tmp2, "Game %d %s received: %s -> [%s]\n",
	    (g + 1),
	    parray[pp].name,
	    wpstring[p],
	    holding_str(gs->holding[c]));

	for (pl = 0; pl < p_num; pl++) {
		if (parray[pl].status == PLAYER_EMPTY)
			continue;

		if (player_is_observe(pl, g) || parray[pl].game == g) {
			pprintf_prompt(pl, "%s",
			    (IsMachineStyle(parray[pl].style) ? &tmp1[0] :
			    &tmp2[0]));
		}
	}
}

PUBLIC char *
board_to_string(char *wn, char *bn, int wt, int bt, game_state_t *b, move_t *ml,
    int style, int orientation, int relation, int p)
{
	int bh = (b->gameNum >= 0 && garray[b->gameNum].link >= 0);

	wName		= wn;
	bName		= bn;
	wTime		= wt;
	bTime		= bt;
	orient		= orientation;
	myTurn		= relation;
	forPlayer	= p;

	if (style < 0 || style >= MAX_STYLES)
		return NULL;

	if (style != 11) {    // game header
		msnprintf(bstring, sizeof bstring, "Game %d (%s vs. %s)\n\n",
		    (b->gameNum + 1),
		    garray[b->gameNum].white_name,
		    garray[b->gameNum].black_name);
	} else
		bstring[0] = '\0';

	if (bh && !IsMachineStyle(style)) {
		append_holding_display(bstring, sizeof bstring, b,
		    (orientation == BLACK));
	}
	if (styleFuncs[style](b, ml))
		return NULL;
	if (bh) {
		if (IsMachineStyle(style)) {
			append_holding_machine(bstring, sizeof bstring,
			    b->gameNum, 0, 0);
		} else {
			append_holding_display(bstring, sizeof bstring, b,
			    (orientation == WHITE));
		}
	}

	return bstring;
}

PUBLIC char *
move_and_time(move_t *m)
{
	static char tmp[20];

	msnprintf(tmp, sizeof tmp, "%-7s (%s)", m->algString,
	    tenth_str(m->tookTime, 0));
	return &tmp[0];
}

PRIVATE int
genstyle(game_state_t *b, move_t *ml, char *wp[], char *bp[],
    char *wsqr, char *bsqr, char *top, char *mid, char *start, char *end,
    char *label, char *blabel)
{
	char	tmp[80];
	int	f, r, count;
	int	first, last, inc;
	int	ws, bs;

	board_calc_strength(b, &ws, &bs);

	if (orient == WHITE) {
		first	= 7;
		last	= 0;
		inc	= -1;
	} else {
		first	= 0;
		last	= 7;
		inc	= 1;
	}

	mstrlcat(bstring, top, sizeof bstring);

	for (f = first, count = 7; f != last + inc; f += inc, count--) {
		msnprintf(tmp, sizeof tmp, "     %d  %s", f + 1, start);
		mstrlcat(bstring, tmp, sizeof bstring);

		for (r = last; r != first - inc; r = r - inc) {
			if (square_color(r, f) == WHITE)
				mstrlcat(bstring, wsqr, sizeof bstring);
			else
				mstrlcat(bstring, bsqr, sizeof bstring);

			if (piecetype(b->board[r][f]) == NOPIECE) {
				if (square_color(r, f) == WHITE) {
					mstrlcat(bstring, bp[0],
					    sizeof bstring);
				} else {
					mstrlcat(bstring, wp[0],
					    sizeof bstring);
				}
			} else {
				if (colorval(b->board[r][f]) == WHITE) {
					mstrlcat(bstring,
					    wp[piecetype(b->board[r][f])],
					    sizeof bstring);
				} else {
					mstrlcat(bstring,
					    bp[piecetype(b->board[r][f])],
					    sizeof bstring);
				}
			}
		}

		msnprintf(tmp, sizeof tmp, "%s", end);
		mstrlcat(bstring, tmp, sizeof bstring);

		switch (count) {
		case 7:
			msnprintf(tmp, sizeof tmp, "     Move # : %d (%s)",
			    b->moveNum,
			    CString(b->onMove));
			mstrlcat(bstring, tmp, sizeof bstring);
			break;
		case 6:
			if (garray[b->gameNum].numHalfMoves > 0) {
				// loon: think this fixes the crashing ascii
				// board on takeback bug
				msnprintf(tmp, sizeof tmp, "     %s Moves : "
				    "'%s'",
				    CString(CToggle(b->onMove)), move_and_time
				    (&ml[garray[b->gameNum].numHalfMoves - 1]));
				mstrlcat(bstring, tmp, sizeof bstring);
			}
			break;
		case 5:
			break;
		case 4:
			msnprintf(tmp, sizeof tmp, "     Black Clock : %s",
			    tenth_str((bTime > 0 ? bTime : 0), 1));
			mstrlcat(bstring, tmp, sizeof bstring);
			break;
		case 3:
			msnprintf(tmp, sizeof tmp, "     White Clock : %s",
			    tenth_str((wTime > 0 ? wTime : 0), 1));
			mstrlcat(bstring, tmp, sizeof bstring);
			break;
		case 2:
			msnprintf(tmp, sizeof tmp, "     Black Strength : %d",
			    bs);
			mstrlcat(bstring, tmp, sizeof bstring);
			break;
		case 1:
			msnprintf(tmp, sizeof tmp, "     White Strength : %d",
			    ws);
			mstrlcat(bstring, tmp, sizeof bstring);
			break;
		case 0:
			break;
		} // switch

		mstrlcat(bstring, "\n", sizeof bstring);

		if (count != 0)
			mstrlcat(bstring, mid, sizeof bstring);
		else
			mstrlcat(bstring, top, sizeof bstring);
	} // for

	if (orient == WHITE)
		mstrlcat(bstring, label, sizeof bstring);
	else
		mstrlcat(bstring, blabel, sizeof bstring);
	return 0;
}

/*
 * Experimental ANSI board for colour representation
 */
PUBLIC int
style13(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"   ",
		"\033[37m\033[1m P ",
		"\033[37m\033[1m N ",
		"\033[37m\033[1m B ",
		"\033[37m\033[1m R ",
		"\033[37m\033[1m Q ",
		"\033[37m\033[1m K "
	};
	static char	*bp[] = {
		"   ",
		"\033[21m\033[37m P ",
		"\033[21m\033[37m N ",
		"\033[21m\033[37m B ",
		"\033[21m\033[37m R ",
		"\033[21m\033[37m Q ",
		"\033[21m\033[37m K "
	};
	static char	*wsqr = "\033[40m";
	static char	*bsqr = "\033[45m";
	static char	*top = "\t+------------------------+\n";
	static char	*mid = "";
	static char	*start = "|";
	static char	*end = "\033[0m|";
	static char	*label = "\t  a  b  c  d  e  f  g  h\n";
	static char	*blabel = "\t  h  g  f  e  d  c  b  a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Standard ICS
 */
PUBLIC int
style1(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"   |",
		" P |",
		" N |",
		" B |",
		" R |",
		" Q |",
		" K |"
	};
	static char	*bp[] = {
		"   |",
		" *P|",
		" *N|",
		" *B|",
		" *R|",
		" *Q|",
		" *K|"
	};
	static char	*wsqr = "";
	static char	*bsqr = "";
	static char	*top = "\t---------------------------------\n";
	static char	*mid = "\t|---+---+---+---+---+---+---+---|\n";
	static char	*start = "|";
	static char	*end = "";
	static char	*label = "\t  a   b   c   d   e   f   g   h\n";
	static char	*blabel = "\t  h   g   f   e   d   c   b   a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * USA-Today Sports Center-style board
 */
PUBLIC int
style2(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"+  ",
		"P  ",
		"N  ",
		"B  ",
		"R  ",
		"Q  ",
		"K  "
	};
	static char	*bp[] = {
		"-  ",
		"p' ",
		"n' ",
		"b' ",
		"r' ",
		"q' ",
		"k' "
	};
	static char	*wsqr = "";
	static char	*bsqr = "";
	static char	*top = "";
	static char	*mid = "";
	static char	*start = "";
	static char	*end = "";
	static char	*label = "\ta  b  c  d  e  f  g  h\n";
	static char	*blabel = "\th  g  f  e  d  c  b  a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Experimental vt-100 ANSI board for dark backgrounds
 */
PUBLIC int
style3(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"   ",
		" P ",
		" N ",
		" B ",
		" R ",
		" Q ",
		" K "
	};
	static char	*bp[] = {
		"   ",
		" *P",
		" *N",
		" *B",
		" *R",
		" *Q",
		" *K"
	};
	static char	*wsqr = "\033[0m";
	static char	*bsqr = "\033[7m";
	static char	*top = "\t+------------------------+\n";
	static char	*mid = "";
	static char	*start = "|";
	static char	*end = "\033[0m|";
	static char	*label = "\t  a  b  c  d  e  f  g  h\n";
	static char	*blabel = "\t  h  g  f  e  d  c  b  a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Experimental vt-100 ANSI board for light backgrounds
 */
PUBLIC int
style4(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"   ",
		" P ",
		" N ",
		" B ",
		" R ",
		" Q ",
		" K "
	};
	static char	*bp[] = {
		"   ",
		" *P",
		" *N",
		" *B",
		" *R",
		" *Q",
		" *K"
	};
	static char	*wsqr = "\033[7m";
	static char	*bsqr = "\033[0m";
	static char	*top = "\t+------------------------+\n";
	static char	*mid = "";
	static char	*start = "|";
	static char	*end = "\033[0m|";
	static char	*label = "\t  a  b  c  d  e  f  g  h\n";
	static char	*blabel = "\t  h  g  f  e  d  c  b  a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Style suggested by ajpierce@med.unc.edu
 */
PUBLIC int style5(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"    ",
		"  o ",
		" :N:",
		" <B>",
		" |R|",
		" {Q}",
		" =K="
	};
	static char	*bp[] = {
		"    ",
		"  p ",
		" :n:",
		" <b>",
		" |r|",
		" {q}",
		" =k="
	};
	static char	*wsqr = "";
	static char	*bsqr = "";
	static char	*top = "        .   .   .   .   .   .   .   .   .\n";
	static char	*mid = "        .   .   .   .   .   .   .   .   .\n";
	static char	*start = "";
	static char	*end = "";
	static char	*label = "\t  a   b   c   d   e   f   g   h\n";
	static char	*blabel = "\t  h   g   f   e   d   c   b   a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Email Board suggested by Thomas Fought (tlf@rsch.oclc.org)
 */
PUBLIC int
style6(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {
		"    |",
		" wp |",
		" WN |",
		" WB |",
		" WR |",
		" WQ |",
		" WK |"
	};
	static char	*bp[] = {
		"    |",
		" bp |",
		" BN |",
		" BB |",
		" BR |",
		" BQ |",
		" BK |"};
	static char	*wsqr = "";
	static char	*bsqr = "";
	static char	*top = "\t-----------------------------------------\n";
	static char	*mid = "\t-----------------------------------------\n";
	static char	*start = "|";
	static char	*end = "";
	static char	*label = "\t  A    B    C    D    E    F    G    H\n";
	static char	*blabel = "\t  H    G    F    E    D    C    B    A\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * Miniature board
 */
PUBLIC int
style7(game_state_t *b, move_t *ml)
{
	static char	*wp[] = {"  ", " P", " N", " B", " R", " Q", " K"};
	static char	*bp[] = {" -", " p", " n", " b", " r", " q", " k"};
	static char	*wsqr = "";
	static char	*bsqr = "";
	static char	*top = "\t:::::::::::::::::::::\n";
	static char	*mid = "";
	static char	*start = "..";
	static char	*end = " ..";
	static char	*label = "\t   a b c d e f g h\n";
	static char	*blabel = "\t   h g f e d c b a\n";

	return genstyle(b, ml, wp, bp, wsqr, bsqr, top, mid, start, end, label,
	    blabel);
}

/*
 * ICS interface maker board  --  raw data dump.
 */
PUBLIC int
style8(game_state_t *b, move_t *ml)
{
	char	tmp[80];
	int	f, r;
	int	ws, bs;

	board_calc_strength(b, &ws, &bs);

	msnprintf(tmp, sizeof tmp, "#@#%03d%-16s%s%-16s%s",
	    (b->gameNum + 1),

	    garray[b->gameNum].white_name,
	    (orient == WHITE ? "*" : ":"),

	    garray[b->gameNum].black_name,
	    (orient == WHITE ? ":" : "*"));

	mstrlcat(bstring, tmp, sizeof bstring);

	for (r = 0; r < 8; r++) {
		for (f = 0; f < 8; f++) {
			if (b->board[f][r] == NOPIECE) {
				mstrlcat(bstring, " ", sizeof bstring);
			} else {
				if (colorval(b->board[f][r]) == WHITE) {
					mstrlcat(bstring, wpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				} else {
					mstrlcat(bstring, bpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				}
			}
		}
	}

	msnprintf(tmp, sizeof tmp, "%03d%s%02d%02d%05d%05d%-7s(%s)@#@\n",
	    (garray[b->gameNum].numHalfMoves / 2 + 1),
	    (b->onMove == WHITE ? "W" : "B"),
	    ws,
	    bs,
	    ((wTime + 5) / 10),
	    ((bTime + 5) / 10),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].algString
	    : "none"),

	    (garray[b->gameNum].numHalfMoves
	    ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
	    : "0:00"));

	mstrlcat(bstring, tmp, sizeof bstring);
	return 0;
}

/*
 * Last 2 moves only (previous non-verbose mode).
 */
PUBLIC int
style9(game_state_t *b, move_t *ml)
{
	char	tmp[80];
	int	i, count;
	int	startmove;

	msnprintf(tmp, sizeof tmp, "\nMove     %-23s%s\n",
	    garray[b->gameNum].white_name,
	    garray[b->gameNum].black_name);
	mstrlcat(bstring, tmp, sizeof bstring);

	msnprintf(tmp, sizeof tmp, "----     --------------         "
	    "--------------\n");
	mstrlcat(bstring, tmp, sizeof bstring);

	startmove = ((garray[b->gameNum].numHalfMoves - 3) / 2) * 2;

	if (startmove < 0)
		startmove = 0;

	i = startmove;
	count = 0;

	while (i < garray[b->gameNum].numHalfMoves && count < 4) {
		if (!(i & 0x01)) {
			msnprintf(tmp, sizeof tmp, "  %2d     ", (i / 2 + 1));
			mstrlcat(bstring, tmp, sizeof bstring);
		}

		msnprintf(tmp, sizeof tmp, "%-23s", move_and_time(&ml[i]));
		mstrlcat(bstring, tmp, sizeof bstring);

		if (i & 0x01)
			mstrlcat(bstring, "\n", sizeof bstring);
		i++;
		count++;
	}

	if (i & 0x01)
		mstrlcat(bstring, "\n", sizeof bstring);
	return 0;
}

/*
 * Sleator's 'new and improved' raw dump format...
 */
PUBLIC int
style10(game_state_t *b, move_t *ml)
{
	char	 tmp[80];
	int	 f, r;
	int	 ret, too_long;
	int	 ws, bs;

	board_calc_strength(b, &ws, &bs);
	msnprintf(tmp, sizeof tmp, "<10>\n");
	mstrlcat(bstring, tmp, sizeof bstring);

	for (r = 7; r >= 0; r--) {
		mstrlcat(bstring, "|", sizeof bstring);

		for (f = 0; f < 8; f++) {
			if (b->board[f][r] == NOPIECE) {
				mstrlcat(bstring, " ", sizeof bstring);
			} else {
				if (colorval(b->board[f][r]) == WHITE) {
					mstrlcat(bstring, wpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				} else {
					mstrlcat(bstring, bpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				}
			}
		}

		mstrlcat(bstring, "|\n", sizeof bstring);
	}

	mstrlcat(bstring, (b->onMove == WHITE ? "W " : "B "), sizeof bstring);

	if (garray[b->gameNum].numHalfMoves) {
		msnprintf(tmp, sizeof tmp, "%d ",
		    ml[garray[b->gameNum].numHalfMoves - 1].doublePawn);
	} else {
		msnprintf(tmp, sizeof tmp, "-1 ");
	}

	mstrlcat(bstring, tmp, sizeof bstring);

	msnprintf(tmp, sizeof tmp, "%d %d %d %d %d\n",
	    !(b->wkmoved || b->wkrmoved),
	    !(b->wkmoved || b->wqrmoved),
	    !(b->bkmoved || b->bkrmoved),
	    !(b->bkmoved || b->bqrmoved),

	    (garray[b->gameNum].numHalfMoves -
	    (b->lastIrreversable == -1 ? 0 : b->lastIrreversable)));

	mstrlcat(bstring, tmp, sizeof bstring);

	ret = snprintf(tmp, sizeof tmp, "%d %s %s %d %d %d %d %d %d %d %d %s "
	    "(%s) %s %d\n",
	    b->gameNum,
	    garray[b->gameNum].white_name,
	    garray[b->gameNum].black_name,
	    myTurn,
	    (garray[b->gameNum].wInitTime / 600),
	    (garray[b->gameNum].wIncrement / 10),
	    ws,
	    bs,
	    ((wTime + 5) / 10),
	    ((bTime + 5) / 10),
	    (garray[b->gameNum].numHalfMoves / 2 + 1),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
	    : "none"),

	    (garray[b->gameNum].numHalfMoves
	    ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
	    : "0:00"),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].algString
	    : "none"),

	    (orient == WHITE ? 0 : 1));

	too_long = (ret < 0 || (size_t)ret >= sizeof tmp);

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

	mstrlcat(bstring, tmp, sizeof bstring);

	msnprintf(tmp, sizeof tmp, ">10<\n");
	mstrlcat(bstring, tmp, sizeof bstring);

	return 0;
}

/*
 * Same as 8, but with verbose moves ("P/e3-e4", instead of "e4").
 */
PUBLIC int
style11(game_state_t *b, move_t *ml)
{
	char	tmp[80];
	int	f, r;
	int	ws, bs;

	board_calc_strength(b, &ws, &bs);

	msnprintf(tmp, sizeof tmp, "#@#%03d%-16s%s%-16s%s",
	    b->gameNum,

	    garray[b->gameNum].white_name,
	    (orient == WHITE ? "*" : ":"),

	    garray[b->gameNum].black_name,
	    (orient == WHITE ? ":" : "*"));

	mstrlcat(bstring, tmp, sizeof bstring);

	for (r = 0; r < 8; r++) {
		for (f = 0; f < 8; f++) {
			if (b->board[f][r] == NOPIECE) {
				mstrlcat(bstring, " ", sizeof bstring);
			} else {
				if (colorval(b->board[f][r]) == WHITE) {
					mstrlcat(bstring, wpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				} else {
					mstrlcat(bstring, bpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				}
			}
		}
	}

	msnprintf(tmp, sizeof tmp, "%03d%s%02d%02d%05d%05d%-7s(%s)@#@\n",
	    (garray[b->gameNum].numHalfMoves / 2 + 1),
	    (b->onMove == WHITE ? "W" : "B"),
	    ws,
	    bs,
	    ((wTime + 5) / 10),
	    ((bTime + 5) / 10),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
	    : "none"),

	    (garray[b->gameNum].numHalfMoves
	    ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
	    : "0:00"));

	mstrlcat(bstring, tmp, sizeof bstring);
	return 0;
}

/*
 * Similar to style 10. See the "style12" help file for information.
 */
PUBLIC int
style12(game_state_t *b, move_t *ml)
{
	char	 tmp[80];
	int	 f, r;
	int	 ret, too_long;
	int	 ws, bs;

	board_calc_strength(b, &ws, &bs);
	msnprintf(bstring, sizeof bstring, "<12> ");

	for (r = 7; r >= 0; r--) {
		for (f = 0; f < 8; f++) {
			if (b->board[f][r] == NOPIECE) {
				mstrlcat(bstring, "-", sizeof bstring);
			} else {
				if (colorval(b->board[f][r]) == WHITE) {
					mstrlcat(bstring, wpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				} else {
					mstrlcat(bstring, bpstring
					    [piecetype(b->board[f][r])],
					    sizeof bstring);
				}
			}
		}

		mstrlcat(bstring, " ", sizeof bstring);
	}

	mstrlcat(bstring, (b->onMove == WHITE ? "W " : "B "), sizeof bstring);

	if (garray[b->gameNum].numHalfMoves) {
		msnprintf(tmp, sizeof tmp, "%d ",
		    ml[garray[b->gameNum].numHalfMoves - 1].doublePawn);
	} else {
		msnprintf(tmp, sizeof tmp, "-1 ");
	}

	mstrlcat(bstring, tmp, sizeof bstring);

	msnprintf(tmp, sizeof tmp, "%d %d %d %d %d ",
	    !(b->wkmoved || b->wkrmoved),
	    !(b->wkmoved || b->wqrmoved),
	    !(b->bkmoved || b->bkrmoved),
	    !(b->bkmoved || b->bqrmoved),

	    (garray[b->gameNum].numHalfMoves -
	    (b->lastIrreversable == -1 ? 0 : b->lastIrreversable)));
	mstrlcat(bstring, tmp, sizeof bstring);

	ret = snprintf(tmp, sizeof tmp, "%d %s %s %d %d %d %d %d %d %d %d %s "
	    "(%s) %s %d\n",
	    (b->gameNum + 1),
	    garray[b->gameNum].white_name,
	    garray[b->gameNum].black_name,
	    myTurn,
	    (garray[b->gameNum].wInitTime / 600),
	    (garray[b->gameNum].wIncrement / 10),
	    ws,
	    bs,
	    ((wTime + 5) / 10),
	    ((bTime + 5) / 10),
	    (garray[b->gameNum].numHalfMoves / 2 + 1),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].moveString
	    : "none"),

	    (garray[b->gameNum].numHalfMoves
	    ? tenth_str(ml[garray[b->gameNum].numHalfMoves - 1].tookTime, 0)
	    : "0:00"),

	    (garray[b->gameNum].numHalfMoves
	    ? ml[garray[b->gameNum].numHalfMoves - 1].algString
	    : "none"),

	    (orient == WHITE ? 0 : 1));

	too_long = (ret < 0 || (size_t)ret >= sizeof tmp);

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

	mstrlcat(bstring, tmp, sizeof bstring);

	return 0;
}

PUBLIC int
board_read_file(char *category, char *gname, game_state_t *gs)
{
	FILE	*fp;
	char	 fname[MAX_FILENAME_SIZE + 1];
	int	 c;
	int	 f, r;
	int	 onColor = -1;
	int	 onFile = -1;
	int	 onNewLine = 1;
	int	 onPiece = -1;
	int	 onRank = -1;

	msnprintf(fname, sizeof fname, "%s/%s/%s", board_dir, category, gname);

	if ((fp = fopen(fname, "r")) == NULL)
		return 1;
	for (f = 0; f < 8; f++)
		for (r = 0; r < 8; r++)
			gs->board[f][r] = NOPIECE;
	for (f = 0; f < 2; f++) {
		for (r = 0; r < 8; r++)
			gs->ep_possible[f][r] = 0;
		for (r = PAWN; r <= QUEEN; r++)
			gs->holding[f][r - 1] = 0;
	}

	gs->wkmoved = gs->wqrmoved = gs->wkrmoved = 0;
	gs->bkmoved = gs->bqrmoved = gs->bkrmoved = 0;

	gs->onMove		= -1;
	gs->moveNum		= 1;
	gs->lastIrreversable	= -1;

	while (!feof(fp)) {
		c = fgetc(fp);

		if (onNewLine) {
			if (c == 'W') {
				onColor = WHITE;
				if (gs->onMove < 0)
					gs->onMove = WHITE;
			} else if (c == 'B') {
				onColor = BLACK;
				if (gs->onMove < 0)
					gs->onMove = BLACK;
			} else if (c == '#') {
				while (!feof(fp) && c != '\n')
					c = fgetc(fp); // Comment line
				continue;
			} else { // Skip any line we don't understand
				while (!feof(fp) && c != '\n')
					c = fgetc(fp);
				continue;
			}

			onNewLine = 0;
		} else {
			switch (c) {
			case 'P':
				onPiece = PAWN;
				break;
			case 'R':
				onPiece = ROOK;
				break;
			case 'N':
				onPiece = KNIGHT;
				break;
			case 'B':
				onPiece = BISHOP;
				break;
			case 'Q':
				onPiece = QUEEN;
				break;
			case 'K':
				onPiece = KING;
				break;
			case 'a':
			case 'b':
			case 'c':
			case 'd':
			case 'e':
			case 'f':
			case 'g':
			case 'h':
				onFile = (c - 'a');
				onRank = -1;
				break;
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
				onRank = (c - '1');

				if (onFile >= 0 &&
				    onColor >= 0 &&
				    onPiece >= 0) {
					gs->board[onFile][onRank] =
					    (onPiece | onColor);
				}

				break;
			case '#':
				while (!feof(fp) && c != '\n')
					c = fgetc(fp);	// Comment line
			case '\n':
				onNewLine	= 1;
				onColor		= -1;
				onPiece		= -1;
				onFile		= -1;
				onRank		= -1;
				break;
			default:
				break;
			} // switch
		}
	} // while

	fclose(fp);
	return 0;
}

PRIVATE void
place_piece(board_t b, int piece, int squareColor)
{
	int	placed = 0;
	int	r, f;

	if (iscolor(piece, BLACK))
		r = 7;
	else
		r = 0;

	while (!placed) {
		if (squareColor == ANY_SQUARE) {
			f = (brand() % 8);
		} else {
			f = (brand() % 4) * 2;

			if (SquareColor(f, r) != squareColor)
				f++;
		}

		if ((b)[f][r] == NOPIECE) {
			(b)[f][r] = piece;
			placed = 1;
		}
	}
}

PUBLIC void
wild_update(int style)
{
	board_t b;
	int f, r, i;

	for (f = 0; f < 8; f++) {
		for (r = 0; r < 8; r++)
			b[f][r] = NOPIECE;
	}

	for (f = 0; f < 8; f++) {
		b[f][1] = W_PAWN;
		b[f][6] = B_PAWN;
	}

	switch (style) {
	case 1:
		if (brand() & 0x01) {
			b[4][0] = W_KING;
			b[3][0] = W_QUEEN;
		} else {
			b[3][0] = W_KING;
			b[4][0] = W_QUEEN;
		}
		if (brand() & 0x01) {
			b[4][7] = B_KING;
			b[3][7] = B_QUEEN;
		} else {
			b[3][7] = B_KING;
			b[4][7] = B_QUEEN;
		}

		b[0][0] = b[7][0] = W_ROOK;
		b[0][7] = b[7][7] = B_ROOK;

		/*
		 * Must do bishops before knights to be sure opposite
		 * colored squares are available.
		 */
		place_piece(b, W_BISHOP, WHITE_SQUARE);
		place_piece(b, W_BISHOP, BLACK_SQUARE);
		place_piece(b, W_KNIGHT, ANY_SQUARE);
		place_piece(b, W_KNIGHT, ANY_SQUARE);
		place_piece(b, B_BISHOP, WHITE_SQUARE);
		place_piece(b, B_BISHOP, BLACK_SQUARE);
		place_piece(b, B_KNIGHT, ANY_SQUARE);
		place_piece(b, B_KNIGHT, ANY_SQUARE);
		break;
	case 2:
		place_piece(b, W_KING, ANY_SQUARE);
		place_piece(b, W_QUEEN, ANY_SQUARE);
		place_piece(b, W_ROOK, ANY_SQUARE);
		place_piece(b, W_ROOK, ANY_SQUARE);
		place_piece(b, W_BISHOP, ANY_SQUARE);
		place_piece(b, W_BISHOP, ANY_SQUARE);
		place_piece(b, W_KNIGHT, ANY_SQUARE);
		place_piece(b, W_KNIGHT, ANY_SQUARE);

		/* Black mirrors White */
		for (i = 0; i < 8; i++)
			b[i][7] = (b[i][0] | BLACK);
		break;
	case 3:
		/*
		 * Generate White king on random square plus random
		 * set of pieces.
		 */
		place_piece(b, W_KING, ANY_SQUARE);

		for (i = 0; i < 8; i++) {
			if (b[i][0] != W_KING) {
				b[i][0] = (brand() % 4) + 2;
			}
		}

		/* Black mirrors White */
		for (i = 0; i < 8; i++)
			b[i][7] = (b[i][0] | BLACK);
		break;
	case 4:
		/*
		 * Generate White king on random square plus random
		 * set of pieces.
		 */
		place_piece(b, W_KING, ANY_SQUARE);

		for (i = 0; i < 8; i++) {
			if (b[i][0] != W_KING)
				b[i][0] = (brand() % 4) + 2;
		}

		/*
		 * Black has the same set of pieces, but randomly
		 * permuted, except that Black must have the same
		 * number of bishops on white squares as White has on
		 * black squares, and vice versa. So we must place
		 * Black's bishops first to be sure there are enough
		 * squares left of the correct color.
		 */

		for (i = 0; i < 8; i++) {
			if (b[i][0] == W_BISHOP)
				place_piece(b, B_BISHOP, !SquareColor(i, 0));
		}

		for (i = 0; i < 8; i++) {
			if (b[i][0] != W_BISHOP)
				place_piece(b, (b[i][0] | BLACK), ANY_SQUARE);
		}
		break;
	default:
		return;
		break;
	}

	{
		FILE	*fp;
		char	 fname[MAX_FILENAME_SIZE + 1];
		int	 onPiece;

		msnprintf(fname, sizeof fname, "%s/wild/%d", board_dir, style);

		if ((fp = fopen(fname, "w")) == NULL) {
			warn("%s: can't write file name: %s", __func__, fname);
			return;
		}

		fprintf(fp, "W:");
		onPiece = -1;

		for (r = 1; r >= 0; r--) {
			for (f = 0; f < 8; f++) {
				if (onPiece < 0 || b[f][r] != onPiece) {
					onPiece = b[f][r];

					fprintf(fp, " %s", wpstring
					    [piecetype(b[f][r])]);
				}

				fprintf(fp, " %c%c", (f + 'a'), (r + '1'));
			}
		}

		fprintf(fp, "\nB:");
		onPiece = -1;

		for (r = 6; r < 8; r++) {
			for (f = 0; f < 8; f++) {
				if (onPiece < 0 || b[f][r] != onPiece) {
					onPiece = b[f][r];

					fprintf(fp, " %s", wpstring
					    [piecetype(b[f][r])]);
				}

				fprintf(fp, " %c%c", (f + 'a'), (r + '1'));
			}
		}

		fprintf(fp, "\n");
		fclose(fp);
	}
}

PUBLIC void
wild_init(void)
{
	wild_update(1);
	wild_update(2);
	wild_update(3);
	wild_update(4);
}