#include <c64/joystick.h>
#include <c64/vic.h>
#include <c64/sprites.h>
#include <c64/memmap.h>
#include <c64/rasterirq.h>
#include <c64/sid.h>
#include <c64/charwin.h>
#include <c64/types.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

// Include charset and sprite resources

char charset[2048] = {
	#embed "../resources/connect4chars.bin"
};

char spriteset[2048] = {
	#embed "../resources/connect4sprites.bin"
};

// Address ov video data

byte * const Screen = (byte *)0xc800;
byte * const Font = (byte *)0xd000;
byte * const Color = (byte *)0xd800;
byte * const Sprites = (byte *)0xd800;

// Values for player 1 pieces and player 2 pieces.
// The values 1 and 5 are selected so that the sum of the
// values in a row of four will add up to a unique value
// in the range of 0..24 for ranking

#define PLAYER1_INC		1
#define PLAYER2_INC		5

// Current state of the board

struct Board
{	
	char	fcols[48];	// Pieces in the 6x7 grid using 48 to have a multiplier of 8
	char	ffree[7];	// Number of free places in each column
}	board;

enum GameState
{
	GS_READY,			// Getting ready

	GS_PLAYER_MOVE,		// The players move
	GS_COMPUTER_MOVE,	// The computers move

	GS_PLAYER_WIN,		// The player wins
	GS_COMPUTER_WIN,	// The computer wins
	GS_GAME_OVER		// No more moves
};

// Draw the board into the CharWin using the small
// character set

void board_draw(CharWin * cwin)
{
	for(char y=0; y<6; y++)
	{
		for(char x=0; x<7; x++)
		{
			if (board.fcols[8 * y + x] == PLAYER1_INC)
				cwin_putat_char_raw(cwin, x, y, 82, 10);
			else if (board.fcols[8 * y + x] == PLAYER2_INC)
				cwin_putat_char_raw(cwin, x, y, 82, 15);
			else
				cwin_putat_char_raw(cwin, x, y, 82,  8);
		}

		// Right most column

		cwin_putat_char_raw(cwin, 7, y, 83, 8);
	}
}

// Draw a big piece or place with the given color, the
// color 0 denotes an empty place.  Empty places use different
// tiles to allow sprite priority to obscure the falling pieces

void board_draw_item(CharWin * cwin, char x, char y, char c)
{
	char cx = x * 3 + 1, cy = y * 3 + 2;

	if (c)
	{
		cwin_putat_char_raw(cwin, cx + 0, cy + 0, 73, c);
		cwin_putat_char_raw(cwin, cx + 1, cy + 0, 74, c);
		cwin_putat_char_raw(cwin, cx + 2, cy + 0, 75, c);

		cwin_putat_char_raw(cwin, cx + 0, cy + 1, 76, c);
		cwin_putat_char_raw(cwin, cx + 1, cy + 1, 77, c);
		cwin_putat_char_raw(cwin, cx + 2, cy + 1, 78, c);

		cwin_putat_char_raw(cwin, cx + 0, cy + 2, 79, c);
		cwin_putat_char_raw(cwin, cx + 1, cy + 2, 80, c);
		cwin_putat_char_raw(cwin, cx + 2, cy + 2, 81, c);
	}
	else
	{
		cwin_putat_char_raw(cwin, cx + 0, cy + 0, 64, 14);
		cwin_putat_char_raw(cwin, cx + 1, cy + 0, 65, 14);
		cwin_putat_char_raw(cwin, cx + 2, cy + 0, 66, 14);

		cwin_putat_char_raw(cwin, cx + 0, cy + 1, 67, 14);
		cwin_putat_char_raw(cwin, cx + 1, cy + 1, 68, 14);
		cwin_putat_char_raw(cwin, cx + 2, cy + 1, 69, 14);

		cwin_putat_char_raw(cwin, cx + 0, cy + 2, 70, 14);
		cwin_putat_char_raw(cwin, cx + 1, cy + 2, 71, 14);
		cwin_putat_char_raw(cwin, cx + 2, cy + 2, 72, 14);		
	}
}

// Draw the big board

void board_draw_main(CharWin * cwin)
{
	for(char y=0; y<6; y++)
	{
		for(char x=0; x<7; x++)
		{
			if (board.fcols[8 * y + x] == PLAYER1_INC)
				board_draw_item(cwin, x, y, 10);
			else if (board.fcols[8 * y + x] == PLAYER2_INC)
				board_draw_item(cwin, x, y, 15);
			else
				board_draw_item(cwin, x, y, 0);
		}
	}
}

// Draw the decoration of the big board

void board_init_main(CharWin * cwin)
{
	// Frame around the board

	cwin_putat_char_raw(cwin, 0, 1, 84, 14);
	cwin_fill_rect_raw(cwin, 1, 1, 21, 1, 85, 14);
	cwin_putat_char_raw(cwin, 22, 1, 86, 14);
	cwin_fill_rect_raw(cwin, 0, 2, 1, 18, 87, 14);
	cwin_fill_rect_raw(cwin, 22, 2, 1, 18, 88, 14);
	cwin_putat_char_raw(cwin, 0, 20, 89, 14);
	cwin_fill_rect_raw(cwin, 1, 20, 21, 1, 90, 14);
	cwin_putat_char_raw(cwin, 22, 20, 91, 14);

	// Fields in the board

	for(char x=0; x<7; x++)
		cwin_putat_char_raw(cwin, 3 * x + 2, 0, '1' + x, 1);

	board_draw_main(cwin);
}

// Play animation of a dropped piece

void item_drop_anim(CharWin * cwin, char x, char y, char c)
{
	// Initial position

	int	ix = (cwin->sx + 3 * x + 1) * 8 + 24, iy = (cwin->sy + 2) * 8 + 20;

	// Show sprite at start position
	spr_set(0, true, ix, iy, 97,  c, true, false, false);

	// Set sprite priority to be behind background
	vic.spr_priority = 0x01;

	// Speed and target position, using four fractional bits
	int	vy = 0, aiy = iy * 16, ty = ((cwin->sy + y * 3 + 2) * 8 + 50) * 16;

	// Bounce three times back when reaching bottom
	char	bounce = 3;

	// Loop through the bounces
	while (bounce > 0)
	{
		// Let gravity do its thing of accelerating
		vy += 3;

		// Add vertical velocity to position
		aiy += vy;

		// Reached the bottom yet?
		if (aiy > ty)
		{
			// Reflect position
			aiy = 2 * ty - aiy;

			// Reflect speed
			vy = - vy * 5 >> 4;

			// One bounce down
			bounce--;
		}

		// Move sprite and wait for display
		spr_move(0, ix, aiy >> 4);
		vic_waitFrame();
	}
}

// Play column selection animation

void column_select_anim(CharWin * cwin, char x0, char x1, char c)
{
	// Start position
	int	ix = (cwin->sx + 3 * x0 + 1) * 8 + 24, iy = (cwin->sy + 2) * 8 + 20;

	// Target position
	int	tx = (cwin->sx + 3 * x1 + 1) * 8 + 24;

	// Show sprite at start position
	spr_set(0, true, ix, iy, 97,  c, true, false, false);

	// Set sprite priority to be behind background
	vic.spr_priority = 0x01;

	// Not at target position already?
	if (ix != tx)
	{
		// Eight frames for movement
		for(int i=1; i<=8; i++)
		{
			vic_waitFrame();
			// Move sprite along path
			spr_move(0, (ix * (8 - i) + tx * i) >> 3, iy);
		}
	}
}

// Initialize the board to empty

void board_init(void)
{
	// Column has six free slots
	for(char x=0; x<7; x++)
		board.ffree[x] = 6;

	// Clear all slots
	for(char i=0; i<48; i++)
		board.fcols[i] = 0;
}

// Drop one piece of player p down column x

inline bool board_drop(char x, bool p)
{
	// Check for free space
	if (board.ffree[x])
	{
		// Fill one slot
		char	y = --(board.ffree[x]);
		board.fcols[8 * y + x] = p ? PLAYER2_INC : PLAYER1_INC;

		// Success
		return true;
	}
	return false;
}

// Remove the top piece from column x

inline void board_undrop(char x)
{
	// Free one slot
	char	y = (board.ffree[x])++;
	board.fcols[8 * y + x] = 0x00;
}

// Score for rows with 0, 1, 2, and 3 elements of same color

static const int score[5] = {0, 1, 8, 128, 10000};

// Score index is calculated (25 * rv + 5 * p1 + p2) with rv the
// value range of the row, p1 and p2 number of pieces from player 1 or 2

int fscore[125];

// Indices of the board positions of four slots of each of the 69 
// 4 slot rows in the board.  The fifth value is the value range
// of the row

char frows[5][69];

#pragma align(fscore, 256)

// Tables for opening library for computer move 1, 2, 3 and 4

const char open1 = 3;
const char open2[4 * 7] = 
{
	4, 3, 3, 3, 4, 3, 3, 3, 5, 3, 3, 4, 3, 3, 3, 3, 2, 3, 2, 2, 2, 4, 2, 4, 3, 2, 4, 2
};

const char open3[4 * 7 * 7] = {
	0,  5,  2,  2,  5,  3,  2,  5,  4,  2,  4,  3,  3,  5,  2,  2,  3,  2,  3,  2,  2,  4,  5,  2,  4,  4,  3,  2,
	3,  3,  3,  3,  3,  3,  4,  1,  3,  2,  5,  3,  2,  1,  2,  5,  2,  2,  4,  1,  4,  5,  4,  2,  4,  3,  3,  5,
	4,  1,  3,  4,  4,  4,  3,  2,  1,  3,  2,  3,  2,  3,  5,  5,  1,  5,  4,  3,  3,  3,  4,  3,  3,  5,  3,  3,
	3,  5,  2,  3,  4,  1,  3,  5,  4,  3,  1,  4,  3,  5,  2,  2,  3,  2,  3,  2,  2,  2,  1,  3,  2,  3,  2,  3,
	3,  1,  3,  2,  2,  3,  3,  2,  1,  3,  2,  2,  2,  2,  3,  3,  3,  2,  1,  3,  3,  3,  3,  1,  3,  3,  2,  3,
	2,  3,  3,  3,  3,  3,  3,  3,  2,  1,  2,  3,  4,  3,  2,  4,  3,  2,  2,  2,  2,  1,  0,  2,  4,  3,  4,  4,
	4,  4,  1,  2,  5,  2,  2,  2,  2,  3,  2,  4,  6,  5,  4,  4,  4,  4,  3,  2,  4,  3,  2,  3,  4,  5,  4,  3,
};

const char open4[4 * 7 * 7 * 7] = {
	2,  5,  2,  2,  5,  3,  2,  2,  2,  6,  2,  2,  2,  2,  0,  4,  4,  3,  2,  3,  3,  5,  5,  5,  5,  5,  1,  1,
	2,  2,  6,  2,  2,  2,  2,  0,  4,  4,  4,  3,  5,  4,  1,  5,  1,  1,  1,  1,  1,  0,  4,  2,  4,  3,  4,  5,
	5,  1,  4,  5,  5,  3,  2,  3,  3,  1,  1,  1,  1,  3,  5,  5,  4,  5,  5,  4,  4,  3,  3,  3,  1,  3,  3,  3,
	3,  3,  3,  1,  3,  3,  3,  5,  4,  2,  5,  3,  1,  4,  0,  3,  6,  2,  3,  3,  3,  3,  3,  1,  1,  1,  1,  3,
	3,  3,  3,  2,  3,  3,  3,  2,  1,  6,  2,  4,  5,  2,  3,  3,  3,  2,  3,  3,  3,  3,  1,  5,  5,  3,  3,  3,
	3,  3,  0,  2,  3,  3,  3,  0,  5,  2,  2,  5,  1,  2,  4,  4,  2,  4,  5,  4,  2,  2,  2,  3,  2,  2,  2,  2,
	2,  5,  5,  2,  2,  1,  2,  4,  3,  4,  4,  3,  3,  4,  1,  3,  2,  1,  4,  4,  1,  4,  5,  4,  4,  4,  2,  4,
	0,  3,  3,  4,  3,  3,  3,  3,  3,  3,  1,  3,  5,  3,  3,  3,  3,  2,  2,  4,  3,  4,  3,  4,  4,  3,  3,  4,
	3,  3,  2,  3,  3,  5,  6,  3,  5,  4,  5,  5,  3,  3,  3,  3,  2,  3,  3,  3,  5,  0,  5,  3,  4,  4,  3,  2,
	3,  3,  3,  1,  3,  3,  3,  3,  1,  5,  5,  3,  3,  3,  3,  3,  2,  1,  4,  3,  1,  3,  3,  3,  4,  3,  3,  3,
	2,  2,  3,  2,  5,  5,  4,  2,  5,  3,  1,  4,  2,  1,  0,  2,  4,  4,  3,  2,  4,  5,  4,  2,  5,  3,  1,  4,
	3,  3,  0,  2,  3,  3,  3,  4,  2,  4,  4,  2,  2,  4,  3,  3,  3,  4,  6,  3,  3,  2,  5,  3,  1,  4,  2,  1,
	2,  2,  3,  2,  2,  4,  6,  0,  4,  2,  4,  3,  4,  5,  5,  1,  4,  5,  5,  3,  2,  3,  3,  1,  1,  1,  1,  3,
	5,  5,  4,  5,  5,  4,  4,  3,  3,  3,  1,  3,  3,  3,  3,  3,  3,  1,  3,  3,  3,  5,  4,  2,  5,  3,  1,  4,
	2,  2,  6,  2,  2,  2,  2,  4,  4,  3,  4,  4,  4,  3,  4,  1,  3,  5,  3,  3,  3,  2,  2,  6,  2,  2,  2,  2,
	5,  1,  3,  3,  5,  4,  3,  2,  2,  6,  2,  2,  2,  2,  4,  1,  3,  5,  3,  1,  3,  3,  3,  1,  1,  1,  1,  3,
	6,  2,  3,  1,  4,  5,  4,  3,  3,  3,  2,  3,  3,  3,  1,  2,  3,  1,  4,  1,  1,  3,  3,  3,  4,  3,  2,  3,
	1,  3,  1,  1,  4,  2,  1,  3,  3,  3,  2,  3,  3,  3,  4,  4,  2,  4,  5,  4,  2,  4,  1,  6,  4,  4,  4,  2,
	1,  3,  2,  4,  3,  1,  1,  4,  4,  4,  4,  4,  4,  2,  3,  3,  4,  4,  3,  3,  4,  3,  5,  3,  1,  3,  1,  3,
	5,  2,  2,  5,  4,  3,  5,  3,  3,  3,  1,  3,  5,  3,  3,  1,  4,  4,  5,  3,  3,  3,  1,  3,  2,  2,  2,  3,
	3,  3,  4,  4,  3,  3,  4,  3,  5,  3,  3,  5,  5,  3,  5,  4,  2,  5,  5,  3,  5,  3,  3,  3,  1,  1,  5,  3,
	3,  3,  3,  1,  3,  3,  3,  5,  1,  1,  3,  4,  3,  3,  1,  3,  1,  1,  4,  2,  1,  5,  5,  2,  1,  4,  1,  1,
	5,  4,  2,  5,  5,  3,  5,  3,  3,  2,  3,  5,  5,  1,  3,  3,  3,  5,  3,  3,  3,  5,  4,  2,  5,  3,  1,  4,
	2,  1,  1,  4,  3,  4,  4,  3,  3,  3,  2,  3,  3,  3,  5,  3,  2,  5,  4,  3,  3,  3,  3,  3,  1,  1,  5,  3,
	3,  3,  3,  5,  3,  3,  3,  4,  3,  2,  2,  3,  1,  6,  0,  3,  6,  2,  3,  3,  3,  3,  3,  1,  1,  1,  1,  3,
	3,  3,  3,  2,  3,  3,  3,  2,  1,  6,  2,  4,  5,  2,  3,  3,  3,  2,  3,  3,  3,  3,  1,  5,  5,  3,  3,  3,
	3,  3,  0,  2,  3,  3,  3,  3,  3,  1,  1,  1,  1,  3,  6,  2,  3,  1,  4,  5,  4,  3,  3,  3,  2,  3,  3,  3,
	1,  2,  3,  1,  4,  1,  1,  3,  3,  3,  4,  3,  2,  3,  1,  3,  1,  1,  4,  2,  1,  3,  3,  3,  2,  3,  3,  3,
	3,  3,  3,  4,  3,  3,  3,  2,  3,  1,  3,  0,  1,  1,  3,  3,  3,  3,  4,  3,  3,  3,  3,  3,  3,  2,  2,  3,
	4,  4,  4,  2,  2,  3,  4,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  3,  2,  2,  3,  2,  2,  2,  2,
	1,  3,  2,  4,  3,  1,  1,  0,  1,  2,  2,  4,  5,  3,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,  2,
	2,  3,  3,  2,  2,  3,  3,  2,  3,  3,  2,  2,  3,  2,  3,  1,  3,  4,  3,  3,  3,  1,  1,  4,  4,  2,  4,  2,
	3,  4,  3,  4,  3,  4,  4,  3,  2,  3,  2,  3,  3,  3,  1,  2,  2,  2,  4,  4,  2,  3,  4,  4,  4,  3,  5,  3,
	3,  2,  4,  4,  3,  3,  3,  3,  1,  5,  5,  3,  3,  3,  1,  3,  1,  1,  4,  2,  1,  3,  1,  1,  3,  3,  1,  3,
	2,  3,  3,  2,  2,  3,  3,  3,  4,  4,  4,  3,  5,  3,  3,  3,  1,  2,  2,  5,  3,  3,  1,  3,  5,  3,  3,  3,
	1,  3,  3,  3,  4,  3,  3,  3,  3,  1,  1,  2,  1,  3,  0,  1,  3,  3,  4,  3,  3,  2,  3,  3,  2,  2,  3,  2,
	3,  2,  4,  4,  3,  3,  3,  3,  1,  3,  5,  3,  3,  3,  3,  3,  3,  2,  3,  3,  6,  0,  2,  1,  2,  3,  4,  4,
	4,  1,  3,  3,  4,  4,  2,  4,  3,  2,  4,  3,  3,  4,  3,  3,  2,  4,  4,  4,  3,  3,  2,  1,  4,  3,  3,  3,
	4,  2,  1,  4,  5,  4,  2,  4,  2,  1,  2,  3,  4,  4,  2,  2,  1,  2,  1,  2,  2,  1,  1,  3,  3,  3,  5,  1,
	2,  5,  3,  2,  5,  3,  3,  2,  3,  1,  1,  1,  6,  2,  1,  4,  4,  1,  4,  6,  5,  2,  2,  1,  6,  6,  2,  4,
	2,  2,  1,  2,  5,  4,  2,  4,  3,  2,  4,  3,  3,  4,  4,  3,  3,  3,  3,  1,  1,  1,  0,  3,  3,  3,  3,  3,
	1,  0,  2,  2,  4,  5,  2,  1,  0,  3,  1,  3,  1,  3,  1,  0,  2,  5,  2,  2,  5,  1,  0,  3,  2,  2,  5,  2,
	2,  2,  1,  4,  4,  4,  2,  2,  4,  0,  2,  4,  2,  4,  5,  4,  2,  1,  5,  4,  1,  2,  1,  2,  2,  2,  4,  2,
	5,  2,  1,  5,  4,  2,  1,  2,  4,  2,  4,  6,  2,  4,  4,  2,  2,  2,  5,  4,  4,  4,  1,  4,  2,  3,  6,  5,
	1,  4,  4,  1,  4,  6,  5,  3,  5,  3,  5,  3,  6,  5,  2,  1,  2,  4,  4,  6,  5,  3,  3,  3,  3,  3,  6,  5,
	5,  5,  3,  3,  3,  3,  2,  2,  3,  3,  2,  4,  3,  2,  4,  2,  1,  4,  5,  4,  2,  2,  4,  0,  0,  5,  4,  2,
	1,  0,  2,  5,  2,  2,  5,  4,  0,  5,  5,  5,  3,  4,  3,  3,  1,  4,  3,  1,  2,  5,  1,  3,  3,  3,  5,  5,
	2,  2,  5,  4,  5,  4,  4,  2,  2,  3,  4,  5,  4,  2,  2,  2,  1,  2,  5,  4,  2,  3,  3,  3,  2,  5,  4,  3,
	3,  2,  2,  2,  4,  3,  3,  2,  3,  3,  2,  4,  3,  2,  4,  2,  2,  3,  3,  5,  2,  2,  2,  3,  4,  5,  4,  6,
};

// Init the ai tables
void ai_init(void)
{
	// Loop over 5 value ranges, and 0 to 4 pieces of each player
	// in a row

	for(char k=0; k<5; k++)
	{
		for(char i=0; i<5; i++)
		{
			for(char j=0; j<5; j++)
			{
				if (i && j)
					fscore[25 * k + PLAYER2_INC * i + j] = 0;
				else if (i == 4 || j == 4)
					fscore[25 * k + PLAYER2_INC * i + j] = score[i] - score[j];
				else
					fscore[25 * k + PLAYER2_INC * i + j] = (score[i] - score[j]) * (1 + k);
			}
		}
	}

	// Create the indices of all 69 rows

	char	p = 0;

	// Vertical rows
	for(char x=0; x<7; x++)
	{
		for(char y=0; y<3; y++)
		{
			frows[0][p] = x + 8 * y;
			frows[1][p] = x + 8 * y + 8;
			frows[2][p] = x + 8 * y + 16;
			frows[3][p] = x + 8 * y + 24;
			frows[4][p] = 25 * (y + 1);
			p++;
		}
	}

	for(char x=0; x<4; x++)
	{
		// Horizontal rows
		for(char y=0; y<6; y++)
		{
			frows[0][p] = x + 8 * y;
			frows[1][p] = x + 8 * y + 1;
			frows[2][p] = x + 8 * y + 2;
			frows[3][p] = x + 8 * y + 3;
			frows[4][p] = y > 0 ? 25 * (y - 1) : 0;
			p++;
		}

		// Diagonal down rows
		for(char y=0; y<3; y++)
		{
			frows[0][p] = x + 8 * y;
			frows[1][p] = x + 8 * y + 9;
			frows[2][p] = x + 8 * y + 18;
			frows[3][p] = x + 8 * y + 27;
			frows[4][p] = 25 * (y + 1);
			p++;
		}

		// Diagonal up rows
		for(char y=3; y<6; y++)
		{
			frows[0][p] = x + 8 * y;
			frows[1][p] = x + 8 * y - 7;
			frows[2][p] = x + 8 * y - 14;
			frows[3][p] = x + 8 * y - 21;
			frows[4][p] = 25 * (y - 2);
			p++;
		}
	}
}

// Evaluate the current board state for the player

int board_eval(bool player)
{
	int	score = 0;

	// Loop through all rows
	for(char r=0; r<69; r++)
	{
		// Build score index for row
		char	sum = 
			board.fcols[frows[0][r]] +
			board.fcols[frows[1][r]] +
			board.fcols[frows[2][r]] +
			board.fcols[frows[3][r]] +
			frows[4][r];

		// Help compiler to avoid two byte pointer arithmetic
		__assume(sum < 128);

		// Add score to board score
		score += fscore[sum];
	}

	// Check if there was a row with four winning pieces and
	// normalize to +/- 32000 then

	if (score < -8192)
		score = -32000;
	else if (score >= 8192)
		score = 32000;

	// Invert score for other player

	return player ? score : -score;
}

CharWin	cwt, cws;
CharWin	cw, cwm;

char	buff[20];

static const char optx[7] = {3, 2, 4, 1, 5, 0, 6};

// Find a good move for the player using alpha/beta pruning
// see: https://en.wikipedia.org/wiki/Alpha-beta_pruning

int board_check(char level, int alpha, int beta, bool player)
{
	int		best = alpha;
	bool	checked = false;

	// Current score to cutof early if clear win condition
	int 	val = board_eval(player) + level;

	// Best move
	char	zx = 0;

	// Check for end of search
	if (level < 5 && val < 5000 && val > -5000)
	{
		// Update current check display at level 3
		if (level == 3)
		{
			board_draw(&cwt);
			itoa(val, buff, 10);
			cwin_fill_rect(&cwt, 0, 6, 7, 1, ' ', 1);
			cwin_putat_string(&cwt, 0, 6, buff, 1);
		}

		// Check all seven columns
		for(char i=0; i<7; i++)
		{
			// Start check in the center, due to higher value of
			// pieces there

			char ix = optx[i];

			// Try to drop a piece

			if (board_drop(ix, player))
			{
				// We still have a move
				checked = true;

				// Check score of position
				int	score = - board_check(level + 1, -beta, -best, !player);

				// Better than what we have so far
				if (score > best)
				{
					// Remember
					best = score;						
					zx = ix;

					// Update best move if on level zero
					if (level == 0) 
					{
						board_draw(&cw);
						itoa(best, buff, 10);
						cwin_fill_rect(&cw, 0, 6, 7, 1, ' ', 1);
						cwin_putat_string(&cw, 0, 6, buff, 1);		
					}

					// Check for beta pruning
					if (best > beta)
					{
						board_undrop(ix);
						break;
					}
				}

				// Undo move
				board_undrop(ix);
			}
		}
	}

	// Return new position if first move level
	if (level == 0)
		return zx;
	else if (checked)		
		return best;
	else
		return val;
}

// Return a move from the opening library if available

char board_opening(char step, char * moves)
{
	switch (step)
	{
		case 1:
			return open1;
		case 2:
			if (moves[0] < 4)
				return open2[moves[0] * 7 + moves[1]];
			else
				return 6 - open2[(6 - moves[0]) * 7 + (6 - moves[1])];

		case 3:
			if (moves[0] < 4)
				return open3[moves[0] * 49 + moves[1] * 7 + moves[2]];
			else
				return 6 - open3[(6 - moves[0]) * 49 + (6 - moves[1]) * 7 + (6 - moves[2])];

		case 4:
			if (moves[0] < 4)
				return open4[moves[0] * 343 + moves[1] * 49 + moves[2] * 7 + moves[3]];
			else
				return 6 - open4[(6 - moves[0]) * 343 + (6 - moves[1]) * 49 + (6 - moves[2]) * 7 + (6 - moves[3])];

		default:
			return 0xff;
		}
}

// Current state of the game
struct Game
{
	GameState	state;		// State
	char		count;		// Auto continue counter
	char		posx;		// Column osition of new piece
	char		step;		// Number of player moves
	char		moves[21];	// Pieces placed by player

}	TheGame;

// Set new game state

void game_state(GameState state)
{
	// Set new state
	TheGame.state = state;

	// Clear status line

	cwin_fill_rect(&cws, 0, 0, 40, 1, ' ', 1);

	switch (state)
	{
	case GS_READY:
		// Start play in one second
		TheGame.count = 60;
		TheGame.step = 0;

		// Clear the board
		board_init();
		board_init_main(&cwm);
		break;

	case GS_PLAYER_MOVE:
		// Place selection sprite in center
		TheGame.posx = 3;
		column_select_anim(&cwm, 3, 3, 7);		
		cwin_putat_string(&cws, 0, 0, P"PLAYER MOVE", 7);
		break;		

	case GS_COMPUTER_MOVE:
		cwin_putat_string(&cws, 0, 0, P"COMPUTER MOVE", 2);
		break;

	case GS_PLAYER_WIN:
		cwin_putat_string(&cws, 0, 0, P"PLAYER WINS", 7);

		// Continue in 4 seconds
		TheGame.count = 240;
		break;

	case GS_COMPUTER_WIN:
		cwin_putat_string(&cws, 0, 0, P"COMPUTER WINS", 2);

		// Continue in 4 seconds
		TheGame.count = 240;
		break;

	case GS_GAME_OVER:
		cwin_putat_string(&cws, 0, 0, P"NO MORE MOVES", 1);

		// Continue in 4 seconds
		TheGame.count = 240;
		break;
	}
}

// Main game loop

void game_loop()
{
	char	bx = 0xff, cx;

	switch (TheGame.state)
	{
	case GS_READY:
		if (!--TheGame.count)
			game_state(GS_PLAYER_MOVE);	
		break;

	case GS_PLAYER_MOVE:
		// Current selection
		cx = TheGame.posx;

		// Check for key pressed
		if (kbhit())
		{
			// Get key
			char	ch = getch();	

			// Numbers 1 to 7 drop immediate
			if (ch >= '1' && ch <= '7')
				bx = cx = ch - '1';
		}
		else
		{
			// Check Joystick
			joy_poll(0);

			// Move cursor
			if (joyx[0] < 0 && cx > 0)
				cx--;
			else if (joyx[0] > 0 && cx < 6)
				cx++;
			else if(joyb[0])
				bx = cx;
		}

		// Move selection piece
		column_select_anim(&cwm, TheGame.posx, cx, 7);
		TheGame.posx = cx;

		if (bx != 0xff)
		{
			// Drop piece into board
			if (board_drop(bx, true))
			{
				// Remember move
				TheGame.moves[TheGame.step++] = bx;

				// Play drop animation
				item_drop_anim(&cwm, bx, board.ffree[bx], 7);

				// Show new state of the board
				board_draw_main(&cwm);
				spr_show(0, false);

				// Check for win condition
				if (board_eval(true) == 32000)
					game_state(GS_PLAYER_WIN);
				else
					game_state(GS_COMPUTER_MOVE);
			}
		}
		break;

	case GS_COMPUTER_MOVE:
		// Check for opening move or calculate next move
		bx = board_opening(TheGame.step, TheGame.moves);
		if (bx == 0xff)
			bx = board_check(0, -32767, 32767, false);

		// Drop piece into board
		if (board_drop(bx, false))
		{
			// Play drop animation
			item_drop_anim(&cwm, bx, board.ffree[bx], 2);

			// Show new state of the board
			board_draw_main(&cwm);
			spr_show(0, false);

			// Check for loss or draw condition
			if (board_eval(false) == 32000)
				game_state(GS_COMPUTER_WIN);
			else if (TheGame.step == 21)
				game_state(GS_GAME_OVER);
			else
				game_state(GS_PLAYER_MOVE);
		}
		break;

	case GS_PLAYER_WIN:
	case GS_COMPUTER_WIN:
	case GS_GAME_OVER:
		if (!--TheGame.count)
			game_state(GS_READY);	
		break;
	}
}


int main(void)
{
	mmap_trampoline();

	// Install character set
	mmap_set(MMAP_RAM);
	memcpy(Font, charset, 2048);
	memcpy(Sprites, spriteset, 256);
	mmap_set(MMAP_NO_BASIC);

	// Switch screen
	vic_setmode(VICM_TEXT_MC, Screen, Font);

	spr_init(Screen);

	// Change colors
	vic.color_border = VCOL_BLUE;
	vic.color_back = VCOL_BLACK;
	vic.color_back1 = VCOL_BLUE;
	vic.color_back2 = VCOL_WHITE;

	vic.spr_mcolor0 = VCOL_DARK_GREY;
	vic.spr_mcolor1 = VCOL_WHITE;

	// Clear screen
	memset(Screen, 96, 1000);
	memset(Color, 15, 1000);

	// Prepare char wins
	cwin_init(&cws, Screen, 5, 0, 30, 1);
	cwin_init(&cwm, Screen, 2, 3, 28, 19);
	cwin_init(&cw, Screen, 30, 6, 8, 7);
	cwin_init(&cwt, Screen, 30, 15, 8, 7);

	// Init AI
	ai_init();

	game_state(GS_READY);

	for(;;)		
	{
		game_loop();
		vic_waitFrame();
	}	


	return 0;
}
