
#include "stdafx.h"
#include "Search.h"

Move g_moveWinner[4];
BOOL g_bRecord;
BOOL g_bThreadRunning;
BOOL g_bAbortSearch;

#define MASK_1		0x0004		//   track of directions that
#define MASK_2		0x0008		//   have already been considered
#define MASK_3		0x0010		//   for a specific move
#define MASK_4		0x0020		// There's a mask for every possible
#define MASK_5		0x0040		//   winning direction
#define MASK_6		0x0080
#define MASK_7		0x0100
#define MASK_8		0x0200
#define MASK_9		0x0400
#define MASK_10		0x0800
#define MASK_11		0x1000
#define MASK_12		0x2000
#define MASK_13		0x4000
#define MASK_ALL	0x3FFC

#define BONUS_ONESIES	0		// single ball in a line (not used)
#define BONUS_TWOSIES	4		// two balls in a line
#define BONUS_THREESIES	16		// three balls in a line

#define RAND( min, max ) ((rand() % (int)(max - min)) + min)

static void evalPlaceBall(UINT *cube, int *hits, int i, int j, int k);
static int sortCompare(const void *a1, const void *a2);

static int searchNegamax(int level, UINT *cube, UINT turn);
static void searchSwitchPlayers(UINT &turn);
static void searchUnMakeMove(UINT *cube, Move &move);
static void searchMakeMove(UINT *cube, UINT &turn, Move &move);
static void searchGenerateMoveList(UINT *cube, Move *list, int &count, int turn);

// Evaluates a single move (placement of a ball in some arbitrary location)
//
static void evalPlaceBall(UINT *cube, int *hits, int i, int j, int k)
{
	UINT
		c = cube[i * 16 + j * 4 + k],
		o = (c & MOVE) == PLAYER_RED ? PLAYER_BLUE : PLAYER_RED,
		x;
	int ii,
		s;
	BOOL f;

	// winning combinations, strictly 1D

	if (!(c & MASK_1))
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + j * 4 + k;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_1;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)					// g_bRecord move, if asked
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = j;
				g_moveWinner[ii].nMoveK = k;
			}
	}

	if (!(c & MASK_2))
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = i * 16 + ii * 4 + k;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_2;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = i;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = k;
			}
	}

	if (!(c & MASK_3))
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = i * 16 + j * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_3;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = i;
				g_moveWinner[ii].nMoveJ = j;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	// winning combinations, strictly 2D

	if (!(c & MASK_4) && i == j)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + ii * 4 + k;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_4;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = k;
			}
	}

	if (!(c & MASK_5) && i == 3 - j)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + (3 - ii) * 4 + k;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_5;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = 3 - ii;
				g_moveWinner[ii].nMoveK = k;
			}
	}

	if (!(c & MASK_6) && j == k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = i * 16 + ii * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_6;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = i;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	if (!(c & MASK_7) && j == 3 - k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = i * 16 + ii * 4 + (3 - ii);
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_7;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = i;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = 3 - ii;
			}
	}

	if (!(c & MASK_8) && i == k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + j * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_8;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = j;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	if (!(c & MASK_9) && i == 3 - k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + j  * 4 + (3 - ii);
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_9;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = j;
				g_moveWinner[ii].nMoveK = 3 - ii;
			}
	}

	// winning combinations, strictly 3D

	if (!(c & MASK_10) && i == j && j == k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + ii * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_10;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	if (!(c & MASK_11) && 3 - i == j && j == k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = (3 - ii) * 16 + ii * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_11;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = 3 - ii;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	if (!(c & MASK_12) && i == j && j == 3 - k)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + ii * 4 + (3 - ii);
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_12;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = ii;
				g_moveWinner[ii].nMoveK = 3 - ii;
			}
	}

	if (!(c & MASK_13) && i == k && i == 3 - j)
	{
		for (ii = 0, f = TRUE, s = -1; ii < 4; ii++)
		{
			x = ii * 16 + (3 - ii) * 4 + ii;
			if ((cube[x] & MOVE) == (c & MOVE)) s++;
			else if ((cube[x] & MOVE) == o) f = FALSE;
			cube[x] |= MASK_13;
		}
		if (f) hits[s]++;
		if (g_bRecord && s == 3)
			for (ii = 0; ii < 4; ii++)
			{
				g_moveWinner[ii].nMoveI = ii;
				g_moveWinner[ii].nMoveJ = 3 - ii;
				g_moveWinner[ii].nMoveK = ii;
			}
	}

	cube[i * 16 + j * 4 + k] |= MASK_ALL;
}

// evaluates a position in the game
int searchEvaluate(UINT *cube, UINT turn)
{
	UINT 
		i, j, k, 
		x;
	int
		p_hits[4],
		o_hits[4];

	for (i = 0; i < 4; i++) 
		p_hits[i] = o_hits[i] = 0;	// initialize scores

	for (i = 0; i < 64; i++) 
		cube[i] &= MOVE;			// initialize masks

	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
			for (k = 0; k < 4; k++)
			{
				x = (i << 4) + (j << 2) + k;
				
				if ((cube[x] & MOVE) == PLAYER_RED)
					evalPlaceBall(cube, p_hits, i, j, k);	// evaluate all valid player 1 moves

				if (p_hits[3] > 0) 
					return INFINITY;					// winning position
			}

	for (i = 0; i < 64; i++) 
		cube[i] &= MOVE;

	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
			for (k = 0; k < 4; k++)
			{
				x = i * 16 + j * 4 + k;
				
				if ((cube[x] & MOVE) == PLAYER_BLUE)
					evalPlaceBall(cube, o_hits, i, j, k);	// evaluate all valid player 2 moves
				
				if (o_hits[3] > 0) 
					return -INFINITY;					// winning position
			}

	// return value is (score of first player - score of second player)
	return (p_hits[0] * BONUS_ONESIES + p_hits[1] * BONUS_TWOSIES + p_hits[2] * BONUS_THREESIES) - 
			(o_hits[0] * BONUS_ONESIES + o_hits[1] * BONUS_TWOSIES + o_hits[2] * BONUS_THREESIES);
}

// compares two moves by evaluation (for qsorting)
//
static int sortCompare(const void *a1, const void *a2)
{
	return (*(Move*)a2).nEval - (*(Move*)a1).nEval;
}

// searching function (NegaMax)
static int searchNegamax(int level, UINT *cube, UINT turn)
{
	Move* moves;
	int i,
		best,
		count,
		eval;

	searchSwitchPlayers(turn);
	eval = searchEvaluate(cube, turn);

	if (level <= 0 || abs(eval) == INFINITY)	// already at deepest level - stop searching
		return (turn == PLAYER_RED) ? eval : -eval;

	moves = (Move*)malloc(sizeof(Move) * 64);
	searchGenerateMoveList(cube, moves, count, turn);	// generate move list

	for (i = 0; i < count && !g_bAbortSearch; i++)	// make every possible move, evaluate positions
	{
		searchMakeMove(cube, turn, moves[i]);
		moves[i].nEval = -searchNegamax(level - 1, cube, turn);
		searchUnMakeMove(cube, moves[i]);
	}

	qsort(moves, count, sizeof(Move), sortCompare);	// sort move list

	for (i = 0; i < count; i++)
		if (moves[i].nEval != moves[0].nEval) break;

	best = (i > 1) ? moves[RAND(0, i)].nEval : moves[0].nEval;

	free(moves);
	return best;
}


// switches current player
//
static void searchSwitchPlayers(UINT &turn)
{
	turn = (turn == 1) ? 2 : 1;
}

// un-makes a move (sets a location to 'blank')
//
static void searchUnMakeMove(UINT *cube, Move &move)
{
	cube[(move.nMoveI << 4) + (move.nMoveJ << 2) + move.nMoveK] = 0;
}

// makes a move (places a ball of specified color in specified location)
//
static void searchMakeMove(UINT *cube, UINT &turn, Move &move)
{
	cube[(move.nMoveI << 4) + (move.nMoveJ << 2) + move.nMoveK] = turn;
}

#define CURRENT_BALL		(cube[(i << 4) + (j << 2) + k] & MOVE)

// generates a list of all possible moves for a given position
//
static void searchGenerateMoveList(UINT *cube, Move *list, int &count, int turn)
{
	int i, j, k, n;

	n = 0;
	for (i = 0; i < 4; i++)
		for (j = 0; j < 4; j++)
			for (k = 0; k < 4; k++)
			{
				if (CURRENT_BALL == 0)
				{
					list[n].nMoveI = i;
					list[n].nMoveJ = j;
					list[n].nMoveK = k;
					n++;
				}
			}

	count = n;
}

// finds and records computer's best move
//   this function is meant to be run in a separate thread
//   and is invoked by a call to CreateThread elsewhere in program
// most of what happens in here is identical of what happens in negamax()
//
UINT searchGo(LPVOID param)
{
	ThreadParam *tpParam = (ThreadParam*)param;

	Move
		*moves,
		*best;
	UINT
		this_cube[64],
		this_turn;
	int i,
		count;

	g_bRecord = FALSE;
	this_turn = tpParam->nTurn;

	for (i = 0; i < 64; i++) 
		this_cube[i] = tpParam->uCube[i];

	moves = (Move*)malloc(sizeof(Move) * 64);
	searchGenerateMoveList(this_cube, moves, count, this_turn);

	for (i = 0; i < count && !g_bAbortSearch; i++)
	{
		searchMakeMove(this_cube, this_turn, moves[i]);
		moves[i].nEval = -searchNegamax(tpParam->nSkill, this_cube, this_turn);
		searchUnMakeMove(this_cube, moves[i]);
	}

	if (!g_bAbortSearch)
	{
		qsort(moves, count, sizeof(Move), sortCompare);

		for (i = 0; i < count; i++)
			if (moves[i].nEval != moves[0].nEval) 
				break;

		best = (Move*)malloc(sizeof(Move));
		*best = (i > 1) ? moves[RAND(0, i)] : moves[0];
		free(moves);

		PostMessage(tpParam->hMainWindow, WM_SEARCH_DONE, (WPARAM)best, 0);
	}
	else
	{
		free(moves);
		g_bThreadRunning = FALSE;
	}

	return 0;
}