#include <iostream>
#include <stack>
#include <set>
#include <conio.h>
#include <time.h>
#include <windows.h>
using namespace std;

const int NUM_STACKS=10;
const short GAME_LEFT=10;			
const short GAME_BOTTOM=24;
const short GAME_TOP=0;
const int GOAL_SUM=10;
const int NICE_CHANCE=50;			// % of the time a useful number will be generated rather than a random number

const int BASE_GAME_SPEED=4100;		// ms delay on level 1 (plus the speedup)
const int SPEEDUP_PER_LEVEL=100;	// ms removed from delay
const int MAX_GAME_SPEED=250;		// delay wont go below 250 ms

struct cell
{
	int value;
	bool selected;
};
enum colors
{
	black,	 blue,   green,   cyan,   red,   purple,   yellow,   white, 
	BRblack, BRblue, BRgreen, BRcyan, BRred, BRpurple, BRyellow, BRwhite
};
enum keys
{	KEY_LEFT=75,	KEY_RIGHT=77,	KEY_ENTER=13 };

void playGame(int startingNums=5);
void setScreenPos(COORD);
void setColor(int foreground, int background);
bool addNums(stack<cell> *, int);
void highlightStack(int stackIndex, bool unhighlight=false);
int removeAllSelectedCells(stack<cell> *, bool popAsWell=false);
void displaySum(int sum);
void displayLevel(int level);
void displayScore(int score, int nextLevelScore);
int getSumOfStackTops(const stack<cell> *stacks);

int selectCell(stack<cell> *, int selectedStack, int sum);
void displayStackTop(const stack<cell> *stacks, int stackNum, int color=BRyellow);

int main()
{
	srand( static_cast<unsigned int>( time(NULL) ) );

	cout << "How to play:\n\n"
		 << "You will have " << NUM_STACKS << " stacks that will gradually fill up with numbers between 1 & 9\n"
		 << "If any one stack reaches the top of the screen it's game over for you!\n"
		 << "To prevent this add numbers together to a sum of " << GOAL_SUM << endl
		 << "You can use the left & right arrow keys to select a stack,\n"
		 << "and enter to select the top-most number in that stack.  Once selected the \n"
		 << "number will be highlighted in red.  Although you may only select the number at\n"
		 << "the top of the stack it can remain selected even when more numbers have piled \n"
		 << "on top of it.\n"
		 << "The sum of all selected numbers will be shown at the bottom of the screen.\n"
		 << "When the sum is exactly equal to " << GOAL_SUM << " the selected numbers will \n"
		 << "be removed and you will be given points. The number of points given is \n"
		 << "determined by the quantity of numbers you have removed, so it would be better\n"
		 << "to use multiple small numbers than only a few large ones.\n"
		 << "If the sum of the numbers is greater than " << GOAL_SUM << " then all selected numbers will\n"
		 << "be unselected and your sum reset to 0.\n"
		 << "The game will start out slow and get progressivly faster.\n"
		 << "I hope you're good at adding!!\n";

	cout << "\n -=-{ Press any key to play }-=-\n";
	_getch();
	
	char key;
	do {
		playGame(15);
		cout << "Play again? ";
		do {
			key=toupper(_getch());
		} while ( key != 'N' && key != 'Y' );
	} while ( key != 'N' );

	return 0;
}

void playGame(int startingNums)
{
	system("cls");
	COORD c;

	// display keys
	c.Y=3;
	c.X=GAME_LEFT+(NUM_STACKS*3)+3;
	setScreenPos(c);
	setColor(white,black);
	cout << "<-- Left/A   Right/D -->";
	c.Y++;	setScreenPos(c);
	cout << "[Enter] Select top of stack";
	c.Y++;	setScreenPos(c);
	cout << "[Space] Unselect all";
	// draw left & right borders
	setColor(BRcyan,black);
	for ( c.Y=0; c.Y <= GAME_BOTTOM; c.Y++ )
	{
		c.X=GAME_LEFT-2; setScreenPos(c);
		cout << char(179);
		c.X=GAME_LEFT+(NUM_STACKS*3)-1; setScreenPos(c);
		cout << char(179);		
	}

	stack<cell> *stacks = new stack<cell>[NUM_STACKS];
	int selectedStack=int(NUM_STACKS/2);	// select middle stack by default

	int timeDelay=BASE_GAME_SPEED;
	int gameLevel=1;
	displayLevel(gameLevel);
	int score=0;
	int pointsUntilNextLevel=10;
	displayScore(score,pointsUntilNextLevel);

	// generate some starting numbers
	for ( int i=0; i < startingNums; i++ )
	{
		addNums(stacks,gameLevel);
	}

	clock_t lastTick=clock();
	clock_t thisTick;

	int sum=0;

	do {

		highlightStack(selectedStack);
		displaySum(sum);

		setColor(black,black);

		do {
			if ( _kbhit() )
			{
				char key=_getch();
				if ( key == -32 ) key=_getch();

				if ( key == KEY_LEFT || 'a' == key || 'A' == key )
				{
					highlightStack(selectedStack,true);
					selectedStack--;	if ( selectedStack < 0 ) selectedStack=0;
					highlightStack(selectedStack);
				} 
				else if ( key == KEY_RIGHT || 'd' == key || 'D' == key )
				{
					highlightStack(selectedStack,true);
					selectedStack++; if ( selectedStack >= NUM_STACKS ) selectedStack=NUM_STACKS-1; 
					highlightStack(selectedStack);
				} 
				else if ( key == 32 )
				{
					removeAllSelectedCells(stacks);
					sum=0;
				} 
				else if ( key == KEY_ENTER )
				{
					if ( !stacks[selectedStack].empty() )
						sum=selectCell(stacks,selectedStack,sum);
					if ( sum > GOAL_SUM ) 
					{	
						removeAllSelectedCells(stacks);
						sum=0;
					} else {
						if ( sum == GOAL_SUM )
						{
							cout << "\b";
							score += removeAllSelectedCells(stacks,true);
							sum=0;
							if ( score >= pointsUntilNextLevel )
							{
								pointsUntilNextLevel = score+(GOAL_SUM*gameLevel*(1.25));
								gameLevel++;
								timeDelay=BASE_GAME_SPEED-(SPEEDUP_PER_LEVEL*gameLevel);
								if ( timeDelay < MAX_GAME_SPEED ) timeDelay=MAX_GAME_SPEED;
								displayLevel(gameLevel);
							}
							displayScore(score,pointsUntilNextLevel);
						}
					}
					displaySum(sum);					
				}
			}

			thisTick=clock();
		} while ( thisTick - lastTick < timeDelay );

		lastTick=thisTick;

	} while ( addNums(stacks,gameLevel) );

	c.Y=GAME_BOTTOM-3;
	c.X=GAME_LEFT+(NUM_STACKS*3)+3;
	setScreenPos(c);
	setColor(BRwhite,black);
	cout << "Game Over! ";
	delete [] stacks;
}

int selectCell(stack<cell> *stacks, int selectedStack, int sum)
{
	
	if ( stacks[selectedStack].top().selected )	{
		// cell is already seleced, deselected it and remove it from the sum
		stacks[selectedStack].top().selected=false;
		sum-=stacks[selectedStack].top().value;
	} else {
		// select cell and add it to the sum
		stacks[selectedStack].top().selected=true;
		sum+=stacks[selectedStack].top().value;
	}

	// update display
	displayStackTop(stacks, selectedStack);

	return sum;
}

void displayStackTop(const stack<cell> *stacks, int stackNum, int color)
{
	COORD c;
	c.X=GAME_LEFT+(stackNum*3);
	c.Y=GAME_BOTTOM-stacks[stackNum].size();
	setScreenPos(c);
	if ( !stacks[stackNum].empty() )	
	{
		if ( stacks[stackNum].top().selected )
			setColor(BRyellow,BRred);
		else
			setColor(color,black);
		cout << stacks[stackNum].top().value;
	} 

	// fill up the area above the stack top with spaces to clear old stack entries
	c.Y--;
	setColor(BRwhite,black);
	while ( c.Y >= 0 )
	{
		setScreenPos(c);
		cout << " ";
		c.Y--;
	}
}

bool addNums(stack<cell> *stacks, int gameLevel)
{
	// returns false if one of the stacks was full (game over man!)

	int stacksToAddNumbersTo=( rand()%gameLevel )+1;

	set<int> stackIndex;	// use set to avoid duplicate entries
	for ( int i=0; i < stacksToAddNumbersTo; i++ )
	{
		int thisStackIndex=rand()%NUM_STACKS;
		stackIndex.insert(thisStackIndex);
	}

	set<int>::iterator si=stackIndex.begin();
	while ( si != stackIndex.end() )
	{
		if ( stacks[*si].size() >= GAME_BOTTOM ) return false;

		int chance=rand()%100;
		int thisNumber;
		if ( chance <= NICE_CHANCE )
		{
			// add a number the user can use
			thisNumber = 9 - (getSumOfStackTops(stacks) % GOAL_SUM);
			if ( thisNumber < 1 ) thisNumber = 1;
			if ( thisNumber > 9 ) thisNumber = 9;
		} else {
			// add a random number
			thisNumber=rand()%9 + 1; // 1-9
		}

		cell newCell;
		newCell.selected=false;
		newCell.value=thisNumber;

		displayStackTop(stacks,*si,white);
		stacks[*si].push(newCell);
		displayStackTop(stacks,*si);

		si++;
	}

	return true;
}

int getSumOfStackTops(const stack<cell> *stacks)
{
	int sum=0;
	for ( int i=0; i < NUM_STACKS; i++ )
	{
		if ( !stacks[i].empty() )
			sum+=stacks[i].top().value;
	}
	return sum;
}
void displaySum(int sum)
{
	COORD c;
	c.Y=GAME_BOTTOM/2;
	c.X=GAME_LEFT+int(NUM_STACKS*3)+3;
	
	setColor(BRyellow,black);

	static int previousSum=sum;
	if ( sum != previousSum && previousSum > 9 )
	{
		// may need to clear the previous number
		setScreenPos(c);
		cout << "                  ";	
	}

	previousSum=sum;

	setScreenPos(c);
	// display sum of selected cells
	cout << "SUM: " << sum;
}

void displayLevel(int level)
{
	COORD c;
	c.Y=(GAME_BOTTOM/2)+1;
	c.X=GAME_LEFT+int(NUM_STACKS*3)+3;
	setScreenPos(c);
	setColor(BRyellow,black);

	cout << "Level: " << level;
}

void displayScore(int score, int nextLevelScore)
{
	COORD c;
	c.Y=(GAME_BOTTOM/2)+2;
	c.X=GAME_LEFT+int(NUM_STACKS*3)+3;
	setScreenPos(c);
	setColor(BRyellow,black);

	cout << "Score: " << score << "/" << nextLevelScore;
}

int removeAllSelectedCells(stack<cell> *stacks, bool popAsWell)
{	
	// unselects all selected cells, and maybe removes them off the stack too
	// returns the number of cells removed (this will be added to the player's score)

	int cellsRemoved=0;

	for ( int stk=0; stk < NUM_STACKS; stk++ )
	{
		stack<cell> temp;
		do 
		{
			if ( !stacks[stk].empty() ) 
			{
				if ( !(popAsWell && stacks[stk].top().selected) ) {
					temp.push(stacks[stk].top());
					temp.top().selected=false;	
				} else {
					cellsRemoved++;
				}
				stacks[stk].pop();		
			}
		} while ( !stacks[stk].empty() );
		
		do 
		{
			if ( !temp.empty() )
			{
				stacks[stk].push(temp.top());
				temp.pop();
			}

			if ( temp.size() == 0 ) 
				displayStackTop(stacks,stk);
			else
				displayStackTop(stacks,stk,white);

		} while ( !temp.empty() );		
	}

	return cellsRemoved;
}

void setScreenPos(COORD coord)
{
	SetConsoleCursorPosition ( GetStdHandle(STD_OUTPUT_HANDLE), coord);
}

void setColor(int foreground, int background)
{
	int color=foreground;
	color+=(16*background);

	HANDLE hConsole;
	hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hConsole,color);
}

void highlightStack(int stackIndex, bool unhighlight)
{
	COORD c;
	char chBottom[4], chEdge;
	if ( unhighlight ) 
	{
		chBottom[0]=char(32);	chBottom[1]=char(32);	chBottom[2]=char(32);
		chEdge=char(32);	
	}
	else
	{
		chBottom[0]=char(211);	chBottom[1]=char(196);	chBottom[2]=char(189);
		chEdge=char(186);	
	}
	chBottom[3]=NULL;

	// draw bottom
	c.Y=GAME_BOTTOM;	c.X=GAME_LEFT+(stackIndex*3)-1;
	setScreenPos(c); setColor(BRgreen,black);
	cout << chBottom;

	
	// draw edges
	for ( short row=GAME_TOP; row < GAME_BOTTOM; row++ )
	{
		c.X=GAME_LEFT+(stackIndex*3)-1;
		c.Y=row;
		setScreenPos(c);
		cout << chEdge;
		c.X+=2;
		setScreenPos(c);
		cout << chEdge;
	}
}
