/*
* Neural Networks Tearm Project
* Martin Johne
* 04.10.2006
*/

#include <irrlicht.h>
#include <stdio.h>
#include "consoleCommands.h"
#include "Muele.h"
#include "TestWin.h"
#include "PreferencesWindow.h"
#include "LookUpTable.h"
#include "AICompetitionWindow.h"
#include "NeuralNetwork.h"
#include "Symmetry.h"
#include "NeuralNetworkWindow.h"

using namespace irr;

#pragma comment(lib, "Irrlicht.lib")

//#define _DEBUG

/****************************************************************************************
* destructor ****************************************************************************/

CMuele::~CMuele()
{
	delete[] pBoard;
	delete[] pWinnerBoard;
	delete   pNeuralNetwork;
	delete   pSymmetry;
	delete[] pMoveHistory;
}

/****************************************************************************************
* constructor ***************************************************************************/

CMuele::CMuele(): pBoard(NULL), pWinnerBoard(NULL), pMoveHistory(NULL), pNeuralNetwork(NULL), pSymmetry(NULL), pLookUpTable(NULL)
{
	boardSizeX = 3;
	boardSizeY = 3;
	boardSizeZ = 1;
	winFieldCount = 3;
	playerOneType = PLAYER_TYPE_HUMAN;
	playerTwoType = PLAYER_TYPE_AINN;

	materialCylinder.SpecularColor.set(0, 0, 0, 0);
	materialCylinder.AmbientColor.set(0, 0, 0, 0);
	materialCylinder.EmissiveColor.set(0, 155, 155, 155);
	materialCylinder.DiffuseColor.set(0, 155, 155, 155);

	materialSphere[PLAYER_1].SpecularColor.set(0, 0, 0, 0);
	materialSphere[PLAYER_1].AmbientColor.set(0, 0, 0, 0);
	materialSphere[PLAYER_1].EmissiveColor.set(0, 170, 60, 60);
	materialSphere[PLAYER_1].DiffuseColor.set(0, 170, 60, 60);

	materialSphere[PLAYER_2].SpecularColor.set(0, 0, 0, 0);
	materialSphere[PLAYER_2].AmbientColor.set(0, 0, 0, 0);
	materialSphere[PLAYER_2].EmissiveColor.set(0, 60, 60, 170);
	materialSphere[PLAYER_2].DiffuseColor.set(0, 60, 60, 170);

	materialSphereWon[PLAYER_1].SpecularColor.set(0, 0, 0, 0);
	materialSphereWon[PLAYER_1].AmbientColor.set(0, 0, 0, 0);
	materialSphereWon[PLAYER_1].EmissiveColor.set(0, 240, 70, 70);
	materialSphereWon[PLAYER_1].DiffuseColor.set(0, 240, 70, 70);

	materialSphereWon[PLAYER_2].SpecularColor.set(0, 0, 0, 0);
	materialSphereWon[PLAYER_2].AmbientColor.set(0, 0, 0, 0);
	materialSphereWon[PLAYER_2].EmissiveColor.set(0, 70, 70, 240);
	materialSphereWon[PLAYER_2].DiffuseColor.set(0, 70, 70, 240);

	boardFieldDistance = 6;
//	showPropabilities = true;
	trainingRunning = false;

	resetBoard(false);
}

/****************************************************************************************/
//! Sets the board to a new size.

void CMuele::resetBoard(bool keepNeuralNetwork)
{
	if ( boardSizeZ < 3)
	{
		if ( boardSizeX < 3 ) boardSizeX = 3;
		if ( boardSizeY < 3 ) boardSizeY = 3;
	}
	if ( boardSizeX < 1 ) boardSizeX = 1;
	if ( boardSizeY < 1 ) boardSizeY = 1;
	if ( boardSizeZ < 1 ) boardSizeZ = 1;

	if ( boardSizeX > 9 ) boardSizeX = 9;
	if ( boardSizeY > 9 ) boardSizeY = 9;
	if ( boardSizeZ > 9 ) boardSizeZ = 9;

	boardFieldCount = boardSizeX * boardSizeY * boardSizeZ;
	boardFieldCountPlain = boardSizeX * boardSizeY;
	winFieldCountTimesX = winFieldCount * boardSizeX;

	if ( !keepNeuralNetwork )
	{
		delete pNeuralNetwork;
		pNeuralNetwork = new CNeuralNetwork(boardSizeX, boardSizeY, boardSizeZ, winFieldCount, 30, true, this);
	}

	delete[] pBoard;
	pBoard = new char[boardFieldCount];

	delete pSymmetry;
	pSymmetry = new CSymmetry( boardSizeX, boardSizeY, boardSizeZ );

	delete[] pWinnerBoard;
	pWinnerBoard = new char[boardFieldCount];

	delete[] pMoveHistory;
	pMoveHistory = new int[boardFieldCount];
	
	halfBoardWidthX = ((boardSizeX - 1) * boardFieldDistance) / 2;
	halfBoardWidthY = ((boardSizeY - 1) * boardFieldDistance) / 2;
	
	restartBoard();
}

/****************************************************************************************/
//! Clears the board for a new game.

void CMuele::restartBoard()
{
	memset(pBoard, 0, boardFieldCount);
	memset(pWinnerBoard, 0, boardFieldCount);
	memset(pMoveHistory, 0, sizeof(int) * boardFieldCount);

	selectedCylinder = -1;
	gameFinished = 0;
	moveNumber = 0;
	actualPlayer = lastPlayer = PLAYER_1;
	actualPlayerType = lastPlayerType = playerOneType;
}

/****************************************************************************************/
//! Receives all events from Irrlicht.

bool CMuele::OnEvent(SEvent event)
{
	// *********************
	// * mouse pressed event	
	if ( event.EventType == irr::EET_MOUSE_INPUT_EVENT && event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN )
	{
		if ( pGuiEnv->getRootGUIElement()->getElementFromPoint( core::position2d<s32>(event.MouseInput.X, event.MouseInput.Y)) !=  pGuiEnv->getRootGUIElement() )
			return false;
		// restart board if the game was already finished
		if ( gameFinished ) restartBoard();

		// a cylinder is selected
		if ( selectedCylinder >= 0 && 
		     ((actualPlayer == PLAYER_1 && playerOneType == PLAYER_TYPE_HUMAN ) ||
			  (actualPlayer == PLAYER_2 && playerTwoType == PLAYER_TYPE_HUMAN )) )

			for ( int iz = 0; iz < boardSizeZ; iz ++)
				if ( *(pBoard + iz * boardFieldCountPlain + selectedCylinder) == EMTY_FIELD )
				{
					switchActualPlayer(iz * boardFieldCountPlain + selectedCylinder);
					break;
				}
	}
	else
	// *********************
	// * gui event
	if ( event.EventType == EET_GUI_EVENT )
	{
		// let the different windows process the GUI event
		pPreferencesWindow->onGuiEvent(event);
		pAICompetitionWindow->onGuiEvent(event);
		pNeuralNetworkWindow->onGuiEvent(event);
		
        // process gui even for the main application
		switch( event.GUIEvent.EventType )
		{
			case gui::EGET_MENU_ITEM_SELECTED:
				{
					gui::IGUIContextMenu* menu = (gui::IGUIContextMenu*)event.GUIEvent.Caller;
					s32 id = menu->getItemCommandId(menu->getSelectedItem());
			
					switch( id )
					{
						case ID_MENU_GAME_PREFERENCES: pPreferencesWindow->toggleVisiblility();	break;
						case ID_MENU_GAME_QUIT:		   exit(0); break;
						case ID_MENU_AI_COMPETITION:   pAICompetitionWindow->toggleVisiblility(); break;
						case ID_MENU_AI_NN_SETUP:	   pNeuralNetworkWindow->toggleVisiblility(); break;
						case ID_MENU_HELP:			   createInformationWindow(250, 130, L"HELP", L"A game using neural Networks. For further informations see doc directory.");
					}
					break;
				}

			case gui::EGET_BUTTON_CLICKED:
				if ( event.GUIEvent.Caller->getID() == ID_BUTTON_GENERAL_CLOSE ) event.GUIEvent.Caller->getParent()->remove();
				break;
		}
	}
	else
	// ****************************
	// * keyboard key pressed event
	if ( event.EventType == irr::EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown )
	{
		switch (event.KeyInput.Key)
		{
		case irr::KEY_ESCAPE:
				pDevice->drop();
				exit(0);
			break;
		// case IC_Console::IC_KEY_TILDE:
		case irr::KEY_INSERT:
		case  220:
			if ( !isVisible() )
				setVisible(true);
			else 
				if ( !event.KeyInput.Control )
					setVisible(false);
			return true;
		/*
		case irr::KEY_KEY_S:
			memcpy(pBoard, pSymmetry->getSymmetry(pBoard), boardFieldCount);
			return true;
		*/
		}

		if(isVisible())
		{
			handleKeyPress(event.KeyInput.Char, event.KeyInput.Key,event.KeyInput.Shift, event.KeyInput.Control);
			return true;
		}
	}
	else
	// ******************
	// * log text event
	if( event.EventType == irr::EET_LOG_TEXT_EVENT )
	{
		logMessage_ANSI(event.LogEvent.Level, event.LogEvent.Text);
		return true;
	}

	return false;
}

/****************************************************************************************
* switch the control to the other player************************************************/

void CMuele::switchActualPlayer(int moveOfActualPlayer)
{

	*(pBoard + moveOfActualPlayer) = actualPlayer;
	*(pMoveHistory + moveNumber) = moveOfActualPlayer;

	CTestWin checkWinner(pBoard, pWinnerBoard, boardSizeX, boardSizeY, boardSizeZ,
			actualPlayer, winFieldCount, 0);
	if ( checkWinner.testWin() ) gameFinished = 1;

	if ( actualPlayer == PLAYER_1 ) 
	{
		actualPlayer = PLAYER_2;
		actualPlayerType = playerTwoType;
		lastPlayer = PLAYER_1;
		lastPlayerType = playerOneType;
	}
	else
	{
		actualPlayer = PLAYER_1;
		actualPlayerType = playerOneType;
		lastPlayer = PLAYER_2;
		lastPlayerType = playerTwoType;
	}


	moveNumber++;
	if ( moveNumber == boardFieldCount ) gameFinished = 1;
}

/****************************************************************************************
* create an emty window in the middle of the screen *************************************/

gui::IGUIWindow* CMuele::createWindow(int sizeX, int sizeY, wchar_t *name)
{
	s32 width = pDriver->getScreenSize().Width / 2;
	s32 height = pDriver->getScreenSize().Height / 2;

	gui::IGUIWindow *pWindow = 
		pGuiEnv->addWindow(core::rect<s32>(width-sizeX/2, height-sizeY/2, width+sizeX/2, height+sizeY/2), true, name);

	return pWindow;
}

/****************************************************************************************
* create an window with given information text and an OK button *************************/

gui::IGUIWindow* CMuele::createInformationWindow(int sizeX, int sizeY, wchar_t *name, wchar_t *text)
{
	s32 width = pDriver->getScreenSize().Width / 2;
	s32 height = pDriver->getScreenSize().Height / 2;

	gui::IGUIWindow *pWindow = 
		pGuiEnv->addWindow(core::rect<s32>(width-sizeX/2, height-sizeY/2, width+sizeX/2, height+sizeY/2), true, name);

	pWindow->addChild( pGuiEnv->addStaticText(text, core::rect<s32>( 5, 25, sizeX - 5, sizeY - 40)));
	pWindow->addChild( pGuiEnv->addButton(core::rect<s32>(20, sizeY - 35, sizeX - 20, sizeY - 10 ), pWindow, ID_BUTTON_GENERAL_CLOSE, L"OK"));	

	pWindow->updateAbsolutePosition();
	return pWindow;
}


/****************************************************************************************/
//! This method is called from the standart main method and starts the program.

int CMuele::run(char *argv[])
{
	// let user select driver type
	IrrlichtDevice *pDevice;
	video::E_DRIVER_TYPE driverType;
	core::dimension2d<s32> dim;

/*
	IrrlichtDevice *device =
		createDevice(video::EDT_NULL, core::dimension2d<s32>(640, 480), 16, false);
	video::IVideoModeList *pVideoModeList =	device->getVideoModeList();

	core::dimension2d<s32> dim;

	for ( int ia = 0; ia < pVideoModeList->getVideoModeCount(); ia++ ) {
		dim =pVideoModeList->getVideoModeResolution(ia);
		
		printf("%d x %d : %d\n", dim.Width, dim.Height,pVideoModeList->getVideoModeDepth(ia));
	}
	int deskDepth = pVideoModeList->getDesktopDepth();
	
	*/		
//	delete device;

	char i = 'c';		

	printf("Please select the driver you want:\n"\
		" (a) Direct3D 9.0c\n (b) Direct3D 8.1\n (c) OpenGL 1.5\n"\
		/*" (d) Software Renderer\n"\*/
		" (e) Apfelbaum Software Renderer\n"\
		/*" (f) NullDevice\n"\*/
		"(otherKey) exit\n\n");

	//i='c';
	std::cin >> i;

	switch(i)
	{
		case 'a': driverType = video::EDT_DIRECT3D9;break;
		case 'b': driverType = video::EDT_DIRECT3D8;break;
		case 'c': driverType = video::EDT_OPENGL;   break;
		case 'e': driverType = video::EDT_SOFTWARE2;break;
		default: return 0;
	}

	printf("Please select a resolution you want:\n"\
		" (a) 640x480 window\n"\
		" (b) 800x600 window\n"\
		" (c) 1024x768 window\n"\
		" (d) 640x480 fullscreen\n"\
		" (e) 800x600 fullscreen\n"\
		" (f) 1024x768 fullscreen\n"\
		" (g) 1280x960 fullscreen\n"\
		" (h) 1280x1024 fullscreen\n");

	std::cin >> i;

	int x,y;
	bool fullScreen = false;

	switch(i)
	{
		case 'a': x=640; y=480; ;break;
		case 'b': x=800; y=600; break;		
		case 'c': x=1024; y=768; break;		
		case 'd': x=640; y=480; fullScreen=true; break;		
		case 'e': x=800; y=600; fullScreen=true; break;		
		case 'f': x=1024; y=768; fullScreen=true; break;		
		case 'g': x=1280; y=960; fullScreen=true; break;		
		case 'h': x=1280; y=1024; fullScreen=true; break;		
		default: return 0;
	}

	// create device

	 pDevice =
		createDevice(driverType, core::dimension2d<s32>(x, y), 
			32,          // bits per pixel
			fullScreen,  // fullscreen?
			false,       // bool  stencilbuffer = false,  
			false,       // bool  vsync = false,  
			this         // IEventReceiver *  receiver = 0,  
		);

	if (pDevice == 0)
	{
		cout<<"Could not create selected driver!"<<endl;
		return 1;        // could not create selected driver.
	}

	pDriver = pDevice->getVideoDriver();
	pSmgr = pDevice->getSceneManager();
	pGuiEnv = pDevice->getGUIEnvironment();
	irr::ITimer *pTimer = pDevice->getTimer();

	// fixes a problem where Microsoft Visual Studio doesn't change working directory on execution
	if ( !pDevice->getFileSystem()->existFile("data") )
		pDevice->getFileSystem()->changeWorkingDirectoryTo("../../bin");

	// load zipped data file
	if ( !pDevice->getFileSystem()->addZipFileArchive("data") )
	{
		cout<<"Couldn't find the file 'data'.\nProgram stopped.\n"<<endl;
		pDevice->drop();
		std::cin>>i;
		return(-1);
	}

	// set up console
	this->getConfig().dimensionRatios.Y=0.6f;
	this->initializeConsole(pDevice);
	this->loadDefaultCommands();
	this->getConfig().bgColor = video::SColor(220, 0, 0, 80);
	// register additional console commands
	gamestates* cmd = 0;
	cmd = new gamestates();
	cmd->pMuele = this;
	this->registerCommand(cmd);

	dim = pDriver->getScreenSize();
	WideString inf = "Used resolution: ";
	inf+=dim.Width;
	inf+=" x ";
	inf+=dim.Height;
	this->logConsoleA(inf);

	gui::IGUISkin* pSkin = pGuiEnv->getSkin();
	gui::IGUIFont* pFont = pGuiEnv->getFont("console.bmp");
	if (pFont)
		pSkin->setFont(pFont);

	scene::ICameraSceneNode* pCamera = 
		pSmgr->addCameraSceneNodeMaya(0, -1000, 200, 111);
		//smgr->addCameraSceneNodeFPS(0, 100.0f, 300.0f, -1, 0, 0, true);

	pCamera->setPosition(core::vector3df(4, 10, -25));
	pCamera->setTarget(core::vector3df(0, (f32)boardSizeZ, 0));
	
	//pDriver->setAmbientLight(video::SColor(0, 100, 100, 100));

	scene::ILightSceneNode *pLight = 
		pSmgr->addLightSceneNode(pSmgr->getRootSceneNode(), core::vector3df(20, 20, -15),
				video::SColorf(video::SColor(0, 15, 15, 15)),100);

	pLight->getLightData().AmbientColor.set(0, 0, 0, 0);
	pLight->getLightData().DiffuseColor.set(0.1f, 0.1f, 0.1f);
	pLight->getLightData().SpecularColor.set(0.1f, 0.1f , 0.1f);


	scene::IAnimatedMesh *pCylinder = pSmgr->getMesh("cylinderF24.obj");
	scene::IAnimatedMesh *pSphere   = pSmgr->getMesh("sphereFa320.obj");

	scene::ITriangleSelector *pSelectorCyliner;
	scene::ISceneNode *pSelectCylinder = pSmgr->addEmptySceneNode();
	scene::IMeshBuffer *pMbCylinder = NULL;
	if (pCylinder)
	{
		pMbCylinder = pCylinder->getMesh(0)->getMeshBuffer(0);
		pSelectorCyliner = pSmgr->createTriangleSelector( pCylinder->getMesh(0), pSelectCylinder );
		pSelectCylinder->setTriangleSelector(pSelectorCyliner);
		pSelectorCyliner->drop();
	}
	
	scene::IMeshBuffer *pMbSphere = NULL;
	if (pSphere)
	{
		pMbSphere = pSphere->getMesh(0)->getMeshBuffer(0);
		pSmgr->addMeshSceneNode(pSphere->getMesh(0), pLight);
	}

	//pM->getMaterial(0).Wireframe=true;

	// create the main menu
	gui::IGUIContextMenu* menu = pGuiEnv->addMenu();
	menu->addItem(L"Game", -1, true, true);
	menu->addItem(L"AI", -1, true, true);
	menu->addItem(L"Help", -1, true, true);
	
	gui::IGUIContextMenu* submenu;
	submenu = menu->getSubMenu(0);
	submenu->addItem(L"Preferences", ID_MENU_GAME_PREFERENCES);
	submenu->addSeparator();
	submenu->addItem(L"Quit", ID_MENU_GAME_QUIT);

	submenu = menu->getSubMenu(1);
	submenu->addItem(L"Competition", ID_MENU_AI_COMPETITION);
	submenu->addItem(L"Neural Network Setup", ID_MENU_AI_NN_SETUP);

	submenu = menu->getSubMenu(2);
	submenu->addItem(L"About", ID_MENU_HELP);
	
	// create setting windows
	pPreferencesWindow = new CPreferencesWindow(this, menu->getAbsolutePosition().getHeight());
	pPreferencesWindow->toggleVisiblility();
	pAICompetitionWindow = new CAICompetitionWindow(this);
	pAICompetitionWindow->toggleVisiblility();
	pNeuralNetworkWindow = new CNeuralNetworkWindow(this, menu->getAbsolutePosition().getHeight());
	pNeuralNetworkWindow->resetWindow(true);
	pNeuralNetworkWindow->toggleVisiblility();

	pNeuralNetwork->loadNet(NULL);

	// change transparence of user interface
	for (s32 i=0; i<irr::gui::EGDC_COUNT ; ++i)
	{
		video::SColor col = pGuiEnv->getSkin()->getColor((gui::EGUI_DEFAULT_COLOR)i);
		col.setAlpha(240);
		pGuiEnv->getSkin()->setColor((gui::EGUI_DEFAULT_COLOR)i, col);
	}



	/*** main loop ***************************************************************/
		
	int lastFPS = -1;

	unsigned lastTime = pTimer->getRealTime();
	unsigned blinkTime = pTimer->getRealTime();
	bool blink = true;

	while(pDevice->run())
	if ( ( pDevice->isWindowActive() ) || ( !pDevice->isWindowActive() && ( pTimer->getRealTime() - lastTime ) > 250) )
	{
		lastTime = pTimer->getRealTime();
		pDriver->beginScene(true, true, video::SColor(0, 40, 40, 40));

		pSmgr->drawAll();

		// -----------------------------------------------------
		// render cylinders and pick cylinder under mouse cursor
		if ( pMbCylinder )
		{
			pDriver->setMaterial(materialCylinder);
			scene::ISceneCollisionManager *pSCM = pSmgr->getSceneCollisionManager();
			core::line3d<f32> rayFromMouse = pSCM->getRayFromScreenCoordinates( pDevice->getCursorControl()->getPosition() );
			core::vector3df intersection;
			core::triangle3df triangle;
			core::vector3df vectorTranslation;
			core::vector3df vectorScale;
			core::matrix4 matrixTranslation;
			core::matrix4 matrixScale;

			// selectiion is only possible if no training game is running
			if ( !trainingRunning ) selectedCylinder = -1; else selectedCylinder = 0;

			for ( int iy = 0; iy < boardSizeY; iy++ )
				for ( int ix = 0; ix < boardSizeX; ix++ )
				{
					vectorTranslation.X = -halfBoardWidthX + boardFieldDistance * ix;
					vectorTranslation.Y =  (f32)boardSizeZ - 1;
					vectorTranslation.Z = -halfBoardWidthY + boardFieldDistance * iy;
					
					vectorScale.X = 2.0f;
					vectorScale.Y = (f32)boardSizeZ;
					vectorScale.Z = 2.0f;

					// picking
					pSelectCylinder->setPosition(vectorTranslation);
					pSelectCylinder->setScale(vectorScale);
					pSelectCylinder->updateAbsolutePosition();
					if ( selectedCylinder < 0 &&
						 pSCM->getCollisionPoint(rayFromMouse, pSelectorCyliner, intersection , triangle) )
					{
						// mouse is over actual cylinder
						selectedCylinder = iy * boardSizeX + ix;
						pDriver->setMaterial(materialSphere[1]);
					} 
					else
						pDriver->setMaterial(materialCylinder);

					// drawing
					matrixScale.setScale(vectorScale); // strech height to board hight
					matrixTranslation.setTranslation(vectorTranslation);
					pDriver->setTransform( video::ETS_WORLD, matrixTranslation * matrixScale );
					pDriver->drawMeshBuffer(pMbCylinder);
				}
		}

		// ------------------
		// render all spheres
		if ( pMbSphere )
		{
			if ( pTimer->getRealTime() - blinkTime > 333 )
			{
				blinkTime = pTimer->getRealTime();
				blink=!blink;
			}
			core::matrix4 translation;

			for ( int iz = 0; iz < boardSizeZ; iz++ )	
				for ( int iy = 0; iy < boardSizeY; iy++ )
					for ( int ix = 0; ix < boardSizeX; ix++ )
						if ( FIELD_VALUE(pBoard, ix, iy, iz) != EMTY_FIELD )
						{
							pDriver->setMaterial(materialSphere[FIELD_VALUE(pBoard, ix, iy, iz)]);

							// set a different material for a winning combination
							if ( gameFinished )
								if ( ( blink || trainingRunning ) && FIELD_VALUE(pWinnerBoard, ix, iy, iz) != EMTY_FIELD )
									pDriver->setMaterial(materialSphereWon[FIELD_VALUE(pWinnerBoard, ix, iy, iz)]);

							translation.setTranslation( core::vector3df(
									-halfBoardWidthX + boardFieldDistance * ix,
									(f32)iz * 2,
									-halfBoardWidthY + boardFieldDistance * iy
								));
							pDriver->setTransform(video::ETS_WORLD, translation);
							pDriver->drawMeshBuffer(pMbSphere);
						}
		}

		// -----------------------------------
		// let the computer player make a move
		if ( !gameFinished )
		{
			switch( actualPlayerType )
			{
			case PLAYER_TYPE_AILT:
				switchActualPlayer(pLookUpTable->getBestMove( pBoard, actualPlayer));
				break;
			case PLAYER_TYPE_AINN:
				switchActualPlayer(pNeuralNetwork->getBestMove( pBoard, actualPlayer));
				break;
			}
		}

		// --------------------------------------------------------------------------
		// show propabilities of all possibel previous AI moves
		if ( lastPlayerType != PLAYER_TYPE_HUMAN && pMbCylinder && pPreferencesWindow->pCheckProbabilities->isChecked() && !trainingRunning )
		{
			core::vector3df vectorTranslation;
			core::matrix4 matrixTranslation;
			core::stringw val;			

			// remove last move from board
			if ( moveNumber ) *(pBoard + *(pMoveHistory + moveNumber - 1)) = 0;

			for ( int iy = 0; iy < boardSizeY; iy++ )
				for ( int ix = 0; ix < boardSizeX; ix++ )
				{
					vectorTranslation.X = -halfBoardWidthX + boardFieldDistance * ix;
					vectorTranslation.Y =  (f32)boardSizeZ - 1;
					vectorTranslation.Z = -halfBoardWidthY + boardFieldDistance * iy;
						
					core::position2d<s32> pos = this->getScreenCoordinatesFrom3DPosition(vectorTranslation); 

					if (*(pBoard + iy * boardSizeX + ix) == EMTY_FIELD )
					{
						*(pBoard + iy * boardSizeX + ix) = lastPlayer;

						if ( lastPlayerType == PLAYER_TYPE_AILT )
							val = pLookUpTable->getStateValue(pSymmetry->getSymmetry(pBoard));
						if ( lastPlayerType == PLAYER_TYPE_AINN )
							val =pNeuralNetwork->propagate(pSymmetry->getSymmetry(pBoard), lastPlayer);
						
						if ( pFont ) 
							pFont->draw(val.c_str(), core::rect<s32>(pos, pos), video::SColor(255, 200, 255, 200),true );	
						*(pBoard + iy * boardSizeX + ix) =0;
					}
				}
			// restore last move again
			if ( moveNumber ) *(pBoard + *(pMoveHistory + moveNumber - 1)) = lastPlayer;
		}

		pGuiEnv->drawAll();

		this->renderConsole();

		/*
		That's it, we just have to finish drawing.
		*/

		pDriver->endScene();

		int fps = pDriver->getFPS();

		if (lastFPS != fps)
		{
			
		  core::stringw str = L"Connection - [";
		  str += pDriver->getName();
		  str += "] FPS:";
		  str += fps;
		  str += "  primitive count: ";
		  str += (s32)pDriver->getPrimitiveCountDrawn();
		  str += "  pr./s: ";
		  str += fps*(s32)pDriver->getPrimitiveCountDrawn();

		  pDevice->setWindowCaption(str.c_str());
		  lastFPS = fps;
		}

		if ( trainingRunning )
		{
			//pNeuralNetwork->learn();
			pNeuralNetwork->learnTest();
			//pNeuralNetwork->learnTest2();

			pNeuralNetworkWindow->resetWindow(false);
			if ( gameFinished )
			{
				memset( pWinnerBoard, 0, boardFieldCount );
				CTestWin checkWinner(pBoard, pWinnerBoard, boardSizeX, boardSizeY, boardSizeZ, actualPlayer, winFieldCount, 0);
				checkWinner.testWin();
			}
		}
	}

	pDevice->drop();

	return 0;
}

//! Calculates 2d screen position from a 3d position.
core::position2d<s32> CMuele::getScreenCoordinatesFrom3DPosition
									(core::vector3df pos3d)
{
	core::position2d<s32> pos2d(-1000,-1000);

	if (!this->pSmgr || !pDriver)
		return pos2d;	

	scene::ICameraSceneNode *pCamera = pSmgr->getActiveCamera();

	if (!pCamera)
		return pos2d;

	core::rect<s32> viewPort = pDriver->getViewPort();
	core::dimension2d<s32> dim = pDriver->getScreenSize();     //(viewPort.getWidth(), viewPort.getHeight());												

	dim.Width /= 2;
	dim.Height /= 2;

	f32 transformedPos[4];

	core::matrix4 trans = pCamera->getProjectionMatrix();
	trans *= pCamera->getViewMatrix();

	transformedPos[0] = pos3d.X;
	transformedPos[1] = pos3d.Y;
	transformedPos[2] = pos3d.Z;
	transformedPos[3] = 1.0f;

	trans.multiplyWith1x4Matrix(transformedPos);

	if (transformedPos[3] < 0)
		return core::position2d<s32>(-10000,-10000);

	f32 zDiv = transformedPos[3] == 0.0f ? 1.0f :
		(1.0f / transformedPos[3]);

	pos2d.X = (s32)(dim.Width * transformedPos[0] * zDiv) + dim.Width ;
	pos2d.Y = ((s32)(dim.Height  - (dim.Height * (transformedPos[1] * zDiv))));

	return pos2d;
}

/* end of Muele class **********************************************************
********************************************************************************
********************************************************************************/

// use Windows main method so the windows console does'nt showes up at start
#ifdef WIN32  
/*
int main(int argc[] , char *argv[]);

	int __stdcall WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
	{
		return main();
	}
	*/
#endif

int main(int argc[], char *argv[])
{
	CMuele muele;
	return muele.run(argv);
}

