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

                                 ChessV

                COPYRIGHT (C) 2006, 2010 BY GREGORY STRONG

This file is part of ChessV.  ChessV 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.

ChessV 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; the file 'COPYING' contains the License text, but if for
some reason you need a copy, please write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

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


#include "StdAfx.h"
#include "ChessV.h"
#include "Board.h"
#include "Player.h"
#include "Games/ChessGames/ChessGame.h"
#include "GameParameters.h"
#include "Statistics.h"


#define MAX_LOADSTRING 100


// *** DEBUG MEMORY ALLOCATION *** //
#ifdef _DEBUG
#define new SAFE_NEW
#endif


// Global Variables:
HINSTANCE hInst;								// current instance
TCHAR szTitle[MAX_LOADSTRING];					// The title bar text
TCHAR szWindowClass[MAX_LOADSTRING];			// the main window class name

Board *theBoard;								// pointer to the chess board
Game *theGame;
HWND theWindow;
HINSTANCE theInstance;

bool bInitialized;
bool mouseCaptured;
int destinationSquare;
int mouseX;
int mouseY;

int transpositionTableSize = 64;
int evaluationCacheSize = 16;

char gameSelection[62];
char newVariantName[62];
GameInformation *selectedVariant = NULL;
GameInformationVector includedVariants;
int nIncludedVariants = 0;
int nSupportedVariants = 0;

bool whiteComp = false;
bool blackComp = false;
int maxThinkTime = 15;
int minIDepth = 0;
int maxIDepth = 24;
int personality = PERSONALITY_A;
bool interactiveComputer = false;
bool useOpeningBook = true;
bool rotateBoard = false;
bool loadingGame = false;
bool justLoaded = false;
bool saveNewVariant = false;
bool testingPosition = false;
int testingDepth = 9;
int testingMaxMoves = 100;
int spinner = 0;

bool refreshCausedByBookkeeping = false;
static bool firstPaint = false;

//	board appearance properties
COLORREF squareColor1 = RGB(255, 255, 204);
COLORREF squareColor2 = RGB(93, 126, 126);
COLORREF squareColor3 = RGB(93, 106, 106);
COLORREF pieceColor1 = RGB(255, 255, 255);
COLORREF pieceColor2 = RGB(89, 132, 189);
COLORREF borderColor = RGB(128, 70, 70);
int squareTexture1 = -1;
int squareTexture2 = -1;
int squareTexture3 = -1;
int boardDisplayType = BOARD_IS_CHECKERED;
bool gameSpecificBoardDisplay = true;
char *gameSpecificBoardDisplayDescription = NULL;
HKEY hVariantKey;
HMENU contextMenu;
HWND hMoveList = NULL;
HWND hThinkingList = NULL;
FILE *traceFile;

char *pieceSets[] =
{
	"Standard Pieces",
	"Alternate Pieces",
	"Small Pieces",
	"Abstract Pieces",
	"Old-World Pieces",
	"Motif Pieces",
	"Eurasian Pieces",
	"Ultima Pieces",
	"Rune Pieces"
};

char *textures[] =
{
	"White Marble",
	"White Marble 2",
	"Light Marble",
	"Dark Marble",
	"Cloudy Marble",
	"Gray Marble",
	"Blue Marble",
	"Greenish Marble",
	"Brownish-Green Marble",
	"Orange Marble",
	"Pink Marble",
	"Rose Marble",
	"Wine Marble",
	"Deep Purple Marble",
	"Stone",
	"Orange Space Dust",
	"Rusted Metal",
	"Smoke",
	"Creamy Clouds"
};

char *textureDirectories[] =
{
	"WhiteMarble",
	"WhiteMarble2",
	"LightMarble",
	"DarkMarble",
	"CloudyMarble",
	"GrayMarble",
	"BlueMarble",
	"GreenishMarble",
	"BrownishGreenMarble",
	"OrangeMarble",
	"PinkMarble",
	"RoseMarble",
	"WineMarble",
	"DeepPurpleMarble",
	"Stone",
	"OrangeSpaceDust",
	"RustedMetal",
	"Smoke",
	"CreamyClouds"
};

int nTextures = sizeof(textures) / sizeof(textures[0]);

int pieceSetSizes[] = { 54, 54, 46, 54, 46, 54, 54, 58, 54 };
int nPieceSets = sizeof(pieceSets) / sizeof(pieceSets[0]);
int selectedPieceSet = 0;

HBITMAP *squareTextureBitmaps[3];

int extraCaptureSquares[50];
int extraCaptures = 0;

bool singular = false;
int singularMargin = 100;

HWND mainWindow;
ATOM propWndClass;

Player *pPlayer0;
Player *pPlayer1;

ATOM MyRegisterClass( HINSTANCE hInstance );
BOOL InitInstance( HINSTANCE, int );
LRESULT CALLBACK WndProc( HWND, UINT, WPARAM, LPARAM );
LRESULT CALLBACK About( HWND, UINT, WPARAM, LPARAM );


GameInformation supportedVariants[] =
{
	INTERNAL_CHESS_VARIANT("Alapo", AlapoGame, 6, 6, 36, "1982", "Johannes Tranelis", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Almost Chess", ChessGame, 8, 8, 64, "1977", "Ralph Betza", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Angels and Devils", AngelsAndDevilsGame, 8, 10, 80, "2004", "Greg Strong", "White", "Black", IDD_ANGELS_AND_DEVILS_SELECT_DIALOG),
	INTERNAL_CHESS_VARIANT("Archchess", DecimalChessGame, 10, 10, 100, "1683", "Francesco Piacenza", "White", "Black", IDD_ARCHCHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("ArchCourier Chess", Game_12x8, 8, 12, 96, "2006", "Eric Greenwood", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Berolina Chess", BerolinaChessGame, 8, 8, 64, "1926", "Edmund Hebermann", "White", "Black", IDD_BEROLINA_DIALOG),
	INTERNAL_CHESS_VARIANT("Bird's Chess", CapablancaGame, 8, 10, 80, "1874", "Henry Bird", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Cagliostro's Chess", Game_12x8, 8, 12, 80, "1970s?", "Savio Cagliostro", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Capablanca Chess", CapablancaGame, 8, 10, 80, "1920", "Jose Raul Capablanca", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Capablanca Chess, Aberg variant", CapablancaGame, 8, 10, 80, "2003", "Hans Aberg", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Capablanca Chess, Paulowich variant", CapablancaGame, 8, 10, 80, "2004", "David Paulowich", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Carrera's Chess", CapablancaGame, 8, 10, 80, "1617", "D. Pietro Carrera", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Chess256", ChessGame, 8, 8, 64, "2006", "Mats Winther", "White", "Black", IDD_CHESS256_DIALOG),
	INTERNAL_CHESS_VARIANT2("Chess 480", ChessGame, 8, 8, 64, "2005", "John Kipling Lewis", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Chess with Augmented Knights", CWAKGame, 8, 8, 64, "1980", "Ralph Betza", "White", "Black", IDD_CWAK_SELECT_DIALOG),
	INTERNAL_CHESS_VARIANT2("Chess with Different Armies", CWDAGame, 8, 8, 64, "1980", "Ralph Betza", "White", "Black", IDD_CWDA_SELECT_DIALOG),
	INTERNAL_CHESS_VARIANT2("Chess with Ultima Pieces", CWUPGame, 10, 10, 100, "1986", "Peter Aronson and George Dekle", "White", "Black", IDD_CWUP_SELECT_DIALOG),
//	INTERNAL_CHESS_VARIANT("Citadel Chess", CitadelChessGame, 10, 10, 104, "1400", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Courier Chess", Game_12x8, 8, 12, 96, "1100s", "unknown", "White", "Black", IDD_COURIER_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT2("Cylindrical Chess", CylindricalChessGame, 8, 8, 64, "1850", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Diagonal Chess", DiamondChessGame, 8, 8, 64, "1943", "L. A. Lewis", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Diamond Chess", DiamondChessGame, 8, 8, 64, "1886", "A. K. Porterfield Rynd", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Embassy Chess", CapablancaGame, 8, 10, 80, "2006", "Kevin Hill", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Emperor's Game", DecimalChessGame, 10, 10, 100, "1840", "L. Tressan", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Enep", EnepGame, 8, 8, 64, "2016", "Aurelian Florea", "White", "Black", IDD_GENERIC_GAME_DIALOG ),
	INTERNAL_CHESS_VARIANT2("Eurasian Chess", EurasianChessGame, 10, 10, 100, "2003", "Fergus Duniho", "White", "Black", IDD_EURASIAN_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("Extinction Chess", ExtinctionChessGame, 8, 8, 64, "1985", "R. Wayne Schmittberger", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Fischer Random Chess", ChessGame, 8, 8, 64, "1996", "Bobby Fischer", "White", "Black", IDD_FRC_DIALOG),
	INTERNAL_CHESS_VARIANT("Great Chess", DecimalChessGame, 10, 10, 100, "1600s", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Great Shatranj", GreatShatranjGame, 8, 10, 80, "2006", "Joe Joyce", "White", "Black", IDD_GREAT_SHATRANJ_DIALOG),
	INTERNAL_CHESS_VARIANT("Grand Chess", GrandChessGame, 10, 10, 100, "1984", "Christiaan Freeling", "White", "Black", IDD_GRAND_CHESS_SELECT_DIALOG),
	INTERNAL_CHESS_VARIANT("Grotesque Chess", GrotesqueChessGame, 8, 10, 80, "2004", "Fergus Duniho", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Janus Chess", JanusChessGame, 8, 10, 80, "1990", "Werner Schoendorf", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Janus Kamil Chess", Game_12x8, 8, 12, 80, "2004", "Jorg Knappen", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Kinglet", KingletGame, 8, 8, 64, "1953", "V. R. Parton", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Knightmate", ChessGame, 8, 8, 64, "1972", "Bruce Zimov", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Ladorean Chess", CapablancaGame, 8, 10, 80, "2005", "Bernhard U. Hermes and Fergus Duniho", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Latrunculi Duo Milia et Septum", ChessGame, 8, 8, 64, "2007", "Gary K. Gifford", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Legan's Game", DiamondChessGame, 8, 8, 64, "1913", "L. Legan", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Lemurian Shatranj", LemurianShatranjGame, 8, 8, 64, "2006", "Joe Joyce", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Lions and Unicorns Chess", CapablancaGame, 8, 10, 80, "2004", "David Paulowich", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Los Alamos Chess", LosAlamosGame, 6, 6, 36, "1956", "J. Kister, P. Stein, S. Ulam, W. Walden, and M. Wells", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Modern Kamil", DecimalChessGame, 10, 10, 100, "2002", "Nuno Cruz", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Modern Shatranj", ChessGame, 8, 8, 64, "2005", "Joe Joyce", "White", "Black", IDD_MODERN_SHATRANJ_DIALOG),
	INTERNAL_CHESS_VARIANT("New Chancellor Chess", CapablancaGame, 8, 10, 80, "1997", "David Paulowich", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Odin's Rune Chess", OdinsRuneGame, 10, 10, 100, "2005", "Gary K. Gifford", "White", "Black", IDD_ODINS_RUNE_DIALOG),
	INTERNAL_CHESS_VARIANT("Opti Chess (mirror I)", CapablancaGame, 8, 10, 80, "2006", "Derek Nalls", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Opulent Chess", DecimalChessGame, 10, 10, 100, "2005", "Greg Strong", "White", "Black", IDD_OPULENT_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("Orthodox Chess", ChessGame, 8, 8, 64, "1200s", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Polymorph Chess", PolymorphChessGame, 8, 8, 64, "2004", "Greg Strong", "White", "Black", IDD_POLYMORPH_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("Roman Chess", DecimalChessGame, 10, 10, 100, "1999", "Mark and Eric Woodall", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Royal Court", CapablancaGame, 8, 10, 80, "1999", "Sidney LaVesseur", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Schoolbook Chess", CapablancaGame, 8, 10, 80, "2006", "Sam Trenholme", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Shatranj", ChessGame, 8, 8, 64, "800s", "unknown", "White", "Black", IDD_SHATRANJ_DIALOG),
	INTERNAL_CHESS_VARIANT2("Shatranj Kamil", ShatranjKamilGame, 10, 10, 100, "1000s", "unknown", "White", "Black", IDD_SHATRANJ_KAMIL_DIALOG),
	INTERNAL_CHESS_VARIANT("Shatranj Kamil (64)", ShatranjKamil64Game, 8, 8, 64, "2005", "David Paulowich", "White", "Black", IDD_SHATRANJ_KAMIL_64_DIALOG),
	INTERNAL_CHESS_VARIANT("Sosarian Chess", Game_12x8, 8, 12, 96, "2006", "Greg Strong", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Spartan Chess", SpartanChessGame, 8, 8, 64, "2010", "Steven Streetman", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT2("Switching Chess", SwitchingChessGame, 8, 8, 64, "2004", "Tony Quintanilla", "White", "Black", IDD_SWITCHING_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("TenCubed Chess", DecimalChessGame, 10, 10, 100, "2005", "David Paulowich", "White", "Black", IDD_TEN_CUBED_CHESS_DIALOG),
	INTERNAL_CHESS_VARIANT("Three Checks Chess", ThreeChecksChessGame, 8, 8, 64, "2000", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Ultima", UltimaGame, 8, 8, 64, "1962", "Robert Abbott", "White", "Black", IDD_ULTIMA_DIALOG),
	INTERNAL_CHESS_VARIANT("Unicorn Chess", DecimalChessGame, 10, 10, 100, "2000", "David Paulowich", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Unicorn Great Chess", DecimalChessGame, 10, 10, 100, "2002", "David Paulowich", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Unicorn Grand Chess", DecimalChessGame, 10, 10, 100, "2004", "David Paulowich and Greg Strong", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Univers Chess", CapablancaGame, 8, 10, 80, "2006", "Fergus Duniho and Bruno Violet", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Xiangqi", XiangQiGame, 10, 9, 90, "unknown", "unknown", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	INTERNAL_CHESS_VARIANT("Test Game", DecimalChessGame, 10, 10, 100, "2006", "Greg Strong", "White", "Black", IDD_GENERIC_GAME_DIALOG),
	END_INTERNAL_VARIANT_LIST
};

bool stringCompNoCase( const char *s1, const char *s2 )
{
	if( s1 == NULL )
		return false;
	int cursor = 0;
	while( true )
	{
		if( s1[cursor] == '\0' && s2[cursor] == '\0' )
			return true;
		if( s1[cursor] == '\0' || s2[cursor] == '\0' )
			return false;
		if( toupper( s1[cursor] ) != toupper( s2[cursor] ) )
			return false;
		cursor++;
	}
}

void LoadGame( char *filename );

int ParseStringArgument
	( char *text,
	  int &cursor,
	  int &tokenStart,
	  int length );

int SkipWhitespace
	( char *text,
	  int &cursor,
	  int length,
	  bool skipNewlines );

void LoadSquareTextureBitmaps( int squares, int texture )
{
	if( squares < 0 || squares > 2 )
		ASSERT(FALSE);
	if( squareTextureBitmaps[squares] != NULL )
	{
		for( int x = 0; x < 19; x++ )
			::DeleteObject( squareTextureBitmaps[squares][x] );
		delete[] squareTextureBitmaps[squares];
	}
	HBITMAP *phBitmaps = new HBITMAP[19];
	for( int x = 0; x < 19; x++ )
	{
		char filename[96];
		sprintf( filename, "images\\textures\\%s\\image%d.bmp", textureDirectories[texture], x + 1 );
		phBitmaps[x] = (HBITMAP) ::LoadImage( NULL, filename, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE );
	}
	if( squares == 0 )
		squareTexture1 = texture;
	else if( squares == 1 )
		squareTexture2 = texture;
	else if( squares == 2 )
		squareTexture3 = texture;
	squareTextureBitmaps[squares] = phBitmaps;
}

void LoadSquareTextureBitmaps( int squares, char *textureName )
{
	for( int x = 0; x < nTextures; x++ )
		if( stringCompNoCase( textureName, textures[x] ) )
		{
			LoadSquareTextureBitmaps( squares, x );
			return;
		}
}

void UnloadSquareTextureBitmaps( int squares )
{
	if( squareTextureBitmaps[squares] != NULL )
	{
		for( int x = 0; x < 19; x++ )
			::DeleteObject( squareTextureBitmaps[squares][x] );
		delete[] squareTextureBitmaps[squares];
		squareTextureBitmaps[squares] = NULL;
	}
}

void ScanForInventorAndInvented( char *buffer, int length, int cursor, GameInformation *game )
{
	bool inventorFound = false;
	bool inventedFound = false;

	while( true )
	{
		SkipWhitespace( buffer, cursor, length, true );
		if( buffer[cursor++] == '$' )
		{
			int tokenStart = cursor;
			while( cursor < length &&
				((buffer[cursor] >= 'A' && buffer[cursor] <= 'Z') ||
				 (buffer[cursor] >= 'a' && buffer[cursor] <= 'z')) )
				cursor++;
			if( cursor < length && cursor == tokenStart + 8 )
			{
				char temp = buffer[cursor];
				buffer[cursor] = '\0';
				if( !strcmp( buffer + tokenStart, "inventor" ) && !inventorFound )
				{
					buffer[cursor] = temp;
					while( cursor < length && buffer[cursor] != '=' )
						cursor++;
					if( cursor < length )
					{
						cursor++;
						int start;
						int rtn = ParseStringArgument( buffer, cursor, start, length );
						if( rtn == 0 )
						{
							int len = (int) strlen( buffer + start );
							game->inventor = new char[len + 1];
							strcpy( game->inventor, buffer + start );
							inventorFound = true;
						}
					}
				}
				else if( !strcmp( buffer + tokenStart, "invented" ) && !inventedFound )
				{
					buffer[cursor] = temp;
					while( cursor < length && buffer[cursor] != '=' )
						cursor++;
					if( cursor < length )
					{
						cursor++;
						int start;
						int rtn = ParseStringArgument( buffer, cursor, start, length );
						if( rtn == 0 )
						{
							int len = (int) strlen( buffer + start );
							game->year_of_invention = new char[len + 1];
							strcpy( game->year_of_invention, buffer + start );
							inventedFound = true;
						}
					}
				}
			}
		}
		else
		{
			//	skip to next line
			while( buffer[cursor] != '\n' && buffer[cursor] != '\r' && cursor < length )
				cursor++;
		}
		if( cursor >= length || (inventorFound && inventedFound) )
			return;
	}
}

void LoadIncludeFile( const char *filename )
{
	HANDLE file = ::CreateFile( filename, FILE_READ_DATA, FILE_SHARE_READ, NULL, OPEN_EXISTING,
		FILE_ATTRIBUTE_READONLY, NULL );
	if( file != INVALID_HANDLE_VALUE )
	{
		char *buffer = new char[32000];
		DWORD bytesRead;
		::ReadFile( file, buffer, 200, &bytesRead, NULL );
		if( bytesRead > 2 )
		{
			//	skip whitespace
			int x;
			for( x = 0; x < (int) strlen(buffer) && (buffer[x] == ' ' || buffer[x] == '\t' || buffer[x] == '\n' || buffer[x] == '\r'); x++ )
				;

			//	scan name of new game
			int tokenStart = x;
			bool scanning = true;
			bool foundSemi = false;
			for( ; scanning; x++ )
			{
				if( buffer[x] == ':' )
				{
					buffer[x] = '\0';
					foundSemi = true;
					scanning = false;
				}
				else if( buffer[x] == '\r' || buffer[x] == '\n' || buffer[x] == '\r' || buffer[x] == '\0' )
					scanning = false;
				else if( x >= (int) bytesRead )
					scanning = false;
			}
			if( !foundSemi )
			{
				char errorMessage[200];
				sprintf( errorMessage, "Error in include file: %s", filename );
				::MessageBox( theWindow, errorMessage, "Include ERROR", MB_OK | MB_ICONHAND );
			}
			else
			{
				int nameLength = (int) strlen(buffer+tokenStart) + 1;
				char *gameName = new char[nameLength];
				strcpy( gameName, buffer + tokenStart );

				//	now scan name of the original game from
				//	which this game is derived
				tokenStart = x;
				for( ; x < (int) bytesRead && buffer[x] != '\n' && buffer[x] != '\r' && buffer[x] != '\0'; x++ )
					;
				if( x == bytesRead )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in include file: %s", filename );
					::MessageBox( theWindow, errorMessage, "Include ERROR", MB_OK | MB_ICONHAND );
				}
				else
				{
					buffer[x] = '\0';
					int nameLen = (int) strlen(buffer+tokenStart)+1;
					char *baseGameName = new char[nameLen];
					strcpy( baseGameName, buffer + tokenStart );

					//	create entry for this game
					includedVariants[nIncludedVariants].name = gameName;
					includedVariants[nIncludedVariants].baseGameName = baseGameName;
					//	find base game
					bool found = false;
					for( x = 0; !found && x < nSupportedVariants; x++ )
					{
						if( !strcmp( supportedVariants[x].name, baseGameName ) )
							found = true;
					}
					if( !found )
					{
						char errorMessage[200];
						sprintf( errorMessage, "Error in include file: %s - base game %s not found", filename, baseGameName );
						::MessageBox( theWindow, errorMessage, "Include ERROR", MB_OK | MB_ICONHAND );
					}
					else
					{
						includedVariants[nIncludedVariants].file_count = supportedVariants[x].file_count;
						includedVariants[nIncludedVariants].rank_count = supportedVariants[x].rank_count;
						includedVariants[nIncludedVariants].square_count = supportedVariants[x].square_count;
						includedVariants[nIncludedVariants].createGameFunction = supportedVariants[x].createGameFunction;
						includedVariants[nIncludedVariants].startGameFunction = supportedVariants[x].startGameFunction;
						includedVariants[nIncludedVariants].inventor = "unknown";
						includedVariants[nIncludedVariants].year_of_invention = "unknown";
						ScanForInventorAndInvented( buffer, bytesRead, x, &(includedVariants[nIncludedVariants]) );
						//	allocate memory for, and store the filename
						char *filename_field = new char[256];
						strcpy( filename_field, filename );
						includedVariants[nIncludedVariants].filename = filename_field;
						nIncludedVariants++;
					}
				}
			}
		}
		::CloseHandle( file );
		delete[] buffer;
	}
}

void SetRazorAndFutilityMargins()
{
	//	if the think time is higher than 10 seconds, then scale up the
	//	razor and futility margins based on the think time.  the more time,
	//	the higher the margins should be
	/*if( maxThinkTime > 10 )
	{
		int adjustment = (MIN(maxThinkTime, 20) - 10) * 25;
		if( maxThinkTime > 20 )
			adjustment += (MIN(maxThinkTime, 40) - 20) * 10;
		theGame->SetFutilityMargin( theGame->GetFutilityMargin() + adjustment );
		theGame->SetExtendedFutilityMargin( theGame->GetExtendedFutilityMargin() + adjustment );
		theGame->SetRazorMargin( theGame->GetRazorMargin() + adjustment );
	}*/
}

#ifdef _WINBOARD_VERSION
int post = 0; // [HGM] control Thinking Output in WB mode (exported to Board.cpp)
char promoChar; // indicate promotion piece from input move

DWORD WINAPI WinBoardInput(/*__in*/ LPVOID dummy)
{
	int mps, tc, inc, timeLeft, force, plyCnt=0;
	while(1) {
		char buf[80], command[80], *p = buf;
		while((*p = getchar()) != EOF && *p != '\n') p++;
		*p = 0;
		sscanf(buf, "%s", command);
		if(!strcmp(command, "quit")) {
			exit(0);
		} else if(!strcmp(command, "new")) {
			theGame->GetPlayer(0).SetComputerControl(0);
			theGame->GetPlayer(1).SetComputerControl(1); // play black
			plyCnt = force = 0;
		} else if(!strcmp(command, "go")) {
			int stm = theBoard->GetCurrentPlayerNumber();
			theGame->GetPlayer(!stm).SetComputerControl(0);
			theGame->GetPlayer( stm).SetComputerControl(1); // play stm
			::InvalidateRect( theWindow, NULL, true );
			::UpdateWindow( theWindow );
			theBoard->Think();
			::InvalidateRect( theWindow, NULL, true );
			::UpdateWindow( theWindow );
			plyCnt++; force = 0;
		} else if(!strcmp(command, "level")) {
			int sec = 0;
			sscanf(buf, "level %d %d %d", &mps, &tc, &inc) == 3 ||
				sscanf(buf, "level %d %d:%d %d", &mps, &tc, &sec, &inc);
			tc = 60*tc + sec;
			if(mps) maxThinkTime = tc/mps; else maxThinkTime = tc/80 + inc;
			timeLeft = tc*100;
			SetRazorAndFutilityMargins();
		} else if(!strcmp(command, "time")) {
			sscanf(buf, "time %d", &timeLeft);
			if(mps) { // feedback based on GUI clock
				int movesLeft = (2*mps - (plyCnt+1)%(2*mps) + 1)/2;
				maxThinkTime = 0.01*timeLeft/(movesLeft+1);
			} else {
				maxThinkTime = 0.000166*timeLeft + inc;
			}
		} else if(!strcmp(command, "protover")) {
			// *** WINBOARD feature variants *** //
			printf("feature variants=\"capablanca,fairy,normal,xiangqi,knightmate,spartan,grand,10x10+0_fairy\" ping=1 debug=1 done=1\n");
			fflush(stdout);
		} else if(!strcmp(command, "ping")) {
			int nr;
			sscanf(buf, "ping %d\n", &nr);
			printf("pong %d\n", nr); fflush(stdout);
		} else if(!strcmp(command, "post")) {
			post = 1;
		} else if(!strcmp(command, "nopost")) {
			post = 0;
		} else if(!strcmp(command, "force")) {
			theGame->GetPlayer(0).SetComputerControl(0);
			theGame->GetPlayer(1).SetComputerControl(0); // play neither
			force = 1;
		} else if(buf[1] <= '9' && buf[3] <= '9' && buf[0] >= 'a' && buf[2] >= 'a') {
			// input move; simulate mouse entry
			int nRanks = theBoard->GetNumberOfRanks();
			int nFiles = theBoard->GetNumberOfFiles();
			int from, to;
			if( nRanks < 10 )
			{
				from = (buf[1] - '1') * nFiles + (buf[0] - 'a');
				to   = (buf[3] - '1') * nFiles + (buf[2] - 'a');
			}
			else
			{
				from = (buf[1] - '0') * nFiles + (buf[0] - 'a');
				to   = (buf[3] - '0') * nFiles + (buf[2] - 'a');
			}
			promoChar = buf[4];
			Piece *piece = theBoard->GetSquareContents( from );
//			MovementList &stack = theBoard->GetLegalUserMovementList();
			theBoard->LiftPiece( piece );
			theBoard->DropPiece( to );
			::InvalidateRect( theWindow, NULL, false );
			plyCnt += 2 - force; // if not force, then automatic reply
		}
	}
}
#endif

char *arg = NULL; // [HGM] first command-line parameter
bool argIsFilename = false;

int APIENTRY WinMain
	( HINSTANCE hInstance,
      HINSTANCE hPrevInstance,
      LPTSTR    lpCmdLine,
      int       nCmdShow )
{
	MSG msg;
	HACCEL hAccelTable;

	// Initialize global strings
	::LoadString( hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING );
	::LoadString( hInstance, IDC_CHESSV, szWindowClass, MAX_LOADSTRING );
	MyRegisterClass( hInstance );

	SYSTEMTIME stime;
	::GetSystemTime( &stime );
	srand( stime.wSecond + stime.wMinute * stime.wHour );

	if( lpCmdLine != NULL && strlen( lpCmdLine ) > 0 )
	{
		int l = strlen( lpCmdLine );
		if( l > 1 && lpCmdLine[0] == '\"' )
		{
			int x;
			for( x = 1; x < l && lpCmdLine[x] != '\"'; x++ );
			if( x < l )
				lpCmdLine[x] = '\0';
			arg = lpCmdLine + 1;
		}
		else
			arg = lpCmdLine;
	}

	theInstance = hInstance;

	newVariantName[0] = '\0';

	//	seed random number generator
	time_t seconds;
	time( &seconds );
	init_simple_rand( (unsigned int) seconds );
	//srand( (unsigned int) seconds );

	gameParameters = new GameParameter[MAX_GAME_PARAMS];
	includedVariants.SetSize( 1000 );

	//	store the current directory so that we can restore it later
	char *currentDirectory = new char[4001];
	::GetCurrentDirectory( 4000, currentDirectory );

	//	count up the supported (internal) varaints
	nSupportedVariants = 0;
	GameInformation *pGI = supportedVariants;
	while( pGI->name != NULL )
	{
		nSupportedVariants++;
		pGI++;
	}

	//	load included variants
	WIN32_FIND_DATA finddata;
	HANDLE hFile = ::FindFirstFile( "include\\*.sgf", &finddata );
	if( hFile != INVALID_HANDLE_VALUE )
	{
		while( true )
		{
			if( !(finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
			{
				char buffer[256];
				sprintf( buffer, "include\\%s", finddata.cFileName );
				LoadIncludeFile( buffer );
			}
			if( ::FindNextFile( hFile, &finddata ) == 0 )
				break;
		}
	}
	::FindClose( hFile );

	while( selectedVariant == NULL )
	{
		int rtn = -1;
		if( arg )
		{
			int arglen = strlen( arg );
			if( arglen > 4 && 
				arg[arglen-4] == '.' &&
				(arg[arglen-3] == 's' || arg[arglen-3] == 'S') && 
				(arg[arglen-2] == 'g' || arg[arglen-2] == 'G') && 
				(arg[arglen-1] == 'f' || arg[arglen-1] == 'F') )
				argIsFilename = true;
			else
				strcpy( gameSelection, arg ); 
		}
		else 
			// [HGM] suppress popup if command-line variant
			rtn = (int) ::DialogBox( hInstance, MAKEINTRESOURCE(IDD_NEW_GAME_DIALOG), NULL,
				reinterpret_cast<DLGPROC>(NewGameSelectDlgProc) );

		if( rtn == IDC_LOAD_GAME_BUTTON || 
			(arg != NULL && argIsFilename) )
		{
			LoadGame( arg );
			::SetCurrentDirectory( currentDirectory );
		}
		else if( rtn == IDCANCEL )
		{
			goto done;
		}
		else
		{
			//	look for the selected game
			int i;
			for( i = 0; true; i++ )
			{
				GameInformation &game = supportedVariants[i];
				if( game.name == NULL )
					//	no more entries in supportedVariants array
					break;
				if( stringCompNoCase( game.name, gameSelection ) )
				{
//					strcpy( gameSelection, game.name );
					selectedVariant = &(supportedVariants[i]);
					if( selectedVariant->startGameFunction == NULL )
						GenericStartGameFunction( selectedVariant );
					else
						selectedVariant->startGameFunction();
					break;
				}
			}
			if( selectedVariant == NULL )
			{
				//	this must be an included game
				for( int x = 0; x < nIncludedVariants; x++ )
				{
					GameInformation &game = includedVariants[x];
					if( stringCompNoCase( game.name, gameSelection ) )
					{
						LoadGame( game.filename );
						goto game_loaded;
					}
				}
			}
			theGame = (*selectedVariant->createGameFunction)( *theBoard, *pPlayer0, *pPlayer1 );
			theGame->Initialize();
		}
	}

	delete[] currentDirectory;

game_loaded:
	if( !arg )
		SetRazorAndFutilityMargins(); // [HGM] made into a subroutine

	//	look up settings for this game
	HKEY hkey;
	DWORD dw;
	if( ::RegCreateKeyEx( HKEY_CURRENT_USER, "Software", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, &dw ) != ERROR_SUCCESS )
		::MessageBox( theWindow, "Couldn't open registry key.", "ERROR", MB_ICONHAND | MB_OK );
	else
	{
		HKEY hChessVKey;
		if( ::RegCreateKeyEx( hkey, "ChessV", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hChessVKey, &dw ) != ERROR_SUCCESS )
			::MessageBox( theWindow, "Couldn't open registry key.", "ERROR", MB_ICONHAND | MB_OK );
		else
		{
			if( ::RegCreateKeyEx( hChessVKey, selectedVariant->name, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hVariantKey, &dw ) != ERROR_SUCCESS )
				::MessageBox( theWindow, "Couldn't open registry key.", "ERROR", MB_ICONHAND | MB_OK );
			else
			{
				if( dw == REG_CREATED_NEW_KEY )
				{
					theGame->DefaultSettings();
					int checkered = boardDisplayType != BOARD_IS_UNCHECKERED;
					::RegSetValueEx( hVariantKey, "SquareColor1", 0, REG_DWORD, (BYTE *) &squareColor1, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "SquareColor2", 0, REG_DWORD, (BYTE *) &squareColor2, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "SquareColor3", 0, REG_DWORD, (BYTE *) &squareColor3, sizeof(DWORD) );
					if( squareTexture1 >= 0 )
						::RegSetValueEx( hVariantKey, "SquareTexture1", 0, REG_SZ, (BYTE *) textures[squareTexture1], (DWORD) strlen(textures[squareTexture1]) + 1 );
					if( squareTexture2 >= 0 )
						::RegSetValueEx( hVariantKey, "SquareTexture2", 0, REG_SZ, (BYTE *) textures[squareTexture2], (DWORD) strlen(textures[squareTexture2]) + 1 );
					if( squareTexture3 >= 0 )
						::RegSetValueEx( hVariantKey, "SquareTexture3", 0, REG_SZ, (BYTE *) textures[squareTexture3], (DWORD) strlen(textures[squareTexture3]) + 1 );
					::RegSetValueEx( hVariantKey, "PieceColor1", 0, REG_DWORD, (BYTE *) &pieceColor1, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "PieceColor2", 0, REG_DWORD, (BYTE *) &pieceColor2, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "BorderColor", 0, REG_DWORD, (BYTE *) &borderColor, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "PieceSet", 0, REG_DWORD, (BYTE *) &selectedPieceSet, sizeof(DWORD) );
					::RegSetValueEx( hVariantKey, "BoardDisplayType", 0, REG_DWORD, (BYTE *) &boardDisplayType, sizeof(DWORD) );
					if( gameSpecificBoardDisplayDescription != NULL )
					{
						if( gameSpecificBoardDisplay )
							::RegSetValueEx( hVariantKey, "GameSpecificBoardDisplay", 0, REG_SZ, (BYTE *) "On", (DWORD) 3 );
						else
							::RegSetValueEx( hVariantKey, "GameSpecificBoardDisplay", 0, REG_SZ, (BYTE *) "Off", (DWORD) 4 );
					}
				}
				else
				{
					DWORD sz = (DWORD) sizeof(COLORREF);
					char szRegValue[60];
					sz = 60;
					if( ::RegQueryValueEx( hVariantKey, "SquareTexture1", NULL, NULL, (BYTE *) szRegValue, &sz ) == ERROR_SUCCESS )
					{
						szRegValue[sz] = '\0';
						LoadSquareTextureBitmaps( 0, szRegValue );
					}
					::RegQueryValueEx( hVariantKey, "SquareColor1", NULL, NULL, (BYTE *) &squareColor1, &sz );
					sz = 60;
					if( ::RegQueryValueEx( hVariantKey, "SquareTexture2", NULL, NULL, (BYTE *) szRegValue, &sz ) == ERROR_SUCCESS )
					{
						szRegValue[sz] = '\0';
						LoadSquareTextureBitmaps( 1, szRegValue );
					}
					::RegQueryValueEx( hVariantKey, "SquareColor2", NULL, NULL, (BYTE *) &squareColor2, &sz );
					sz = 60;
					if( ::RegQueryValueEx( hVariantKey, "SquareTexture3", NULL, NULL, (BYTE *) szRegValue, &sz ) == ERROR_SUCCESS )
					{
						szRegValue[sz] = '\0';
						LoadSquareTextureBitmaps( 2, szRegValue );
					}
					::RegQueryValueEx( hVariantKey, "SquareColor3", NULL, NULL, (BYTE *) &squareColor3, &sz );
					::RegQueryValueEx( hVariantKey, "PieceColor1", NULL, NULL, (BYTE *) &pieceColor1, &sz );
					::RegQueryValueEx( hVariantKey, "PieceColor2", NULL, NULL, (BYTE *) &pieceColor2, &sz );
					::RegQueryValueEx( hVariantKey, "BorderColor", NULL, NULL, (BYTE *) &borderColor, &sz );
					::RegQueryValueEx( hVariantKey, "PieceSet", NULL, NULL, (BYTE *) &selectedPieceSet, &sz );
					if( ::RegQueryValueEx( hVariantKey, "BoardDisplayType", NULL, NULL, (BYTE *) &boardDisplayType, &sz ) != ERROR_SUCCESS )
						boardDisplayType = BOARD_IS_CHECKERED;
					if( ::RegQueryValueEx( hVariantKey, "GameSpecificBoardDisplay", NULL, NULL, (BYTE *) szRegValue, &sz ) == ERROR_SUCCESS )
					{
						szRegValue[sz] = '\0';
						if( stringCompNoCase( szRegValue, "Off" ) )
							gameSpecificBoardDisplay = false;
					}
				}
			}
			::RegCloseKey( hChessVKey );
		}
		::RegCloseKey( hkey );
	}

	theBoard->PrepareGame();

	// Perform application initialization:
	if( !InitInstance( hInstance, nCmdShow ) )
	{
		return FALSE;
	}

#ifdef _WINBOARD_VERSION
	if( arg ) 
		CreateThread( NULL, 0, WinBoardInput, NULL, 0, NULL ); // [HGM] watch stdin
#endif

	hAccelTable = ::LoadAccelerators( hInstance, (LPCTSTR) IDC_CHESSV );

	// Main message loop:
	while( ::GetMessage( &msg, NULL, 0, 0 ) )
	{
		if( !TranslateAccelerator( msg.hwnd, hAccelTable, &msg ) )
		{
			::TranslateMessage( &msg );
			::DispatchMessage( &msg );
		}
	}
done:
	CleanupGameParameters();
	delete[] gameParameters;
	if( selectedVariant == &adHocGameInformation )
	{
		delete[] selectedVariant->name;
		delete[] selectedVariant->baseGameName;
	}

	return (int) msg.wParam;
}

#ifdef _WINBOARD_VERSION
// [HGM] run as console app to have stdin and stdout open
int main( int argc, char **argv )
{
	if( argc > 1 )
		arg = argv[1];
	return WinMain( NULL, NULL, NULL, SW_SHOWDEFAULT );
}
#endif

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
//  COMMENTS:
//
//    This function and its usage are only necessary if you want this code
//    to be compatible with Win32 systems prior to the 'RegisterClassEx'
//    function that was added to Windows 95. It is important to call this function
//    so that the application will get 'well formed' small icons associated
//    with it.
//
ATOM MyRegisterClass( HINSTANCE hInstance )
{
	WNDCLASSEX wcex;
	WNDCLASSEX wcex_piece_properties;
	HBRUSH blackBrush = ::CreateSolidBrush( RGB(0, 0, 0) );

	wcex.cbSize = sizeof( WNDCLASSEX );
	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= (WNDPROC) WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= ::LoadIcon( hInstance, (LPCTSTR)IDI_CHESSV );
	wcex.hCursor		= ::LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= blackBrush;
	wcex.lpszMenuName	= (LPCTSTR) IDC_CHESSV;
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= ::LoadIcon( wcex.hInstance, (LPCTSTR) IDI_SMALL );
	ATOM rtn = ::RegisterClassEx( &wcex );

	wcex_piece_properties.cbSize = sizeof( WNDCLASSEX );
	wcex_piece_properties.style			= CS_HREDRAW | CS_VREDRAW;
	wcex_piece_properties.lpfnWndProc	= (WNDPROC) PiecePropertiesWndProc;
	wcex_piece_properties.cbClsExtra	= 0;
	wcex_piece_properties.cbWndExtra	= 0;
	wcex_piece_properties.hInstance		= hInstance;
	wcex_piece_properties.hIcon			= ::LoadIcon( hInstance, (LPCTSTR) IDI_CHESSV );
	wcex_piece_properties.hCursor		= ::LoadCursor( NULL, IDC_ARROW );
	wcex_piece_properties.hbrBackground	= blackBrush;
	wcex_piece_properties.lpszMenuName	= NULL;
	wcex_piece_properties.lpszClassName	= "piece_properties";
	wcex_piece_properties.hIconSm		= ::LoadIcon( wcex.hInstance, (LPCTSTR) IDI_SMALL );
	propWndClass = ::RegisterClassEx( &wcex_piece_properties );

	return rtn;
}

BOOL InitInstance
	( HINSTANCE hInstance,
	  int nCmdShow )
{
	hInst = hInstance; // Store instance handle in our global variable

#ifdef _WINBOARD_VERSION
	mainWindow = ::CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
		0, 0, 725, 120, NULL, NULL, hInstance, NULL );
#else
	mainWindow = ::CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
		0, 0, MAX(720, theBoard->GetBoardRightEdge() + 250), MAX(528, theBoard->GetBoardBottomEdge() + 130), NULL, NULL, hInstance, NULL );
#endif

	if( !mainWindow )
	{
		return FALSE;
	}

	::ShowWindow( mainWindow, nCmdShow );

	return TRUE;
}

void PaintWindow
	( HWND hWnd,
	  HDC hdc,
	  LPPAINTSTRUCT ps )
{
	//	brushes
	HBRUSH blackBrush = (HBRUSH) ::GetStockObject( BLACK_BRUSH );
	HPEN nullPen = (HPEN) ::GetStockObject( NULL_PEN );

	//	various dimensions
	int nRanks = theBoard->GetNumberOfRanks();
	int nFiles = theBoard->GetNumberOfFiles();
	int topMargin    = 75;
	int leftMargin   = 20;
	RECT rect;

	if( nFiles < 8 )
		leftMargin += 54;

	//	black out the background
	::GetClientRect( hWnd, &rect );
	::SelectObject( hdc, blackBrush );
	::SelectObject( hdc, nullPen );
	::Rectangle( hdc, 0, 0, leftMargin + 1, rect.bottom + 1 );
	::Rectangle( hdc, 0, 0, rect.right + 1, topMargin + 1 );
	::Rectangle( hdc, theBoard->GetBoardRightEdge(),
		0, rect.right + 1, rect.bottom + 1 );
	::Rectangle( hdc, 0, theBoard->GetBoardBottomEdge(),
		rect.right + 1, rect.bottom + 1 );

	char outstr[120];
	::SetTextColor( hdc, RGB(255, 255, 255) );
	::SetBkMode( hdc, TRANSPARENT );

	//	draw thinking information
	if( theBoard != NULL && testingPosition )
	{
		RECT textRect;
		textRect.left = 15;
		textRect.top = 26;
		textRect.right = 600;
		textRect.bottom = 40;
		MovementList &list = theBoard->GetPositionTestList();
		sprintf( outstr, "Testing ... Move %d of %d, Depth %d of %d",
			list.GetCurrentUnorderedMoveIndex() + 1,
			MIN(list.GetCount(), testingMaxMoves),
			theBoard->GetIterDepth(), testingDepth );
		::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE );
		outstr[1] = '\0';
		outstr[0] = '/';
		if( spinner == 1 )
			outstr[0] = '-';
		else if( spinner == 2 )
			outstr[0] = '\\';
		else if( spinner == 3 )
			outstr[0] = '|';
		textRect.top = 45;
		textRect.bottom = 60;
		::DrawText( hdc, outstr, 1, &textRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE );
	}
	else if( theBoard != NULL && theBoard->ShowStats() )
	{
		if( ps->rcPaint.top < 70 )
		{
			RECT textRect;
			__int64 nodes = STAT_LOOKUP(nodeCount);
			__int64 qnodes = STAT_LOOKUP(qnodeCount);
			textRect.left = 15;
			textRect.top = 6;
			textRect.right = 80;
			textRect.bottom = 20;
			::DrawText( hdc, "I-Depth:", 8, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			sprintf( outstr, "%d", theBoard->GetIterDepth() );
			textRect.left = 85;
			textRect.right = 100;
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );

			textRect.left = 120;
			textRect.right = 195;
			::DrawText( hdc, "Nodes:", 6, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 190;
			textRect.right = 275;
			if( nodes >= 10000 )
				sprintf( outstr, "%I64dk", nodes / 1000LL );
			else
				sprintf( outstr, "%I64d", nodes );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.top = 22;
			textRect.bottom = 36;
			textRect.left = 120;
			textRect.right = 195;
			::DrawText( hdc, "Q-Nodes:", 8, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 190;
			textRect.right = 275;
			if( qnodes >= 10000 )
				sprintf( outstr, "%I64dk", qnodes / 1000LL );
			else
				sprintf( outstr, "%I64d", qnodes );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.top = 38;
			textRect.bottom = 52;
			textRect.left = 120;
			textRect.right = 195;
			::DrawText( hdc, "Q-Node %:", 9, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 190;
			textRect.right = 275;
			sprintf( outstr, "%d%%", (int) (((double) qnodes / (double) MAX(nodes, 1)) * 100.0) );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );

			textRect.top = 6;
			textRect.bottom = 20;
			textRect.left = 280;
			textRect.right = 400;
			::DrawText( hdc, "TT hits:", 8, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 405;
			textRect.right = 470;
			if( transpositionTableSize > 0 )
				sprintf( outstr, "%3.3f%%", (double) (STAT_LOOKUP(ttLookupMatchCount) * (double) 100.0 / MAX(STAT_LOOKUP(ttLookupCount),1)) );
			else
				sprintf( outstr, "disabled" );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.top = 22;
			textRect.bottom = 36;
			textRect.left = 280;
			textRect.right = 400;
			::DrawText( hdc, "TT exact:", 9, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 405;
			textRect.right = 470;
			if( transpositionTableSize > 0 )
				sprintf( outstr, "%3.3f%%", (double) (STAT_LOOKUP(ttExactCount) * (double) 100.0 / MAX(STAT_LOOKUP(ttLookupCount),1)) );
			else
				sprintf( outstr, "disabled" );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.top = 38;
			textRect.bottom = 52;
			textRect.left = 280;
			textRect.right = 400;
			::DrawText( hdc, "TT bounds:", 10, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 405;
			textRect.right = 470;
			if( transpositionTableSize > 0 )
				sprintf( outstr, "%3.3f%%", (double) ((STAT_LOOKUP(ttLowerBoundCount) + STAT_LOOKUP(ttUpperBoundCount)) * (double) 100.0 / MAX(STAT_LOOKUP(ttLookupCount),1)) );
			else
				sprintf( outstr, "disabled" );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 280;
			textRect.right = 400;
			textRect.top = 54;
			textRect.bottom = 68;
			::DrawText( hdc, "EC matches:", 11, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 405;
			textRect.right = 470;
			if( evaluationCacheSize > 0 )
				sprintf( outstr, "%3.3f%%", (double) ((STAT_LOOKUP(ecMatchCount)) * (double) 100.0 / ((double) MAX(STAT_LOOKUP(ecLookupCount),1))) );
			else
				sprintf( outstr, "disabled" );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );


			textRect.top = 6;
			textRect.bottom = 20;
			textRect.left = 475;
			textRect.right = 570;
			::DrawText( hdc, "Null cuts:", 10, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 580;
			textRect.right = 680;
			sprintf( outstr, "%I64dk", STAT_LOOKUP(nullCutoffs) / 1000LL );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );

			textRect.top = 22;
			textRect.bottom = 36;
			textRect.left = 475;
			textRect.right = 570;
			::DrawText( hdc, "pruned:", 7, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 580;
			textRect.right = 680;
			sprintf( outstr, "%I64dk", STAT_LOOKUP(nodesPruned) / 1000LL );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.top = 38;
			textRect.bottom = 52;
			textRect.left = 475;
			textRect.right = 570;
			::DrawText( hdc, "razored:", 8, &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
			textRect.left = 580;
			textRect.right = 680;
			sprintf( outstr, "%I64dk", STAT_LOOKUP(nodesRazored) / 1000LL );
			::DrawText( hdc, outstr, (int) strlen(outstr), &textRect, DT_RIGHT | DT_VCENTER | DT_SINGLELINE );
		}
	}

	if( !refreshCausedByBookkeeping && theBoard != NULL )
	{
		theBoard->DrawBoard( hWnd, hdc, ps );
		theBoard->DrawPiecesOnBoard( hWnd, hdc, ps );
	}
}

void MouseButtonPressed( int x, int y )
{
	int square = theBoard->SquareHitTest( x, y );
	if( square >= 0 )
	{
		Piece *piece = theBoard->GetSquareContents( square );
		if( piece != NULL )
		{
			if( piece->GetPlayerNumber() != theBoard->GetCurrentPlayerNumber() )
			{
				::MessageBox( theWindow, "That is not your piece to move.", "ERROR", MB_ICONHAND | MB_OK );
			}
			else
			{
				if( theBoard->IsImmobilized( piece->GetSquareNumber() ) )
				{
					//	see if this piece has a suicide move or not
					int suicideMove = -1;
					MovementList &list = theBoard->GetLegalUserMovementList();
					for( int i = 0; i < list.GetCount(); i++ )
					{
						MoveInfo &move = list.GetMove( i );
						if( move.fromSquare == square &&
							move.type == SuicideMove )
							//	yes, we can commit suicide
							suicideMove = i;
					}
					if( suicideMove >= 0 )
					{
						if( ::MessageBox( theWindow, "That piece is immobilized, but it can commit suicide.\nIs that what you would like to do?",
							"Commit Suicide", MB_ICONHAND | MB_YESNO ) == IDYES )
						{
							theBoard->PerformMove( suicideMove );
							::InvalidateRect( theWindow, NULL, false );
						}
					}
					else
					{
						::MessageBox( theWindow, "That piece cannot move at this time, because it is immobilized.", "ERROR", MB_ICONHAND | MB_OK );
					}
				}
				else
				{
					int moveCount = 0;
					int pseudoLegalMoveCount;
					MovementList &stack = theBoard->GetLegalUserMovementList();
					for( int i = 0; i < stack.GetCount(); i++ )
					{
						MoveInfo &move = stack.GetMove( i );
						if( move.fromSquare == square )
							moveCount++;
					}
					if( moveCount == 0 )
						moveCount = piece->CountMoves( pseudoLegalMoveCount );
					else
						pseudoLegalMoveCount = moveCount;

					if( pseudoLegalMoveCount == 0 )
					{
						::MessageBox( theWindow, "That piece cannot move at this time, because it is trapped.", "ERROR", MB_ICONHAND | MB_OK );
					}
					else if( moveCount == 0 )
					{
						if( theBoard->IsInCheck() )
							::MessageBox( theWindow, "That piece cannot move at this time,  \nbecause your King is in check.", "ERROR", MB_ICONHAND | MB_OK );
						else
							if( piece->GetType().IsRoyal() )
								::MessageBox( theWindow, "Your King cannot move at this time,  \nbecause it would be moving into Check.", "ERROR", MB_ICONHAND | MB_OK );
							else
								::MessageBox( theWindow, "That piece cannot move at this time,  \nbecause it is pinned.", "ERROR", MB_ICONHAND | MB_OK );
					}
					else
					{
						theBoard->LiftPiece( piece );
						::SetCapture( theWindow );
						mouseCaptured = true;
						mouseX = x;
						mouseY = y;
						destinationSquare = -1;
						::InvalidateRect( theWindow, NULL, false );
					}
				}
			}
		}
	}
}

void MouseButtonReleased( int x, int y )
{
	int squareNumber = theBoard->SquareHitTest( x, y );
	theBoard->ClearAnyExtraCaptures();
	if( mouseCaptured )
	{
		::ReleaseCapture();
		mouseCaptured = false;
	}
	if( theBoard->GetPieceBeingLifted() != NULL )
	{
		theBoard->DropPiece( squareNumber );
		::InvalidateRect( theWindow, NULL, false );
	}
}

void MouseMoved( int x, int y )
{
	if( mouseCaptured )
	{
		RECT r;
		r.left = mouseX - 40;
		r.right = mouseX + 40;
		r.top = mouseY - 40;
		r.bottom = mouseY + 40;
		::InvalidateRect( theWindow, &r, false );
		r.left = x - 40;
		r.right = x + 40;
		r.top = y - 40;
		r.bottom = y + 40;
		::InvalidateRect( theWindow, &r, false );
		mouseX = x;
		mouseY = y;
		int squareNumber = theBoard->SquareHitTest( mouseX, mouseY );
		if( squareNumber >= 0 && squareNumber != destinationSquare &&
			!theBoard->CanMoveToSquare( squareNumber, true ) )
		{
			theBoard->ClearAnyExtraCaptures();
			squareNumber = -1;
		}
		if( squareNumber != destinationSquare )
		{
			int oldDestinationSquare = destinationSquare;
			destinationSquare = squareNumber;
			if( oldDestinationSquare != -1 )
				theBoard->InvalidateSquare( oldDestinationSquare );
			if( destinationSquare != -1 )
				theBoard->InvalidateSquare( destinationSquare );
		}
	}
}

char *fileContents = NULL;
int lineToPlayOffset;

bool TranslateBoolValue
	( char *text )
{
	char upperCaseText[6];
	int length;
	for( length = 0;
		((text[length] >= 'a' && text[length] <= 'z') ||
		 (text[length] >= 'A' && text[length] <= 'Z'));
		length++ )
	{
		upperCaseText[length] = toupper( text[length] );
	}
	upperCaseText[length] = '\0';
	if( !strcmp( upperCaseText, "TRUE" ) )
		return true;
	if( !strcmp( upperCaseText, "YES" ) )
		return true;
	if( !strcmp( upperCaseText, "FALSE" ) )
		return false;
	if( !strcmp( upperCaseText, "NO" ) )
		return false;
	if( !strcmp( upperCaseText, "NONE" ) )
		return false;

	//	ERROR!  invalid value provided
	::MessageBox( NULL, "Illegal boolean value provided in save game file", "FATAL ERROR",
		MB_ICONHAND | MB_OK );

	return false;
}

void CreatePlayers
	( char *player0Name,
	  char *player1Name,
	  bool player0ComputerControlled,
	  bool player1ComputerControlled )
{
	char p0Name[40];
	char p1Name[40];
	strcpy( p0Name, player0Name );
	strcpy( p1Name, player1Name );
	char *lookupValue;
	char playerValue[80];
	if( LookupStringParameter( "players", lookupValue ) )
	{
		strcpy( playerValue, lookupValue );
		int start;
		for( start = 0; playerValue[start] == ' ' || playerValue[start] == '\t'; start++ )
			;
		int end;
		for( end = start; playerValue[end] != '\0' && playerValue[end] != ';'; end++ )
			;
		for( ; playerValue[end-1] == ' '; end-- )
			;
        playerValue[end] = '\0';
		strcpy( p0Name, playerValue + start );
		for( start = end + 2; playerValue[start] == ' ' || playerValue[start] == '\t'; start++ )
			;
		for( end = start; playerValue[end] != '\0' && playerValue[end] != '\n'; end++ )
			;
		for( ; playerValue[end-1] == ' '; end-- )
			;
        playerValue[end] = '\0';
		strcpy( p1Name, playerValue + start );
	}
	pPlayer0 = new Player( p0Name, 0, player0ComputerControlled );
	pPlayer1 = new Player( p1Name, 1, player1ComputerControlled );
}

#define ENCOUNTERED_END_OF_FILE                       1
#define ENCOUNTERED_NEW_LINE                          5
#define ENCOUNTERED_OPEN_BRACE                        8
#define ENCOUNTERED_OPEN_PAREN                        9
#define ENCOUNTERED_COLON                            10
#define ENCOUNTERED_CLOSE_BRACE                      11
#define ENCOUNTERED_CLOSE_PAREN                      12
#define ENCOUNTERED_COMMA                            13


int ParseGameName
	( char *filename,
	  char *text,
	  int &cursor,
	  int &tokenStart,
	  int length )
{
	//	skip whitespace
	for( ; cursor < length && (text[cursor] == ' ' || text[cursor] == '\t' || text[cursor] == '\n' || text[cursor] == '\r'); cursor++ )
		;

	//	scan name of game
	tokenStart = cursor;
	for( ; ; cursor++ )
	{
		if( cursor >= length )
			return ENCOUNTERED_END_OF_FILE;
		else if( text[cursor] == ':' )
		{
			text[cursor] = '\0';
			cursor++;
			return ENCOUNTERED_COLON;
		}
		else if( text[cursor] == '\r' || text[cursor] == '\012' || text[cursor] == '\n' )
		{
			text[cursor++] = '\0';
			//	skip any additional newline characters
			for( ; text[cursor] == '\r' || text[cursor] == '\012' || text[cursor] == '\n'; cursor++ )
				;
			return ENCOUNTERED_NEW_LINE;
		}
		else if( text[cursor] == '(' )
			return ENCOUNTERED_OPEN_PAREN;
		else if( text[cursor] == '{' )
			return ENCOUNTERED_OPEN_BRACE;
	}
	ASSERT(FALSE);
	return 0;
}

int SkipWhitespace
	( char *text,
	  int &cursor,
	  int length,
	  bool skipNewlines )
{
	for( ; cursor < length && (text[cursor] == ' ' || text[cursor] == '\t' ||
		(text[cursor] == '\n' && skipNewlines) ||
		(text[cursor] == '\r' && skipNewlines)); cursor++ )
		;
	if( cursor >= length )
		return ENCOUNTERED_END_OF_FILE;
	return 0;
}

int ParseVariableName
	( char *text,
	  int &cursor,
	  int length )
{
	while( cursor < length &&
		   ((text[cursor] >= 'a' && text[cursor] <= 'z') ||
			(text[cursor] >= 'A' && text[cursor] <= 'Z') ||
			(text[cursor] >= '0' && text[cursor] <= '9') ||
			 text[cursor] == '-' || text[cursor] == '_') )
		cursor++;
	if( cursor >= length )
		return ENCOUNTERED_END_OF_FILE;
	if( text[cursor] == '(' )
		return ENCOUNTERED_OPEN_PAREN;
	if( text[cursor] == '{' )
		return ENCOUNTERED_OPEN_BRACE;
	if( text[cursor] == ')' )
		return ENCOUNTERED_CLOSE_PAREN;
	if( text[cursor] == '}' )
		return ENCOUNTERED_CLOSE_BRACE;
	if( text[cursor] == ',' )
		return ENCOUNTERED_COMMA;
	if( text[cursor] == ':' )
		return ENCOUNTERED_COLON;
	return 0;
}

#define FUNCTION_WITH_1_ARGUMENT(functionName, cName, arg1Type, priority)                                                             \
{	functionName, 1, { arg1Type, 0, 0, 0, 0, 0 }, cName, priority   },

#define FUNCTION_WITH_2_ARGUMENTS(functionName, cName, arg1Type, arg2Type, priority)                                                  \
{	functionName, 2, { arg1Type, arg2Type, 0, 0, 0, 0 }, cName, priority   },

#define FUNCTION_WITH_3_ARGUMENTS(functionName, cName, arg1Type, arg2Type, arg3Type, priority)                                               \
{	functionName, 3, { arg1Type, arg2Type, arg3Type, 0, 0, 0 }, cName, priority   },

#define FUNCTION_WITH_4_ARGUMENTS(functionName, cName, arg1Type, arg2Type, arg3Type, arg4Type, priority)                                     \
{	functionName, 4, { arg1Type, arg2Type, arg3Type, arg4Type, 0, 0 }, cName, priority   },

int ChangePieceName
	( char *existingName,
	  char *newName,
	  char *newNotation,
	  Game *pGame,
	  Board *pBoard )
{
	PieceType *typeIterator = PieceType::GetFirstType();
	while( typeIterator != NULL )
	{
		if( stringCompNoCase( typeIterator->GetExtendedName(), existingName ) &&
			typeIterator->GetNumberOfFiles() == pBoard->GetNumberOfFiles() &&
			typeIterator->GetNumberOfRanks() == pBoard->GetNumberOfRanks() )
		{
			//	allocate space for and copy new names
			int newNameSize = (int) strlen(newName) + 1;
			char *fullName = new char[newNameSize];
			strcpy( fullName, newName );
			typeIterator->SetFullName( fullName );
			int abbrevSize = (int) strlen(newNotation) + 1;
			char *abbrev = new char[abbrevSize];
			strcpy( abbrev, newNotation );
			typeIterator->SetNotation( abbrev );
			return 0;
		}
		typeIterator = typeIterator->GetNextType();
	}
	return -1;
}

int ChangePieceValue
	( char *existingName,
	  int newBaseValue,
	  int newPerCaptureValueBonus,
	  int newPerCaptureValueThreshold,
	  Game *pGame,
	  Board *pBoard )
{
	PieceType *typeIterator = PieceType::GetFirstType();
	while( typeIterator != NULL )
	{
		if( stringCompNoCase( typeIterator->GetExtendedName(), existingName ) &&
			typeIterator->GetNumberOfRanks() == pBoard->GetNumberOfRanks() &&
			typeIterator->GetNumberOfFiles() == pBoard->GetNumberOfFiles() )
		{
			typeIterator->SetBaseValue( newBaseValue );
			typeIterator->SetPerCaptureValueBonus( newPerCaptureValueBonus );
			typeIterator->SetPerCaptureBonusThreshold( newPerCaptureValueThreshold );
			return 0;
		}
		typeIterator = typeIterator->GetNextType();
	}
	return -1;
}

int RemovePieceType
	( char *existingName,
	  Game *pGame,
	  Board *pBoard )
{
	PieceType *typeIterator = PieceType::GetFirstType();
	while( typeIterator != NULL )
	{
		if( stringCompNoCase( typeIterator->GetExtendedName(), existingName ) &&
			typeIterator->GetNumberOfRanks() == pBoard->GetNumberOfRanks() &&
			typeIterator->GetNumberOfFiles() == pBoard->GetNumberOfFiles() )
		{
			if( pGame->RemovePieceType( typeIterator ) )
			{
				if( pBoard->RemovePieceType( *typeIterator ) )
					return 0;
			}
			return -1;
		}
		typeIterator = typeIterator->GetNextType();
	}
	return -1;
}

int AddPieceType
	( char *existingName,
	  Game *pGame,
	  Board *pBoard )
{
	PieceType *typeIterator = PieceType::GetFirstType();
	while( typeIterator != NULL )
	{
		if( stringCompNoCase( typeIterator->GetExtendedName(), existingName ) &&
			typeIterator->GetNumberOfRanks() == pBoard->GetNumberOfRanks() &&
			typeIterator->GetNumberOfFiles() == pBoard->GetNumberOfFiles() )
		{
			if( pGame->AddPieceType( typeIterator, 0 ) )
			{
				if( pGame->AddPieceType( typeIterator, 1 ) )
				{
					pBoard->AddPlayerPieceTypeBothPlayers( *typeIterator );
					return 0;
				}
			}
			return -1;
		}
		typeIterator = typeIterator->GetNextType();
	}
	return -1;
}

FunctionDecl functionDeclarations[] =
{
	FUNCTION_WITH_1_ARGUMENT("remove-piece-type", RemovePieceType, FN_ARG_TYPE__STRING, 12)
	FUNCTION_WITH_1_ARGUMENT("add-piece-type", AddPieceType, FN_ARG_TYPE__STRING, 14)
	FUNCTION_WITH_3_ARGUMENTS("change-piece-name", ChangePieceName, FN_ARG_TYPE__STRING, FN_ARG_TYPE__STRING, FN_ARG_TYPE__STRING, 16)
	FUNCTION_WITH_4_ARGUMENTS("change-piece-value", ChangePieceValue, FN_ARG_TYPE__STRING, FN_ARG_TYPE__INTEGER, FN_ARG_TYPE__INTEGER, FN_ARG_TYPE__INTEGER, 16)
};

void QueueRemovePieceTypeFunctionCall
	( char *pieceName )
{
	//	find correct function declaration
	FunctionDecl *decl = functionDeclarations;
	while( !stringCompNoCase( decl->name, "remove-piece-type" ) )
		decl++;
	//	add call to function
	functionCalls[nFunctionCalls].pFunctionDecl = decl;
	functionCalls[nFunctionCalls].args[0].stringArg = pieceName;
	nFunctionCalls++;
}

void QueueAddPieceTypeFunctionCall
	( char *pieceName )
{
	//	find correct function declaration
	FunctionDecl *decl = functionDeclarations;
	while( !stringCompNoCase( decl->name, "add-piece-type" ) )
		decl++;
	//	add call to function
	functionCalls[nFunctionCalls].pFunctionDecl = decl;
	functionCalls[nFunctionCalls].args[0].stringArg = pieceName;
	nFunctionCalls++;
}

int ParseStringArgument
	( char *text,
	  int &cursor,
	  int &tokenStart,
	  int length )
{
	//	skip whitespace
	for( ; cursor < length && (text[cursor] == ' ' || text[cursor] == '\t'); cursor++ )
		;

	//	match double-quote
	if( cursor == length )
	{
		::MessageBox( theWindow, "Unexpected end-of-file encountered; string argument expected",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}
	if( text[cursor] != '"' )
	{
		::MessageBox( theWindow, "Error in string argument, double-quote expected",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}
	tokenStart = ++cursor;

	//	search for close-quotes or newline
	for( ; cursor < length &&
		(text[cursor] != '"' && text[cursor] != '\n' && text[cursor] != '\r'); cursor++ )
		 ;

	if( cursor == length )
	{
		::MessageBox( theWindow, "Unexpected end-of-file encountered in string constant",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}
	if( text[cursor] == '\n' || text[cursor] == '\r' )
	{
		::MessageBox( theWindow, "Unexpected new-line encountered in string constant",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}

	text[cursor++] = '\0';
	return 0;
}

int ParseIntegerArgument
	( char *text,
	  int &cursor,
	  int &tokenStart,
	  int length )
{
	//	skip whitespace
	for( ; cursor < length && (text[cursor] == ' ' || text[cursor] == '\t'); cursor++ )
		;

	if( cursor == length )
	{
		::MessageBox( theWindow, "Unexpected end-of-file encountered; string argument expected",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}
	tokenStart = cursor;
	while( text[cursor] >= '0' && text[cursor] <= '9' && cursor < length )
		cursor++;

	if( tokenStart == cursor )
	{
		::MessageBox( theWindow, "Error in integer constant",
			"Game Load ERROR", MB_ICONHAND | MB_OK );
		return -1;
	}
	if( cursor == length || text[cursor] == '\n' || text[cursor] == '\r' )
	{
		text[cursor] = '\0';
		return 0;
	}
	if( text[cursor] == ' ' || text[cursor] == '\t' )
	{
		while( text[cursor] == ' ' || text[cursor] == '\t' )
			cursor++;
		if( cursor == length || text[cursor] == '\n' || text[cursor] == '\r' )
		{
			text[cursor] = '\0';
			return 0;
		}
	}
	if( text[cursor] == ',' )
	{
		text[cursor] = '\0';
		return ENCOUNTERED_COMMA;
	}
	if( text[cursor] == ')' )
	{
		text[cursor] = '\0';
		return ENCOUNTERED_CLOSE_PAREN;
	}
	::MessageBox( theWindow, "Unexpected character encountered in integer variable",
		"Game Load ERROR", MB_ICONHAND | MB_OK );
	return -1;
}

int nFunctionCalls = 0;
FunctionCall functionCalls[100];

int ParseFunctionCall
	( FunctionDecl *fn,
	  char *filename,
	  char *text,
	  int &cursor,
	  int length )
{
	int tokenStart = cursor;
	functionCalls[nFunctionCalls].pFunctionDecl = fn;
	for( int n = 0; n < fn->nArgs; n++ )
	{
		if( fn->argType[n] == FN_ARG_TYPE__STRING )
		{
			if( ParseStringArgument( text, cursor, tokenStart, length ) != 0 )
				return -1;
			functionCalls[nFunctionCalls].args[n].stringArg = text + tokenStart;
			if( SkipWhitespace( text, cursor, length,  false ) == ENCOUNTERED_END_OF_FILE )
			{
			}
			if( n == fn->nArgs - 1 && text[cursor++] != ')' )
			{
			}
			if( n < fn->nArgs - 1 && text[cursor++] != ',' )
			{
			}
		}
		if( fn->argType[n] == FN_ARG_TYPE__INTEGER )
		{
			int rtnCode = ParseIntegerArgument( text, cursor, tokenStart, length );
			if( rtnCode < 0 )
				return -1;
			if( rtnCode == ENCOUNTERED_END_OF_FILE )
			{
			}
			if( n == fn->nArgs - 1 && rtnCode != ENCOUNTERED_CLOSE_PAREN )
			{
			}
			if( n < fn->nArgs - 1 && rtnCode != ENCOUNTERED_COMMA )
			{
			}
			functionCalls[nFunctionCalls].args[n].integerArg = atoi( text + tokenStart );
			cursor++;
		}
		if( fn->argType[n] == FN_ARG_TYPE__BOOLEAN )
		{
		}
	}
	nFunctionCalls++;
	return 0;
}

int CallFunction
	( int nFunction,
	  Game *pGame,
	  Board *pBoard )
{
	FunctionCall *call = &(functionCalls[nFunction]);
	FunctionDecl *fn = call->pFunctionDecl;

	//	call function, based on signature
	if( fn->nArgs == 1 )
	{
		if( fn->argType[0] == FN_ARG_TYPE__STRING )
		{
			int (*pFunction)( char *, Game *, Board * ) = (int (*)( char *, Game *, Board * ))(fn->pFunction);
			return (*pFunction)( call->args[0].stringArg, pGame, pBoard );
		}
	}
	else if( fn->nArgs == 2 )
	{
	}
	else if( fn->nArgs == 3 )
	{
		if( fn->argType[0] == FN_ARG_TYPE__STRING )
		{
			if( fn->argType[1] == FN_ARG_TYPE__STRING )
			{
				if( fn->argType[2] == FN_ARG_TYPE__STRING )
				{
					int (*pFunction)( char *, char *, char *, Game *, Board * ) = (int (*)( char *, char *, char *, Game *, Board * ))(fn->pFunction);
					return (*pFunction)( call->args[0].stringArg, call->args[1].stringArg, call->args[2].stringArg, pGame, pBoard );
				}
			}
		}
	}
	else if( fn->nArgs == 4 )
	{
		if( fn->argType[0] == FN_ARG_TYPE__STRING )
		{
			if( fn->argType[1] == FN_ARG_TYPE__INTEGER )
			{
				if( fn->argType[2] == FN_ARG_TYPE__INTEGER )
				{
					if( fn->argType[3] == FN_ARG_TYPE__INTEGER )
					{
						int (*pFunction)( char *, int, int, int, Game *, Board * ) = (int (*)( char *, int, int, int, Game *, Board * ))(fn->pFunction);
						return (*pFunction)( call->args[0].stringArg, call->args[1].integerArg, call->args[2].integerArg, call->args[3].integerArg, pGame, pBoard );
					}
				}
			}
		}
	}
	else if( fn->nArgs == 5 )
	{
	}
	else if( fn->nArgs == 6 )
	{
	}
	return -1;
}

int CallFunctions
	( int GEPriority,			//	functions with priority greater-than-or-equal-to this
	  int LTPriority, 			//	and with priority less-than this
	  Game *pGame,				//	pointer to the Game object
	  Board *pBoard )			//	pointer to the Board object
{
	for( int priority = GEPriority; priority < LTPriority; priority++ )
	{
		for( int x = 0; x < nFunctionCalls; x++ )
		{
			if( functionCalls[x].pFunctionDecl->priority == priority )
			{
				int rtn = CallFunction( x, pGame, pBoard );
				if( rtn < 0 )
					return rtn;
			}
		}
	}
	return 0;
}

int ParseInstruction
	( char *filename,
	  char *text,
	  int &cursor,
	  int &tokenStart,
	  int length )
{
	//	skip whitespace
	SkipWhitespace( text, cursor, length, true );

	//	check for end-of-file
	if( cursor >= length )
		return ENCOUNTERED_END_OF_FILE;

	char *variableName;
	int returnCode;
	int tokenEnd;
	char firstChar = text[cursor];
    switch( firstChar )
	{
	  case '$':
		cursor++;
		tokenStart = cursor;
		returnCode = ParseVariableName( text, cursor, length );
		tokenEnd = cursor;
		if( returnCode != ENCOUNTERED_END_OF_FILE )
			returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( tokenEnd == tokenStart )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected variable name following '$'", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( text[cursor++] != '=' )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected '='", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		text[tokenEnd] = '\0';
		variableName = text + tokenStart;
		returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		tokenStart = cursor;
		if( ParseStringArgument( text, cursor, tokenStart, length ) != 0 )
			return -1;
		cursor = cursor + 1;
		StoreParameter( variableName, text + tokenStart );
		if( cursor >= length )
			return ENCOUNTERED_END_OF_FILE;
		return ENCOUNTERED_NEW_LINE;

	  case '?':
		cursor++;
		tokenStart = cursor;
		returnCode = ParseVariableName( text, cursor, length );
		tokenEnd = cursor;
		if( returnCode != ENCOUNTERED_END_OF_FILE )
			returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( tokenEnd == tokenStart )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected variable name following '$'", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( text[cursor++] != '=' )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected '='", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		text[tokenEnd] = '\0';
		variableName = text + tokenStart;
		returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		tokenStart = cursor;
		while( cursor < length && text[cursor] != '\r' && text[cursor] != '\n' && text[cursor] != '\012' &&
			text[cursor] != ' ' && text[cursor] != '\t' )
			cursor++;
		if( returnCode != ENCOUNTERED_END_OF_FILE )
		{
			if( cursor == tokenStart + 4 &&
				toupper(text[tokenStart]) == 'T' &&
				toupper(text[tokenStart+1]) == 'R' &&
				toupper(text[tokenStart+2]) == 'U' &&
				toupper(text[tokenStart+3]) == 'E' &&
				(text[tokenStart+4] == ' ' ||
				 text[tokenStart+4] == '\t' || text[tokenStart+4] == '\n' ||
				 text[tokenStart+4] == '\r' || text[tokenStart+4] == '\012') )
			{
				SkipWhitespace( text, cursor, length, false );
				if( text[cursor] != '\n' && text[cursor] != '\r' && text[cursor] != '\012' )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unexpected text in boolean definition", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				StoreParameter( variableName, true );
				return ENCOUNTERED_NEW_LINE;
			}
			else if( cursor == tokenStart + 3 &&
				toupper(text[tokenStart]) == 'Y' &&
				toupper(text[tokenStart+1]) == 'E' &&
				toupper(text[tokenStart+2]) == 'S' &&
				(text[tokenStart+3] == ' ' ||
				 text[tokenStart+3] == '\t' || text[tokenStart+3] == '\n' ||
				 text[tokenStart+3] == '\r' || text[tokenStart+3] == '\012') )
			{
				SkipWhitespace( text, cursor, length, false );
				if( text[cursor] != '\n' && text[cursor] != '\r' && text[cursor] != '\012' )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unexpected text in boolean definition", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				StoreParameter( variableName, true );
				return ENCOUNTERED_NEW_LINE;
			}
			else if( cursor == tokenStart + 5 &&
				toupper(text[tokenStart]) == 'F' &&
				toupper(text[tokenStart+1]) == 'A' &&
				toupper(text[tokenStart+2]) == 'L' &&
				toupper(text[tokenStart+3]) == 'S' &&
				toupper(text[tokenStart+4]) == 'E' &&
				(text[tokenStart+5] == ' ' ||
				 text[tokenStart+5] == '\t' || text[tokenStart+5] == '\n' ||
				 text[tokenStart+5] == '\r' || text[tokenStart+5] == '\012') )
			{
				SkipWhitespace( text, cursor, length, false );
				if( text[cursor] != '\n' && text[cursor] != '\r' && text[cursor] != '\012' )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unexpected text in boolean definition", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				StoreParameter( variableName, false );
				return ENCOUNTERED_NEW_LINE;
			}
			else if( cursor == tokenStart + 2 &&
				toupper(text[tokenStart]) == 'N' &&
				toupper(text[tokenStart+1]) == 'O' &&
				(text[tokenStart+2] == ' ' ||
				 text[tokenStart+2] == '\t' || text[tokenStart+2] == '\n' ||
				 text[tokenStart+2] == '\r' || text[tokenStart+2] == '\012') )
			{
				SkipWhitespace( text, cursor, length, false );
				if( text[cursor] != '\n' && text[cursor] != '\r' && text[cursor] != '\012' )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unexpected text in boolean definition", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				StoreParameter( variableName, false );
				return ENCOUNTERED_NEW_LINE;
			}
			else if( cursor == tokenStart + 4 &&
				toupper(text[tokenStart]) == 'N' &&
				toupper(text[tokenStart+1]) == 'O' &&
				toupper(text[tokenStart+2]) == 'N' &&
				toupper(text[tokenStart+3]) == 'E' &&
				(text[tokenStart+4] == ' ' ||
				 text[tokenStart+4] == '\t' || text[tokenStart+4] == '\n' ||
				 text[tokenStart+4] == '\r' || text[tokenStart+4] == '\012') )
			{
				SkipWhitespace( text, cursor, length, false );
				if( text[cursor] != '\n' && text[cursor] != '\r' && text[cursor] != '\012' )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unexpected text in boolean definition", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				StoreParameter( variableName, false );
				return ENCOUNTERED_NEW_LINE;
			}
		}
		if( cursor >= length )
			return ENCOUNTERED_END_OF_FILE;
		return ENCOUNTERED_NEW_LINE;

	  case '#':
		cursor++;
		tokenStart = cursor;
		returnCode = ParseVariableName( text, cursor, length );
		tokenEnd = cursor;
		if( returnCode != ENCOUNTERED_END_OF_FILE )
			returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( tokenEnd == tokenStart )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected variable name following '$'", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		if( text[cursor++] != '=' )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; expected '='", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		text[tokenEnd] = '\0';
		variableName = text + tokenStart;
		returnCode = SkipWhitespace( text, cursor, length, false );
		if( returnCode == ENCOUNTERED_END_OF_FILE )
		{
			char errorMessage[200];
			sprintf( errorMessage, "Error in game file %s; unexpected end-of-line", filename );
			::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		tokenStart = cursor;
		if( ParseIntegerArgument( text, cursor, tokenStart, length ) != 0 )
			return -1;
		cursor = cursor + 1;
		StoreParameter( variableName, atoi(text + tokenStart) );
		if( cursor >= length )
			return ENCOUNTERED_END_OF_FILE;
		return ENCOUNTERED_NEW_LINE;

	  case '{':
		return ENCOUNTERED_OPEN_BRACE;

	  default:
		if( (text[cursor] >= 'a' && text[cursor] <= 'z') ||
			(text[cursor] >= 'A' && text[cursor] <= 'Z') )
		{
			tokenStart = cursor;
			returnCode = ParseVariableName( text, cursor, length );
			if( returnCode == ENCOUNTERED_OPEN_PAREN )
			{
				//	skip the open paren
				cursor++;
				//	search for the declaration for this function
				FunctionDecl *fn = NULL;
				int fnCount = sizeof(functionDeclarations)/sizeof(functionDeclarations[0]);
				for( int x = 0; x < fnCount; x++ )
				{
					bool match = true;
					for( int y = 0; match && y < (int) strlen(functionDeclarations[x].name); y++ )
					{
						if( toupper(functionDeclarations[x].name[y]) !=
							toupper(text[tokenStart+y]) )
							match = false;
					}
					if( match )
					{
						fn = &(functionDeclarations[x]);
						break;
					}
				}
				if( fn == NULL )
				{
					char errorMessage[200];
					sprintf( errorMessage, "Error in game file %s; unknown function", filename );
					::MessageBox( theWindow, errorMessage, "Game Load ERROR", MB_OK | MB_ICONHAND );
					return -1;
				}
				else
				{
					ParseFunctionCall( fn, filename, text, cursor, length );
				}
			}
			return ENCOUNTERED_NEW_LINE;
		}
	}
	return 0;
}

int ParseFile
	( char *filename,
	  char *text,
	  int &cursor,
	  int length,
	  bool &derivedGame,
	  char *&baseGameName,
	  char *&gameName )
{
	int tokenStart;

	derivedGame = false;
	//	parse game name
	int returnValue = ParseGameName( filename, text, cursor, tokenStart, length );
	gameName = text + tokenStart;
	if( returnValue == ENCOUNTERED_COLON )
	{
		//	this is a derived game; now scan the name of the
		//	base game from which it is derived.
		derivedGame = true;
		returnValue = ParseGameName( filename, text, cursor, tokenStart, length );
		if( returnValue == ENCOUNTERED_COLON )
		{
			char errorMessage[200];
			#ifdef _DEBUG
			ASSERT(FALSE);
			#endif
			sprintf( errorMessage, "Error in game file %s; encountered unexpected ':'", filename );
			::MessageBox( theWindow, errorMessage, "Include ERROR", MB_OK | MB_ICONHAND );
			return -1;
		}
		else if( returnValue == ENCOUNTERED_NEW_LINE )
		{
			baseGameName = text + tokenStart;
			derivedGame = true;
		}
		else if( returnValue == ENCOUNTERED_END_OF_FILE )
		{
			baseGameName = text + tokenStart;
			derivedGame = true;
		}
	}

	//	parse instruction lines one at a time
	bool continueReadingInstructions = true;
	while( continueReadingInstructions )
	{
		returnValue = ParseInstruction( filename, text, cursor, tokenStart, length );
		if( returnValue == ENCOUNTERED_END_OF_FILE )
			continueReadingInstructions = false;
		else
		{
			if( returnValue == ENCOUNTERED_OPEN_BRACE )
			{
				continueReadingInstructions = false;
			}
		}
	}

	return returnValue;
}

GameInformation adHocGameInformation;

void LoadGame( char *filename /* = NULL */ )
{
	OPENFILENAME ofn;       // common dialog box structure
	char szFile[260];       // buffer for filename
	HANDLE hf;              // file handle

	if( filename == NULL )
	{
		// Initialize OPENFILENAME
		ZeroMemory( &ofn, sizeof(OPENFILENAME) );
		ofn.lStructSize = sizeof(OPENFILENAME);
		ofn.hwndOwner = theWindow;
		ofn.lpstrFile = szFile;
		ofn.nMaxFile = sizeof(szFile);
		ofn.lpstrFilter = "Saved Game Files\0*.sgf\0\0";
		ofn.nFilterIndex = 0;
		ofn.lpstrFileTitle = NULL;
		ofn.nMaxFileTitle = 0;
		ofn.lpstrInitialDir = NULL;
		ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
		ofn.lpstrDefExt = "sgf";
		szFile[0] = '\0';

		//	display the Open dialog box
		DWORD d = -1;
		int r = (int) ::GetOpenFileName( &ofn );
		if( r == TRUE )
			filename = ofn.lpstrFile;
	}
	if( filename != NULL )
	{
		hf = ::CreateFile( filename, GENERIC_READ,
			0, (LPSECURITY_ATTRIBUTES) NULL,
			OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
			(HANDLE) NULL );
		if( hf != NULL )
		{
			char *contents = new char[10000];
//			char tokenName[60];
			DWORD bytesRead;
			if( ::ReadFile( hf, contents, 10000, &bytesRead, NULL ) == 0 || bytesRead == 0 )
			{
				::MessageBox( theWindow, "Can't read from file", "ERROR!", MB_ICONHAND | MB_OK );
				delete[] contents;
			}
			else
			{
				int cursor = 0;
				bool derivedGame;
				char *baseGameName;
				char *gameName;
				int returnCode;
				fileContents = contents;
				returnCode = ParseFile( filename, contents, cursor,  bytesRead, derivedGame, baseGameName, gameName );

				//	search for this variant
				for( int i = 0; i < sizeof(supportedVariants)/sizeof(supportedVariants[0]); i++ )
				{
					if( !strcmp( supportedVariants[i].name, derivedGame ? baseGameName : gameName ) )
					{
						selectedVariant = &(supportedVariants[i]);
						break;
					}
				}

				if( selectedVariant == NULL )
					::MessageBox( theWindow, "Unrecognized file format or unsupported variant", "ERROR!", MB_ICONHAND | MB_OK );

				//	if a derived game, than the game information should be determined from the save-game file,
				//	and not from one of the internal GameInformation objects.  this is where the
				//	adHocGameInformation variable comes into play
				if( derivedGame )
				{
					char *bgName = new char[strlen(baseGameName)+1];
					strcpy( bgName, baseGameName );
					adHocGameInformation.baseGameName = bgName;
					char *gName = new char[strlen(gameName)+1];
					strcpy( gName, gameName );
					adHocGameInformation.name = gName;
					adHocGameInformation.file_count = selectedVariant->file_count;
					adHocGameInformation.rank_count = selectedVariant->rank_count;
					adHocGameInformation.square_count = selectedVariant->square_count;
					adHocGameInformation.player0Name = selectedVariant->player0Name;
					adHocGameInformation.player1Name = selectedVariant->player1Name;
					adHocGameInformation.startGameFunction = selectedVariant->startGameFunction;
					adHocGameInformation.createGameFunction = selectedVariant->createGameFunction;
					adHocGameInformation.dialogResourceID = selectedVariant->dialogResourceID;
					selectedVariant = &adHocGameInformation;
				}

				justLoaded = true;
				//	create game object
				if( selectedVariant->startGameFunction == NULL )
					GenericStartGameFunction( selectedVariant );
				else
					(*selectedVariant->startGameFunction)();
				theGame = (*selectedVariant->createGameFunction)( *theBoard, *pPlayer0, *pPlayer1 );
				theGame->Initialize();

				if( returnCode == ENCOUNTERED_END_OF_FILE )
					lineToPlayOffset = -1;
				else if( returnCode == ENCOUNTERED_OPEN_BRACE )
					lineToPlayOffset = cursor + 1;

				//	initialize some stuff
				theBoard->currentPlayer = 0;
				theBoard->gamePly = 1;
				theBoard->gameRecords[0].pieceMoved = NULL;
				theBoard->gameRecords[0].lastCaptureOrPawn = 0;
				theBoard->gameRecords[0].primaryHash = 0UL;
				theBoard->gameRecords[0].gameInt1 = 0;
				theBoard->gameRecords[0].gameInt2 = 0;
				theBoard->gameRecords[0].gameFlags = 0UL;
			}

			::CloseHandle( hf );
		}
	}
}

void SaveGame()
{
	OPENFILENAME ofn;       // common dialog box structure
	char szFile[260];       // buffer for filename
	HANDLE hf;              // file handle

	// Initialize OPENFILENAME
	ZeroMemory( &ofn, sizeof(OPENFILENAME) );
	ofn.lStructSize = sizeof(OPENFILENAME);
	ofn.hwndOwner = theWindow;
	ofn.lpstrFile = szFile;
	ofn.nMaxFile = sizeof(szFile);
	ofn.lpstrFilter = "Saved Game Files\0*.sgf\0\0";
	ofn.nFilterIndex = 0;
	ofn.lpstrFileTitle = NULL;
	ofn.nMaxFileTitle = 0;
	if( saveNewVariant )
		ofn.lpstrInitialDir = "include";
	else
		ofn.lpstrInitialDir = NULL;
	ofn.Flags = OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST;
	ofn.lpstrDefExt = "sgf";
	szFile[0] = '\0';
	saveNewVariant = false;

	//	display the Open dialog box
	DWORD d = -1;
	int r = (int) ::GetSaveFileName( &ofn );
	if( r == TRUE )
	{
		hf = ::CreateFile( ofn.lpstrFile, GENERIC_WRITE,
			0, (LPSECURITY_ATTRIBUTES) NULL,
			CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
			(HANDLE) NULL );
		if( hf != NULL )
			theGame->SaveGame( hf );
	}
	else
		d = CommDlgExtendedError();
}

static int moveListTabstops[] = {22, 4, 4};
static int thinkingListTabstops[] = {25, 25, 25, 25, 25};

LRESULT CALLBACK WndProc
	( HWND hWnd,
	  UINT message,
	  WPARAM wParam,
	  LPARAM lParam )
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc, hWorkDC;
	HBITMAP hbm, hbm2;
	HBITMAP hbm_old, hbm2_old;
	int xres, yres;
	BOOL r;

	switch (message)
	{
	  case WM_CREATE:
		theWindow = hWnd;
		mouseCaptured = false;
		break;

	  case WM_COMMAND:
		wmId = LOWORD(wParam);
		wmEvent = HIWORD(wParam);

		// Parse the menu selections:
		switch( wmId )
		{
		  case IDM_TAKE_BACK_MOVE:
			theBoard->TakeBackMove();
			break;

		  case ID_GAME_TAKEBACKALLMOVES:
			theBoard->TakeBackAllMoves();
			break;

		  case IDM_ABOUT:
			::DialogBox( hInst, (LPCTSTR) IDD_ABOUTBOX, hWnd, (DLGPROC) About );
			break;

		  case ID_FILE_SAVEGAME:
			SaveGame();
			break;

		  case ID_GAME_COMPUTERPLAYSWHITE:
			whiteComp = !whiteComp;
			pPlayer0->SetComputerControl( whiteComp );
			{
				HMENU hmenu = ::GetMenu( hWnd );
				HMENU submenu = ::GetSubMenu( hmenu, 1 );
				MENUITEMINFO iteminfo;
				iteminfo.cbSize = sizeof(iteminfo);
				iteminfo.fMask = MIIM_STATE;
				iteminfo.fState = MFS_ENABLED | (whiteComp ? MFS_CHECKED : 0);
				::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSWHITE, false, &iteminfo );
				if( !theBoard->IsThinking() && theBoard->GetCurrentPlayerNumber() == 0 && whiteComp )
					theBoard->Think();
			}
			break;

		  case ID_GAME_COMPUTERPLAYSBLACK:
			blackComp = !blackComp;
			pPlayer1->SetComputerControl( blackComp );
			{
				HMENU hmenu = ::GetMenu( hWnd );
				HMENU submenu = ::GetSubMenu( hmenu, 1 );
				MENUITEMINFO iteminfo;
				iteminfo.cbSize = sizeof(iteminfo);
				iteminfo.fMask = MIIM_STATE;
				iteminfo.fState = MFS_ENABLED | (blackComp ? MFS_CHECKED : 0);
				::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSBLACK, false, &iteminfo );
				if( !theBoard->IsThinking() && theBoard->GetCurrentPlayerNumber() == 1 && blackComp )
					theBoard->Think();
			}
			break;

		  case ID_GAME_STOPTHINKING:
			{
				int currentPlayer = theBoard->GetRootCurrentPlayerNumber();
				if( theBoard->IsThinking() )
				{
					whiteComp = false;
					blackComp = false;
					theGame->GetPlayer( 0 ).SetComputerControl( false );
					theGame->GetPlayer( 1 ).SetComputerControl( false );
					theBoard->AbortSearch();
					HMENU hmenu = ::GetMenu( hWnd );
					HMENU submenu = ::GetSubMenu( hmenu, 1 );
					MENUITEMINFO iteminfo;
					iteminfo.cbSize = sizeof(iteminfo);
					iteminfo.fMask = MIIM_STATE;
					iteminfo.fState = 0;
					::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSWHITE, false, &iteminfo );
					::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSBLACK, false, &iteminfo );
				}
			}
			break;

		  case ID_GAME_COMPUTERSETTINGS:
			::DialogBox( hInst, (LPCTSTR) IDD_COMPUTER_SETTINGS_DIALOG, hWnd, (DLGPROC) ComputerSettingsDlgProc );
			break;

		  case ID_GAME_TEST_THIS_POSITION:
			{
				::DialogBox( hInst, (LPCTSTR) IDD_TEST_POSITION_DIALOG, hWnd, (DLGPROC) PositionTestingDlgProc );
				int time = maxThinkTime;
				maxThinkTime = 100000;
				testingPosition = true;
				theBoard->TestPosition();
				maxThinkTime = time;
				::DialogBox( hInst, (LPCTSTR) IDD_COMP_MOVE_SELECTION_DIALOG, hWnd, (DLGPROC) CompMoveSelectDlgProc );
			}
			break;

		  case ID_BOARD_BOARDAPPEARANCE:
			if( ::DialogBox( hInst, (LPCTSTR) IDD_BOARD_OPTIONS_DIALOG, hWnd, (DLGPROC) BoardOptionsDlgProc ) == IDOK )
				::InvalidateRect( theWindow, NULL, false );
			break;

		  case ID_OPTIONS_ROTATEBOARD:
			rotateBoard = !rotateBoard;
			{
				HMENU hmenu = ::GetMenu( hWnd );
				HMENU submenu = ::GetSubMenu( hmenu, 2 );
				MENUITEMINFO iteminfo;
				iteminfo.cbSize = sizeof(iteminfo);
				iteminfo.fMask = MIIM_STATE;
				iteminfo.fState = MFS_ENABLED | (rotateBoard ? MFS_CHECKED : 0);
				::SetMenuItemInfo( submenu, ID_OPTIONS_ROTATEBOARD, false, &iteminfo );
				::InvalidateRect( theWindow, NULL, FALSE );
			}
			break;

		  case IDM_EXIT:
			::DestroyWindow( hWnd );
			break;

		  default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;

	  case WM_PAINT:
		hdc = ::BeginPaint( hWnd, &ps );
		hWorkDC = ::CreateCompatibleDC( hdc );
		xres = ps.rcPaint.right;
		yres = ps.rcPaint.bottom;
		hbm = ::CreateCompatibleBitmap( hdc, xres, yres );
		hbm2 = ::CreateCompatibleBitmap( hdc, xres, yres );
		hbm_old = (HBITMAP) ::SelectObject( hdc, hbm );
		hbm2_old = (HBITMAP) ::SelectObject( hWorkDC, hbm2 );
		::BitBlt( hWorkDC, 0, 0, xres, yres, hdc, 0, 0, SRCCOPY );
		PaintWindow( hWnd, hWorkDC, &ps );
		::BitBlt( hdc, 0, 0, xres, yres, hWorkDC, 0, 0, SRCCOPY );
		::SelectObject( hdc, hbm_old );
		r = ::DeleteObject( hbm );
		::SelectObject( hWorkDC, hbm2_old );
		r = ::DeleteObject( hbm2 );
		r = ::DeleteDC( hWorkDC );
		::EndPaint( hWnd, &ps );
		if( !firstPaint )
		{
			firstPaint = true;

			//	set check marks by menu options as necessary
			HMENU hmenu = ::GetMenu( hWnd );
			HMENU submenu = ::GetSubMenu( hmenu, 1 );
			MENUITEMINFO iteminfo;
			iteminfo.cbSize = sizeof(iteminfo);
			iteminfo.fMask = MIIM_STATE;
			iteminfo.fState = MFS_ENABLED | (whiteComp ? MFS_CHECKED : 0);
			::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSWHITE, false, &iteminfo );
			iteminfo.fState = MFS_ENABLED | (blackComp ? MFS_CHECKED : 0);
			::SetMenuItemInfo( submenu, ID_GAME_COMPUTERPLAYSBLACK, false, &iteminfo );
			char menuText[60];
			sprintf( menuText, "Computer Plays %s", pPlayer0->GetName() );
			::ModifyMenu( submenu, ID_GAME_COMPUTERPLAYSWHITE, MF_BYCOMMAND | MF_STRING,
				ID_GAME_COMPUTERPLAYSWHITE, menuText );
			sprintf( menuText, "Computer Plays %s", pPlayer1->GetName() );
			::ModifyMenu( submenu, ID_GAME_COMPUTERPLAYSBLACK, MF_BYCOMMAND | MF_STRING,
				ID_GAME_COMPUTERPLAYSBLACK, menuText );

			if( !justLoaded )
				theBoard->StartGame();

			//	create context menu for right-click
			contextMenu = ::CreatePopupMenu();
			MENUITEMINFO info;
			info.cbSize = sizeof(MENUITEMINFO);
			info.fType = MFT_STRING;
			info.fState = MFS_ENABLED;
			info.wID = 1;
			info.hSubMenu = NULL;
			info.fMask = MIIM_FTYPE | MIIM_ID | MIIM_STATE | MIIM_STRING;
			info.dwTypeData = "Properties ...";
			int rtn = ::InsertMenuItem( contextMenu, 0, true, &info );

			//	show the window
			::ShowWindow( mainWindow, SW_SHOWNORMAL );
			::UpdateWindow( mainWindow );

			//	save game, if new variant
			if( saveNewVariant )
			{
				::DialogBox( theInstance, MAKEINTRESOURCE(IDD_NEW_VARIANT_DIALOG), mainWindow, (DLGPROC) SaveNewVariantDlgProc );
			}
		}
		break;

	  case WM_SIZE:
		// *** Create or Move the MoveList *** //
		if( hMoveList == NULL )
		{
			hMoveList = ::CreateWindow( "LISTBOX", "moves", WS_CHILD | WS_BORDER | WS_VISIBLE | LBS_NOSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL | WS_HSCROLL,
				theBoard->GetBoardRightEdge() + 10, 75, LOWORD(lParam) - theBoard->GetBoardRightEdge() - 12, HIWORD(lParam) - 68, hWnd, NULL, theInstance, NULL );
			if( hMoveList == NULL )
				::MessageBox( theWindow, "move list creation failed", "ERROR!", MB_ICONHAND | MB_OK );
			else
				::SendMessage( hMoveList, LB_SETTABSTOPS, (WPARAM) 1, (LPARAM) moveListTabstops );
		}
		else
		{
			::MoveWindow( hMoveList, theBoard->GetBoardRightEdge() + 10, 75,
				LOWORD(lParam) - theBoard->GetBoardRightEdge() - 12, HIWORD(lParam) - 68, TRUE );
		}

		// *** Create or Move the ThinkingList *** //
		if( hThinkingList == NULL )
		{
			hThinkingList = ::CreateWindow( "LISTBOX", "thinking", WS_CHILD | WS_BORDER | WS_VISIBLE | LBS_NOSEL | LBS_HASSTRINGS | LBS_USETABSTOPS | WS_VSCROLL,
				20, theBoard->GetBoardBottomEdge() + 10, theBoard->GetBoardRightEdge() - 20,
				HIWORD(lParam) - theBoard->GetBoardBottomEdge() - 10, hWnd, NULL, theInstance, NULL );
			if( hMoveList == NULL )
				::MessageBox( theWindow, "thinking list creation failed", "ERROR!", MB_ICONHAND | MB_OK );
			else
				::SendMessage( hThinkingList, LB_SETTABSTOPS, (WPARAM) 1, (LPARAM) thinkingListTabstops );
		}
		else
		{
			::MoveWindow( hThinkingList, 20, theBoard->GetBoardBottomEdge() + 10, theBoard->GetBoardRightEdge() - 20,
				HIWORD(lParam) - theBoard->GetBoardBottomEdge() - 10, TRUE );
		}

		// *** Completion of Loading Saved Game *** //
		if( fileContents != NULL )
		{
			//	notify game class that a game load has completed
			theGame->GameLoaded();

			if( lineToPlayOffset >= 0 )
				//	play out the line of moves
				theBoard->PlayLine( fileContents + lineToPlayOffset );

			delete[] fileContents;
			fileContents = NULL;

			if( theGame->GetPlayer( theBoard->GetCurrentPlayerNumber() ).IsComputerControlled() )
			{
				::BringWindowToTop( theWindow );
				::InvalidateRect( theWindow, NULL, true );
				::UpdateWindow( theWindow );
				theBoard->Think();
			}
			else
				theBoard->GetLegalUserMovementList().GenerateMoves( Movement(), Movement(), PV );
		}
		break;

	  case WM_LBUTTONDOWN:
		MouseButtonPressed( LOWORD(lParam), HIWORD(lParam) );
		break;

	  case WM_LBUTTONUP:
		MouseButtonReleased( LOWORD(lParam), HIWORD(lParam) );
		break;

	  case WM_MOUSEMOVE:
		MouseMoved( LOWORD(lParam), HIWORD(lParam) );
		break;

	  case WM_RBUTTONDOWN:
		{
			int square;
			POINT point;
			Piece *piece;
			square = theBoard->SquareHitTest( LOWORD(lParam), HIWORD(lParam) );
			if( square != -1 )
			{
				piece = theBoard->GetSquareContents( square );
				if( piece != NULL )
				{
					point.x = LOWORD(lParam);
					point.y = HIWORD(lParam);
					::ClientToScreen( hWnd, &point );
					int selection = ::TrackPopupMenuEx( contextMenu, TPM_LEFTALIGN | TPM_BOTTOMALIGN | TPM_RIGHTBUTTON | TPM_RETURNCMD,
						point.x, point.y, hWnd, NULL );
					if( selection == 1 )
					{
						//	show properties window for selected piece
						::CreateWindow( "piece_properties", "Piece Properties", WS_VISIBLE | WS_OVERLAPPEDWINDOW, 10, 10, 600, 640, theWindow, NULL, theInstance, piece );
					}
				}
			}
		}

	  case WM_CHAR:
		break;

	  case WM_DESTROY:
		::DestroyMenu( contextMenu );
		::PostQuitMessage( 0 );
		break;

	  case WM_CTLCOLORLISTBOX:
		::SetTextColor( (HDC) wParam, RGB(255, 255, 255) );
		::SetBkColor( (HDC) wParam, RGB(0, 0, 0) );
		return (LRESULT) ::GetStockObject( BLACK_BRUSH );

	  default:
		return ::DefWindowProc( hWnd, message, wParam, lParam );
	}
	return 0;
}

LRESULT CALLBACK About
	( HWND hDlg,
	  UINT message,
	  WPARAM wParam,
	  LPARAM lParam )
{
	switch( message )
	{
	  case WM_INITDIALOG:
		{
			int cursor = 0;
			char buffer[10000];
			for( int i = 0; true; i++ )
			{
				GameInformation &game = supportedVariants[i];
				if( game.name == NULL )
					//	no more entries in supportedVariants array
					break;
				else
				{
					if( strcmp( game.inventor, "unknown" ) )
					{
						sprintf( buffer + cursor, "%s was invented by %s\r\n", game.name, game.inventor );
						cursor = (int) strlen(buffer);
					}
				}
			}
			int rtn = ::SetDlgItemText( hDlg, IDC_GAME_AUTHORS, (LPCTSTR) buffer );
			if( rtn == 0 )
			{
				DWORD dw = ::GetLastError();
			}
		}
		return TRUE;

	  case WM_COMMAND:
		if( LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL )
		{
			::EndDialog( hDlg, LOWORD(wParam) );
			return TRUE;
		}
		break;
	}
	return FALSE;
}

LRESULT CALLBACK ComputerSettingsDlgProc
	( HWND hWndDlg,
	  UINT Msg,
	  WPARAM wParam,
	  LPARAM lParam )
{
	static int oldTTSize;
	static int oldECSize;
	static int oldPersonality;

	switch( Msg )
	{
	  case WM_INITDIALOG:
		{
			char buffer[20];
			oldTTSize = transpositionTableSize;
			oldECSize = evaluationCacheSize;
			oldPersonality = personality;
			itoa( maxThinkTime, buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_THINK_TIME_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( maxIDepth, buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_MAX_DEPTH_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( minIDepth, buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_MIN_DEPTH_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( theGame->GetFutilityMargin(), buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_FUTILITY_MARGIN_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( theGame->GetExtendedFutilityMargin(), buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_EXT_FUTILITY_MARGIN_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( theGame->GetRazorMargin(), buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_RAZOR_MARGIN_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( transpositionTableSize, buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_TT_SIZE_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			itoa( evaluationCacheSize, buffer, 10 );
			::SendDlgItemMessage( hWndDlg, IDC_EC_SIZE_EDIT, WM_SETTEXT, (WPARAM) 0, (LPARAM) buffer );
			if( personality == PERSONALITY_A )
				::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_A, BM_SETCHECK, (WPARAM) BST_CHECKED, (LPARAM) 0 );
			else if( personality == PERSONALITY_B )
				::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_B, BM_SETCHECK, (WPARAM) BST_CHECKED, (LPARAM) 0 );
			else if( personality == PERSONALITY_C )
				::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_C, BM_SETCHECK, (WPARAM) BST_CHECKED, (LPARAM) 0 );
			else if( personality == PERSONALITY_D )
				::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_D, BM_SETCHECK, (WPARAM) BST_CHECKED, (LPARAM) 0 );
			oldTTSize = transpositionTableSize;
			oldECSize = evaluationCacheSize;
		}
		return TRUE;
	  case WM_COMMAND:
		if( LOWORD(wParam) == IDOK )
		{
			char buffer[20];
			::SendDlgItemMessage( hWndDlg, IDC_THINK_TIME_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			maxThinkTime = atoi( buffer );
			::SendDlgItemMessage( hWndDlg, IDC_MAX_DEPTH_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			maxIDepth = atoi( buffer );
			::SendDlgItemMessage( hWndDlg, IDC_MIN_DEPTH_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			minIDepth = atoi( buffer );
			::SendDlgItemMessage( hWndDlg, IDC_FUTILITY_MARGIN_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			theGame->SetFutilityMargin( atoi( buffer ) );
			::SendDlgItemMessage( hWndDlg, IDC_EXT_FUTILITY_MARGIN_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			theGame->SetExtendedFutilityMargin( atoi( buffer ) );
			::SendDlgItemMessage( hWndDlg, IDC_RAZOR_MARGIN_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			theGame->SetRazorMargin( atoi( buffer ) );
			::SendDlgItemMessage( hWndDlg, IDC_TT_SIZE_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			if( ::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_A, BM_GETCHECK, 0, 0 ) == BST_CHECKED )
				personality = PERSONALITY_A;
			else if( ::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_B, BM_GETCHECK, 0, 0 ) == BST_CHECKED )
				personality = PERSONALITY_B;
			else if( ::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_C, BM_GETCHECK, 0, 0 ) == BST_CHECKED )
				personality = PERSONALITY_C;
			else if( ::SendDlgItemMessage( hWndDlg, IDC_PERSONALITY_D, BM_GETCHECK, 0, 0 ) == BST_CHECKED )
				personality = PERSONALITY_D;
			transpositionTableSize = atoi( buffer );
			if( transpositionTableSize != oldTTSize )
				theBoard->SetSizeOfTranspositionTables( transpositionTableSize );
			::SendDlgItemMessage( hWndDlg, IDC_EC_SIZE_EDIT, WM_GETTEXT, (WPARAM) 18, (LPARAM) buffer );
			evaluationCacheSize = atoi( buffer );
			if( evaluationCacheSize != oldECSize )
				theBoard->SetEvaluationCacheSize( evaluationCacheSize );

			if( personality != oldPersonality )
			{
				theGame->SetPersonality( personality );
			}

			::EndDialog( hWndDlg, IDOK );
		}
		else if( LOWORD(wParam) == IDCANCEL )
			::EndDialog( hWndDlg, IDCANCEL );
		break;
	}

	return FALSE;
}


LRESULT CALLBACK SaveNewVariantDlgProc
	( HWND hWndDlg,
	  UINT Msg,
	  WPARAM wParam,
	  LPARAM lParam )
{
	switch( Msg )
	{
	  case WM_INITDIALOG:
		{
			::SetWindowText( ::GetDlgItem( hWndDlg, IDC_YEAR_INVENTED_EDIT ), "2006" );
		}
		break;

	  case WM_COMMAND:
		{
			switch( wParam )
			{
			  case IDOK:
				{
					char buffer[100];
					::GetWindowText( ::GetDlgItem( hWndDlg, IDC_INVENTOR_NAME_EDIT ), buffer, 98 );
					if( (int) strlen( buffer ) >= 1 )
						StoreParameter( "inventor", buffer );
					::GetWindowText( ::GetDlgItem( hWndDlg, IDC_YEAR_INVENTED_EDIT ), buffer, 98 );
					if( (int) strlen( buffer ) >= 1 )
						StoreParameter( "invented", buffer );
					::GetWindowText( ::GetDlgItem( hWndDlg, IDC_VARIANT_NAME_EDIT ), buffer, 61 );
					if( (int) strlen( buffer ) >= 1 )
						strcpy( newVariantName, buffer );
					SaveGame();
					::EndDialog( hWndDlg, IDOK );
				}
				break;

			  case IDCANCEL:
				::EndDialog( hWndDlg, IDCANCEL );
				break;
			}
		}
		break;
	}

	return FALSE;
}
