diff --git a/millgame.sln b/millgame.sln index 71202bc2..0c632e7b 100644 --- a/millgame.sln +++ b/millgame.sln @@ -5,6 +5,8 @@ VisualStudioVersion = 16.0.29009.5 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "millgame", "millgame.vcxproj", "{D6EBE2B6-17F9-30EA-AE68-9CD0BB526200}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "perfect", "src\perfect\perfect.vcxproj", "{EDB1E279-1476-443B-84FA-150A5D3B5A10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -21,12 +23,20 @@ Global {D6EBE2B6-17F9-30EA-AE68-9CD0BB526200}.Release|x64.Build.0 = Release|x64 {D6EBE2B6-17F9-30EA-AE68-9CD0BB526200}.Release|x86.ActiveCfg = Release|Win32 {D6EBE2B6-17F9-30EA-AE68-9CD0BB526200}.Release|x86.Build.0 = Release|Win32 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Debug|x64.ActiveCfg = Debug|x64 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Debug|x64.Build.0 = Debug|x64 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Debug|x86.ActiveCfg = Debug|Win32 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Debug|x86.Build.0 = Debug|Win32 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Release|x64.ActiveCfg = Release|x64 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Release|x64.Build.0 = Release|x64 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Release|x86.ActiveCfg = Release|Win32 + {EDB1E279-1476-443B-84FA-150A5D3B5A10}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {401C61DF-0B94-45A9-96C1-9BD069796A84} Qt5Version = Qt5.13.0 + SolutionGuid = {401C61DF-0B94-45A9-96C1-9BD069796A84} EndGlobalSection EndGlobal diff --git a/perfect/perfect.vcxproj b/perfect/perfect.vcxproj new file mode 100644 index 00000000..1db232b3 --- /dev/null +++ b/perfect/perfect.vcxproj @@ -0,0 +1,150 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + 16.0 + Win32Proj + {462a21df-6c88-4e69-ba15-ab16636cdd1b} + perfect + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + diff --git a/perfect/perfect.vcxproj.filters b/perfect/perfect.vcxproj.filters new file mode 100644 index 00000000..a8a65633 --- /dev/null +++ b/perfect/perfect.vcxproj.filters @@ -0,0 +1,17 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + \ No newline at end of file diff --git a/src/perfect/bufferedFile.cpp b/src/perfect/bufferedFile.cpp new file mode 100644 index 00000000..dae546d8 --- /dev/null +++ b/src/perfect/bufferedFile.cpp @@ -0,0 +1,217 @@ +/********************************************************************* + bufferedFile.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "bufferedFile.h" + +//----------------------------------------------------------------------------- +// Name: bufferedFile() +// Desc: Creates a cyclic array. The passed file is used as temporary data buffer for the cyclic array. +//----------------------------------------------------------------------------- +bufferedFileClass::bufferedFileClass(unsigned int numberOfThreads, unsigned int bufferSizeInBytes, const char *fileName) +{ + // locals + unsigned int curThread; + + // Init blocks + bufferSize = bufferSizeInBytes; + numThreads = numberOfThreads; + readBuffer = new unsigned char [numThreads*bufferSize]; + writeBuffer = new unsigned char [numThreads*bufferSize]; + curWritingPointer = new long long [numThreads]; + curReadingPointer = new long long [numThreads]; + bytesInReadBuffer = new unsigned int [numThreads]; + bytesInWriteBuffer = new unsigned int [numThreads]; + + for (curThread=0; curThread 0) { + if (WriteFile(hFile, pData, sizeInBytes, &dwBytesWritten, NULL) == TRUE) { + restingBytes -= dwBytesWritten; + pData = (void*) (((unsigned char*) pData) + dwBytesWritten); + if (restingBytes > 0) cout << endl << "Still " << restingBytes << " to write!"; + } else { + cout << endl << "WriteFile Failed!"; + } + } + LeaveCriticalSection(&csIO); +} + +//----------------------------------------------------------------------------- +// Name: readDataFromFile() +// Desc: Reads 'sizeInBytes'-bytes from the position 'offset' of the file. +//----------------------------------------------------------------------------- +void bufferedFileClass::readDataFromFile(HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData) +{ + DWORD dwBytesRead; + LARGE_INTEGER liDistanceToMove; + unsigned int restingBytes = sizeInBytes; + + liDistanceToMove.QuadPart = offset; + + EnterCriticalSection(&csIO); + while (!SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN)) cout << endl << "SetFilePointerEx failed!"; + while (restingBytes > 0) { + if (ReadFile(hFile, pData, sizeInBytes, &dwBytesRead, NULL) == TRUE) { + restingBytes -= dwBytesRead; + pData = (void*) (((unsigned char*) pData) + dwBytesRead); + if (restingBytes > 0) cout << endl << "Still " << restingBytes << " to read!"; + } else { + cout << endl << "ReadFile Failed!"; + } + } + LeaveCriticalSection(&csIO); +} + +//----------------------------------------------------------------------------- +// Name: writeBytes() +// Desc: +//----------------------------------------------------------------------------- +bool bufferedFileClass::writeBytes(unsigned int numBytes, unsigned char* pData) +{ + return writeBytes(0, curWritingPointer[0], numBytes, pData); +} + +//----------------------------------------------------------------------------- +// Name: writeBytes() +// Desc: +//----------------------------------------------------------------------------- +bool bufferedFileClass::writeBytes(unsigned int threadNo, long long positionInFile, unsigned int numBytes, unsigned char* pData) +{ + // parameters ok? + if (threadNo >= numThreads) return false; + if (pData == NULL) return false; + + // locals + + // if buffer full or not sequential write operation write buffer to file + if (bytesInWriteBuffer[threadNo] && (positionInFile != curWritingPointer[threadNo] || bytesInWriteBuffer[threadNo] + numBytes >= bufferSize)) { + + writeDataToFile(hFile, curWritingPointer[threadNo] - bytesInWriteBuffer[threadNo], bytesInWriteBuffer[threadNo], &writeBuffer[threadNo*bufferSize+0]); + bytesInWriteBuffer[threadNo] = 0; + } + + // copy data into buffer + memcpy(&writeBuffer[threadNo*bufferSize+bytesInWriteBuffer[threadNo]], pData, numBytes); + bytesInWriteBuffer[threadNo] += numBytes; + curWritingPointer[threadNo] = positionInFile + numBytes; + + // everything ok + return true; +} + +//----------------------------------------------------------------------------- +// Name: takeBytes() +// Desc: +//----------------------------------------------------------------------------- +bool bufferedFileClass::readBytes(unsigned int numBytes, unsigned char* pData) +{ + return readBytes(0, curReadingPointer[0], numBytes, pData); +} + +//----------------------------------------------------------------------------- +// Name: takeBytes() +// Desc: +//----------------------------------------------------------------------------- +bool bufferedFileClass::readBytes(unsigned int threadNo, long long positionInFile, unsigned int numBytes, unsigned char* pData) +{ + // parameters ok? + if (threadNo >= numThreads) return false; + if (pData == NULL) return false; + + // read from file into buffer if not enough data in buffer or if it is not an sequential reading operation? + if (positionInFile != curReadingPointer[threadNo] || bytesInReadBuffer[threadNo] < numBytes) { + bytesInReadBuffer[threadNo] = ((positionInFile + bufferSize <= fileSize) ? bufferSize : (unsigned int) (fileSize - positionInFile)); + if (bytesInReadBuffer[threadNo] < numBytes) return false; + readDataFromFile(hFile, positionInFile, bytesInReadBuffer[threadNo], &readBuffer[threadNo*bufferSize+bufferSize-bytesInReadBuffer[threadNo]]); + } + memcpy(pData, &readBuffer[threadNo*bufferSize+bufferSize-bytesInReadBuffer[threadNo]], numBytes); + bytesInReadBuffer[threadNo] -= numBytes; + curReadingPointer[threadNo] = positionInFile + numBytes; + + // everything ok + return true; +} \ No newline at end of file diff --git a/src/perfect/bufferedFile.h b/src/perfect/bufferedFile.h new file mode 100644 index 00000000..bdd8e787 --- /dev/null +++ b/src/perfect/bufferedFile.h @@ -0,0 +1,52 @@ +/*********************************************************************\ + bufferedFile.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef BUFFERED_FILE_H +#define BUFFERED_FILE_H + +#include +#include +#include + +using namespace std; + +/*** Klassen *********************************************************/ + +class bufferedFileClass +{ +private: + // Variables + HANDLE hFile; // Handle of the file + unsigned int numThreads; // number of threads + unsigned char * readBuffer; // Array of size [numThreads*blockSize] containing the data of the block, where reading is taking place + unsigned char * writeBuffer; // '' - access by [threadNo*bufferSize+position] + long long * curReadingPointer; // array of size [numThreads] with pointers to the byte which is currently read + long long * curWritingPointer; // '' + unsigned int * bytesInReadBuffer; // + unsigned int * bytesInWriteBuffer; // + unsigned int bufferSize; // size in bytes of a buffer + long long fileSize; // size in bytes + CRITICAL_SECTION csIO; + + // Functions + void writeDataToFile (HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData); + void readDataFromFile (HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData); + +public: + // Constructor / destructor + bufferedFileClass (unsigned int numThreads, unsigned int bufferSizeInBytes, const char *fileName); + ~bufferedFileClass (); + + // Functions + bool flushBuffers (); + bool writeBytes (unsigned int numBytes, unsigned char* pData); + bool readBytes (unsigned int numBytes, unsigned char* pData); + bool writeBytes (unsigned int threadNo, long long positionInFile, unsigned int numBytes, unsigned char* pData); + bool readBytes (unsigned int threadNo, long long positionInFile, unsigned int numBytes, unsigned char* pData); + long long getFileSize (); +}; + +#endif diff --git a/src/perfect/console.cpp b/src/perfect/console.cpp new file mode 100644 index 00000000..009b61f7 --- /dev/null +++ b/src/perfect/console.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include "muehle.h" +#include "minMaxKI.h" +#include "randomKI.h" +#include "perfectKI.h" + +using namespace std; + +unsigned int startTestFromLayer = 0; +unsigned int endTestAtLayer = NUM_LAYERS-1; +#ifdef _DEBUG + char databaseDirectory[] = "."; +#elif _RELEASE_X64 + char databaseDirectory[] = ""; +#endif +bool calculateDatabase = false; + +void main(void) +{ + // locals + bool playerOneHuman = false; + bool playerTwoHuman = false; + char tmpChar[100]; + unsigned int pushFrom, pushTo; + muehle* myGame = new muehle(); + perfectKI* myKI = new perfectKI(databaseDirectory); + + SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); + srand(GetTickCount()); + + // intro + cout << "*************************" << endl; + cout << "* Muehle *" << endl; + cout << "*************************" << endl << endl; + + myKI->setDatabasePath(databaseDirectory); + + // begin + myGame->beginNewGame(myKI, myKI, (rand() % 2) ? fieldStruct::playerOne : fieldStruct::playerTwo); + + if (calculateDatabase) { + + // calculate + myKI->calculateDatabase(MAX_DEPTH_OF_TREE, false); + + // test database + cout << endl << "Begin test starting from layer: "; startTestFromLayer; + cout << endl << "End test at layer: "; endTestAtLayer; + myKI->testLayers(startTestFromLayer, endTestAtLayer); + + } else { + + cout << "Is Player 1 human? (y/n):"; cin >> tmpChar; if (tmpChar[0] == 'y') playerOneHuman = true; + cout << "Is Player 2 human? (y/n):"; cin >> tmpChar; if (tmpChar[0] == 'y') playerTwoHuman = true; + + // play + do + { + // print field + cout << "\n\n\n\n\n\n\n\n\n\n\n"; + myGame->getComputersChoice(&pushFrom, &pushTo); + cout << "\n\n"; + cout << "\nlast move was from " << (char)(myGame->getLastMoveFrom() + 97) << " to " << (char)(myGame->getLastMoveTo() + 97) << "\n\n"; + + myGame->printField(); + + // Human + if ((myGame->getCurrentPlayer() == fieldStruct::playerOne && playerOneHuman) + || (myGame->getCurrentPlayer() == fieldStruct::playerTwo && playerTwoHuman)) { + do { + // Show text + if (myGame->mustStoneBeRemoved()) cout << "\n Which stone do you want to remove? [a-x]: \n\n\n"; + else if (myGame->inSettingPhase()) cout << "\n Where are you going? [a-x]: \n\n\n"; + else cout << "\n Your train? [a-x][a-x]: \n\n\n"; + + // get input + cin >> tmpChar; + if ((tmpChar[0] >= 'a') && (tmpChar[0] <= 'x')) pushFrom = tmpChar[0] - 'a'; else pushFrom = fieldStruct::size; + + if (myGame->inSettingPhase()) { + if ((tmpChar[0] >= 'a') && (tmpChar[0] <= 'x')) pushTo = tmpChar[0] - 'a'; else pushTo = fieldStruct::size; + } else { + if ((tmpChar[1] >= 'a') && (tmpChar[1] <= 'x')) pushTo = tmpChar[1] - 'a'; else pushTo = fieldStruct::size; + } + + // undo + if (tmpChar[0] == 'u' && tmpChar[1] == 'n' && tmpChar[2] == 'd' && tmpChar[3] == 'o') { + + // undo moves until a human player shall move + do { + myGame->undoLastMove(); + } while (!((myGame->getCurrentPlayer() == fieldStruct::playerOne && playerOneHuman) + || (myGame->getCurrentPlayer() == fieldStruct::playerTwo && playerTwoHuman))); + + // reprint field + break; + } + + } while (myGame->moveStone(pushFrom, pushTo) == false); + + // Computer + } else { + cout << "\n"; + myGame->moveStone(pushFrom, pushTo); + } + + } while (myGame->getWinner() == 0); + + // end + cout << "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; + + myGame->printField(); + + if (myGame->getWinner() == fieldStruct::playerOne) cout << "\n Player 1 (o) won after " << myGame->getMovesDone() << " move.\n\n"; + else if (myGame->getWinner() == fieldStruct::playerTwo) cout << "\n Player 2 (x) won after " << myGame->getMovesDone() << " move.\n\n"; + else if (myGame->getWinner() == fieldStruct::gameDrawn) cout << "\n Draw!\n\n"; + else cout << "\n A program error has occurred!\n\n"; + } + + char end; + cin >> end; +} diff --git a/src/perfect/cyclicArray.cpp b/src/perfect/cyclicArray.cpp new file mode 100644 index 00000000..53d0cce4 --- /dev/null +++ b/src/perfect/cyclicArray.cpp @@ -0,0 +1,342 @@ +/********************************************************************* + cyclicArray.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "cyclicArray.h" + +//----------------------------------------------------------------------------- +// Name: cyclicArray() +// Desc: Creates a cyclic array. The passed file is used as temporary data buffer for the cyclic array. +//----------------------------------------------------------------------------- +cyclicArray::cyclicArray(unsigned int blockSizeInBytes, unsigned int numberOfBlocks, const char *fileName) +{ + // Init blocks + blockSize = blockSizeInBytes; + numBlocks = numberOfBlocks; + readingBlock = new unsigned char[blockSize]; + writingBlock = new unsigned char[blockSize]; + curReadingPointer = writingBlock; + curWritingPointer = writingBlock; + readWriteInSameRound= true; + curReadingBlock = 0; + curWritingBlock = 0; + + // Open Database-File (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_RANDOM_ACCESS) + hFile = CreateFileA(fileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // opened file succesfully + if (hFile == INVALID_HANDLE_VALUE) { + hFile = NULL; + return; + } +} + +//----------------------------------------------------------------------------- +// Name: ~randomKI() +// Desc: randomKI class destructor +//----------------------------------------------------------------------------- +cyclicArray::~cyclicArray() +{ + // delete arrays + delete [] readingBlock; + delete [] writingBlock; + + // close file + if (hFile != NULL) CloseHandle(hFile); +} + +//----------------------------------------------------------------------------- +// Name: writeDataToFile() +// Desc: Writes 'sizeInBytes'-bytes to the position 'offset' to the file. +//----------------------------------------------------------------------------- +void cyclicArray::writeDataToFile(HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData) +{ + DWORD dwBytesWritten; + LARGE_INTEGER liDistanceToMove; + unsigned int restingBytes = sizeInBytes; + + liDistanceToMove.QuadPart = offset; + + while (!SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN)) cout << endl << "SetFilePointerEx failed!"; + + while (restingBytes > 0) { + if (WriteFile(hFile, pData, sizeInBytes, &dwBytesWritten, NULL) == TRUE) { + restingBytes -= dwBytesWritten; + pData = (void*) (((unsigned char*) pData) + dwBytesWritten); + if (restingBytes > 0) cout << endl << "Still " << restingBytes << " to write!"; + } else { + cout << endl << "WriteFile Failed!"; + } + } +} + +//----------------------------------------------------------------------------- +// Name: readDataFromFile() +// Desc: Reads 'sizeInBytes'-bytes from the position 'offset' of the file. +//----------------------------------------------------------------------------- +void cyclicArray::readDataFromFile(HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData) +{ + DWORD dwBytesRead; + LARGE_INTEGER liDistanceToMove; + unsigned int restingBytes = sizeInBytes; + + liDistanceToMove.QuadPart = offset; + + while (!SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN)) cout << endl << "SetFilePointerEx failed!"; + + while (restingBytes > 0) { + if (ReadFile(hFile, pData, sizeInBytes, &dwBytesRead, NULL) == TRUE) { + restingBytes -= dwBytesRead; + pData = (void*) (((unsigned char*) pData) + dwBytesRead); + if (restingBytes > 0) cout << endl << "Still " << restingBytes << " to read!"; + } else { + cout << endl << "ReadFile Failed!"; + } + } +} + +//----------------------------------------------------------------------------- +// Name: addBytes() +// Desc: Add the passed data to the cyclic array. If the writing pointer reaches the end of a block, +// the data of the whole block is written to the file and the next block is considered for writing. +//----------------------------------------------------------------------------- +bool cyclicArray::addBytes(unsigned int numBytes, unsigned char* pData) +{ + // locals + unsigned int bytesWritten = 0; + + // write each byte + while (bytesWritten < numBytes) { + + // store byte in current reading block + *curWritingPointer = *pData; + curWritingPointer++; + bytesWritten++; + pData++; + + // when block is full then save current one to file and begin new one + if (curWritingPointer == writingBlock + blockSize) { + + // copy data into reading block? + if (curReadingBlock == curWritingBlock) { + memcpy(readingBlock, writingBlock, blockSize); + curReadingPointer = readingBlock + (curReadingPointer - writingBlock); + } + + // will reading block be overwritten? + if (curReadingBlock == curWritingBlock && !readWriteInSameRound) return false; + + // store bock in file + writeDataToFile(hFile, ((long long) blockSize) * ((long long) curWritingBlock), blockSize, writingBlock); + + // set pointer to beginnig of writing block + curWritingPointer = writingBlock; + curWritingBlock = (curWritingBlock + 1) % numBlocks; + if (curWritingBlock == 0) readWriteInSameRound = false; + } + } + + // everything ok + return true; +} + +//----------------------------------------------------------------------------- +// Name: bytesAvailable() +// Desc: +//----------------------------------------------------------------------------- +bool cyclicArray::bytesAvailable() +{ + if (curReadingBlock == curWritingBlock && curReadingPointer == curWritingPointer && readWriteInSameRound) return false; + else return true; +} + +//----------------------------------------------------------------------------- +// Name: takeBytes() +// Desc: Load data from the cyclic array. If the reading pointer reaches the end of a block, +// the data of the next whole block is read from the file. +//----------------------------------------------------------------------------- +bool cyclicArray::takeBytes(unsigned int numBytes, unsigned char* pData) +{ + // locals + unsigned int bytesRead = 0; + + // read each byte + while (bytesRead < numBytes) { + + // was current reading byte already written ? + if (curReadingBlock == curWritingBlock && curReadingPointer == curWritingPointer && readWriteInSameRound) return false; + + // read current byte + *pData = *curReadingPointer; + curReadingPointer++; + bytesRead++; + pData++; + + // load next block? + if (curReadingPointer == readingBlock + blockSize) { + + // go to next block + curReadingBlock = (curReadingBlock + 1) % numBlocks; + if (curReadingBlock == 0) readWriteInSameRound = true; + + // writing block reached ? + if (curReadingBlock == curWritingBlock) { + curReadingPointer = writingBlock; + + } else { + + // set pointer to beginnig of reading block + curReadingPointer = readingBlock; + + // read whole block from file + readDataFromFile(hFile, ((long long) blockSize) * ((long long) curReadingBlock), blockSize, readingBlock); + } + } + } + + // everything ok + return true; +} + +//----------------------------------------------------------------------------- +// Name: loadFile() +// Desc: Load the passed file into the cyclic array. +// The passed filename must be different than the passed filename to the constructor cyclicarray(). +//----------------------------------------------------------------------------- +bool cyclicArray::loadFile(const char *fileName, LONGLONG &numBytesLoaded) +{ + // locals + HANDLE hLoadFile; + unsigned char* dataInFile; + LARGE_INTEGER largeInt; + LONGLONG maxFileSize = ((LONGLONG) blockSize) * ((LONGLONG) numBlocks); + LONGLONG curOffset = 0; + unsigned int numBlocksInFile; + unsigned int curBlock; + unsigned int numBytesInLastBlock; + numBytesLoaded = 0; + + // cyclic array file must be open + if (hFile == NULL) return false; + + // Open Database-File (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_RANDOM_ACCESS) + hLoadFile = CreateFileA(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + // opened file succesfully + if (hLoadFile == INVALID_HANDLE_VALUE) { + return false; + } + + // does data of file fit into cyclic array ? + GetFileSizeEx(hLoadFile, &largeInt); + + if (maxFileSize < largeInt.QuadPart) { + CloseHandle(hLoadFile); + return false; + } + + // reset + curReadingPointer = writingBlock; + curWritingPointer = writingBlock; + readWriteInSameRound= true; + curReadingBlock = 0; + curWritingBlock = 0; + + numBlocksInFile = (unsigned int) (largeInt.QuadPart / ((LONGLONG)blockSize)) + 1; + numBytesInLastBlock = (unsigned int) (largeInt.QuadPart % ((LONGLONG)blockSize)); + dataInFile = new unsigned char[blockSize]; + + // + for (curBlock=0; curBlock +#include +#include + +using namespace std; + +/*** Klassen *********************************************************/ + +class cyclicArray +{ +private: + // Variables + HANDLE hFile; // Handle of the file + unsigned char* readingBlock; // Array of size [blockSize] containing the data of the block, where reading is taking place + unsigned char* writingBlock; // '' + unsigned char* curReadingPointer; // pointer to the byte which is currently read + unsigned char* curWritingPointer; // '' + unsigned int blockSize; // size in bytes of a block + unsigned int curReadingBlock; // index of the block, where reading is taking place + unsigned int curWritingBlock; // index of the block, where writing is taking place + unsigned int numBlocks; // amount of blocks + bool readWriteInSameRound; // true if curReadingBlock > curWritingBlock, false otherwise + + // Functions + void writeDataToFile (HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData); + void readDataFromFile (HANDLE hFile, long long offset, unsigned int sizeInBytes, void *pData); + +public: + // Constructor / destructor + cyclicArray (unsigned int blockSizeInBytes, unsigned int numberOfBlocks, const char *fileName); + ~cyclicArray (); + + // Functions + bool addBytes (unsigned int numBytes, unsigned char* pData); + bool takeBytes (unsigned int numBytes, unsigned char* pData); + bool loadFile (const char *fileName, LONGLONG &numBytesLoaded); + bool saveFile (const char *fileName); + bool bytesAvailable (); +}; + +#endif diff --git a/src/perfect/minMaxKI.cpp b/src/perfect/minMaxKI.cpp new file mode 100644 index 00000000..2aa05e25 --- /dev/null +++ b/src/perfect/minMaxKI.cpp @@ -0,0 +1,550 @@ +/********************************************************************* + minMaxKI.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "minMaxKI.h" + +//----------------------------------------------------------------------------- +// Name: minMaxKI() +// Desc: minMaxKI class constructor +//----------------------------------------------------------------------------- +minMaxKI::minMaxKI() +{ + depthOfFullTree = 0; +} + +//----------------------------------------------------------------------------- +// Name: ~minMaxKI() +// Desc: minMaxKI class destructor +//----------------------------------------------------------------------------- +minMaxKI::~minMaxKI() +{ +} + +//----------------------------------------------------------------------------- +// Name: play() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::play(fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo) +{ + // globals + field = theField; + ownId = field->curPlayer->id; + curSearchDepth = 0; + unsigned int bestChoice; + unsigned int searchDepth; + + // automatic depth + if (depthOfFullTree == 0) { + if (theField->settingPhase) searchDepth = 5; + else if (theField->curPlayer->numStones <= 4) searchDepth = 7; + else if (theField->oppPlayer->numStones <= 4) searchDepth = 7; + else searchDepth = 7; + } else { + searchDepth = depthOfFullTree; + } + + // Inform user about progress + cout << "minMaxKI is thinking with a depth of " << searchDepth << " steps!\n\n\n"; + + // reserve memory + possibilities = new possibilityStruct [ searchDepth + 1]; + oldStates = new backupStruct [ searchDepth + 1]; + idPossibilities = new unsigned int [(searchDepth + 1) * MAX_NUM_POS_MOVES]; + + // start the miniMax-algorithmn + possibilityStruct *rootPossibilities = (possibilityStruct*) getBestChoice(searchDepth, &bestChoice, MAX_NUM_POS_MOVES); + + // decode the best choice + if (field->stoneMustBeRemoved) { *pushFrom = bestChoice; *pushTo = 0; } + else if (field->settingPhase) { *pushFrom = 0; *pushTo = bestChoice; } + else { *pushFrom = rootPossibilities->from[bestChoice]; + *pushTo = rootPossibilities->to [bestChoice]; } + + // release memory + delete [] oldStates; + delete [] idPossibilities; + delete [] possibilities; + + // release memory + field = NULL; +} + +//----------------------------------------------------------------------------- +// Name: setSearchDepth() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::setSearchDepth(unsigned int depth) +{ + depthOfFullTree = depth; +} + +//----------------------------------------------------------------------------- +// Name: prepareBestChoiceCalculation() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::prepareBestChoiceCalculation() +{ + // calculate current value + currentValue = 0; + gameHasFinished = false; +} + +//----------------------------------------------------------------------------- +// Name: getPossSettingPhase() +// Desc: +//----------------------------------------------------------------------------- +unsigned int *minMaxKI::getPossSettingPhase(unsigned int *numPossibilities, void **pPossibilities) +{ + // locals + unsigned int i; + unsigned int *idPossibility = &idPossibilities[curSearchDepth * MAX_NUM_POS_MOVES]; + + // possibilities with cut off + for ((*numPossibilities) = 0, i=0; isize; i++) { + + // move possible ? + if (field->field[i] == field->squareIsFree) { + + idPossibility[*numPossibilities] = i; + (*numPossibilities)++; + } + } + + // possibility code is simple + *pPossibilities = NULL; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossNormalMove() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * minMaxKI::getPossNormalMove(unsigned int *numPossibilities, void **pPossibilities) +{ + // locals + unsigned int from, to, dir; + unsigned int *idPossibility = &idPossibilities[curSearchDepth * MAX_NUM_POS_MOVES]; + possibilityStruct *possibility = &possibilities [curSearchDepth]; + + // if he is not allowed to spring + if (field->curPlayer->numStones > 3) { + + for ((*numPossibilities) = 0, from=0; from < field->size; from++) { for (dir=0; dir<4; dir++) { + + // destination + to = field->connectedSquare[from][dir]; + + // move possible ? + if (to < field->size && field->field[from] == field->curPlayer->id && field->field[to] == field->squareIsFree) { + + // stone is moveable + idPossibility[*numPossibilities] = *numPossibilities; + possibility->from[*numPossibilities] = from; + possibility->to[*numPossibilities] = to; + (*numPossibilities)++; + + // current player is allowed to spring + }}}} else { + + for ((*numPossibilities) = 0, from=0; from < field->size; from++) { for (to=0; to < field->size; to++) { + + // move possible ? + if (field->field[from] == field->curPlayer->id && field->field[to] == field->squareIsFree && *numPossibilities < MAX_NUM_POS_MOVES) { + + // stone is moveable + idPossibility[*numPossibilities] = *numPossibilities; + possibility->from[*numPossibilities] = from; + possibility->to[*numPossibilities] = to; + (*numPossibilities)++; + }}}} + + // pass possibilities + *pPossibilities = (void*)possibility; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossStoneRemove() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * minMaxKI::getPossStoneRemove(unsigned int *numPossibilities, void **pPossibilities) +{ + // locals + unsigned int i; + unsigned int *idPossibility = &idPossibilities[curSearchDepth * MAX_NUM_POS_MOVES]; + + // possibilities with cut off + for ((*numPossibilities) = 0, i=0; isize; i++) { + + // move possible ? + if (field->field[i] == field->oppPlayer->id && !field->stonePartOfMill[i]) { + + idPossibility[*numPossibilities] = i; + (*numPossibilities)++; + } + } + + // possibility code is simple + *pPossibilities = NULL; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossibilities() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * minMaxKI::getPossibilities(unsigned int threadNo, unsigned int *numPossibilities, bool *opponentsMove, void **pPossibilities) +{ + // set opponentsMove + *opponentsMove = (field->curPlayer->id == ownId) ? false : true; + + // When game has ended of course nothing happens any more + if (gameHasFinished) { + *numPossibilities = 0; + return 0; + // look what is to do + } else { + if (field->stoneMustBeRemoved) return getPossStoneRemove (numPossibilities, pPossibilities); + else if (field->settingPhase) return getPossSettingPhase (numPossibilities, pPossibilities); + else return getPossNormalMove (numPossibilities, pPossibilities); + } +} + +//----------------------------------------------------------------------------- +// Name: getValueOfSituation() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::getValueOfSituation(unsigned int threadNo, float &floatValue, twoBit &shortValue) +{ + floatValue = currentValue; + shortValue = 0; +} + +//----------------------------------------------------------------------------- +// Name: deletePossibilities() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::deletePossibilities(unsigned int threadNo, void *pPossibilities) +{ +} + +//----------------------------------------------------------------------------- +// Name: undo() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::undo(unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void *pBackup, void *pPossibilities) +{ + // locals + backupStruct *oldState = (backupStruct*)pBackup; + + // reset old value + currentValue = oldState->value; + gameHasFinished = oldState->gameHasFinished; + curSearchDepth--; + + field->curPlayer = oldState->curPlayer; + field->oppPlayer = oldState->oppPlayer; + field->curPlayer->numStones = oldState->curNumStones; + field->oppPlayer->numStones = oldState->oppNumStones; + field->curPlayer->numStonesMissing = oldState->curMissStones; + field->oppPlayer->numStonesMissing = oldState->oppMissStones; + field->curPlayer->numPossibleMoves = oldState->curPosMoves; + field->oppPlayer->numPossibleMoves = oldState->oppPosMoves; + field->settingPhase = oldState->settingPhase; + field->stonesSet = oldState->stonesSet; + field->stoneMustBeRemoved = oldState->stoneMustBeRemoved; + field->field[oldState->from] = oldState->fieldFrom; + field->field[oldState->to ] = oldState->fieldTo; + + // very expensive + for (int i=0; isize; i++) { + field->stonePartOfMill[i] = oldState->stonePartOfMill[i]; + field->warnings[i] = oldState->warnings[i]; + } +} + +//----------------------------------------------------------------------------- +// Name: setWarning() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::setWarning(unsigned int stoneOne, unsigned int stoneTwo, unsigned int stoneThree) +{ + // if all 3 fields are occupied by current player than he closed a mill + if (field->field[stoneOne] == field->curPlayer->id && field->field[stoneTwo] == field->curPlayer->id && field->field[stoneThree] == field->curPlayer->id) { + + field->stonePartOfMill[stoneOne ]++; + field->stonePartOfMill[stoneTwo ]++; + field->stonePartOfMill[stoneThree]++; + field->stoneMustBeRemoved = 1; + } + + // is a mill destroyed ? + if (field->field[stoneOne] == field->squareIsFree && field->stonePartOfMill[stoneOne] && field->stonePartOfMill[stoneTwo] && field->stonePartOfMill[stoneThree]) { + + field->stonePartOfMill[stoneOne ]--; + field->stonePartOfMill[stoneTwo ]--; + field->stonePartOfMill[stoneThree]--; + } + + // stone was set + if (field->field[stoneOne] == field->curPlayer->id) { + + // a warnig was destroyed + field->warnings[stoneOne] = field->noWarning; + + // a warning is created + if (field->field[stoneTwo ] == field->curPlayer->id && field->field[stoneThree] == field->squareIsFree) field->warnings[stoneThree] |= field->curPlayer->warning; + if (field->field[stoneThree] == field->curPlayer->id && field->field[stoneTwo ] == field->squareIsFree) field->warnings[stoneTwo ] |= field->curPlayer->warning; + + // stone was removed + } else if (field->field[stoneOne] == field->squareIsFree) { + + // a warning is created + if (field->field[stoneTwo ] == field->curPlayer->id && field->field[stoneThree] == field->curPlayer->id) field->warnings[stoneOne] |= field->curPlayer->warning; + if (field->field[stoneTwo ] == field->oppPlayer->id && field->field[stoneThree] == field->oppPlayer->id) field->warnings[stoneOne] |= field->oppPlayer->warning; + + // a warning is destroyed + if (field->warnings[stoneTwo] && field->field[stoneThree] != field->squareIsFree) { + + // reset warning if necessary + if (field->field[field->neighbour[stoneTwo][0][0]] == field->curPlayer->id && field->field[field->neighbour[stoneTwo][0][1]] == field->curPlayer->id) field->warnings[stoneTwo ] = field->curPlayer->warning; + else if (field->field[field->neighbour[stoneTwo][1][0]] == field->curPlayer->id && field->field[field->neighbour[stoneTwo][1][1]] == field->curPlayer->id) field->warnings[stoneTwo ] = field->curPlayer->warning; + else if (field->field[field->neighbour[stoneTwo][0][0]] == field->oppPlayer->id && field->field[field->neighbour[stoneTwo][0][1]] == field->oppPlayer->id) field->warnings[stoneTwo ] = field->oppPlayer->warning; + else if (field->field[field->neighbour[stoneTwo][1][0]] == field->oppPlayer->id && field->field[field->neighbour[stoneTwo][1][1]] == field->oppPlayer->id) field->warnings[stoneTwo ] = field->oppPlayer->warning; + else field->warnings[stoneTwo ] = field->noWarning; + + } else if (field->warnings[stoneThree] && field->field[stoneTwo ] != field->squareIsFree) { + + // reset warning if necessary + if (field->field[field->neighbour[stoneThree][0][0]] == field->curPlayer->id && field->field[field->neighbour[stoneThree][0][1]] == field->curPlayer->id) field->warnings[stoneThree] = field->curPlayer->warning; + else if (field->field[field->neighbour[stoneThree][1][0]] == field->curPlayer->id && field->field[field->neighbour[stoneThree][1][1]] == field->curPlayer->id) field->warnings[stoneThree] = field->curPlayer->warning; + else if (field->field[field->neighbour[stoneThree][0][0]] == field->oppPlayer->id && field->field[field->neighbour[stoneThree][0][1]] == field->oppPlayer->id) field->warnings[stoneThree] = field->oppPlayer->warning; + else if (field->field[field->neighbour[stoneThree][1][0]] == field->oppPlayer->id && field->field[field->neighbour[stoneThree][1][1]] == field->oppPlayer->id) field->warnings[stoneThree] = field->oppPlayer->warning; + else field->warnings[stoneThree] = field->noWarning; + } + } +} + +//----------------------------------------------------------------------------- +// Name: updateWarning() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::updateWarning(unsigned int firstStone, unsigned int secondStone) +{ + // set warnings + if (firstStone < field->size) setWarning(firstStone, field->neighbour[firstStone][0][0], field->neighbour[firstStone][0][1]); + if (firstStone < field->size) setWarning(firstStone, field->neighbour[firstStone][1][0], field->neighbour[firstStone][1][1]); + + if (secondStone < field->size) setWarning(secondStone, field->neighbour[secondStone][0][0], field->neighbour[secondStone][0][1]); + if (secondStone < field->size) setWarning(secondStone, field->neighbour[secondStone][1][0], field->neighbour[secondStone][1][1]); + + // no stone must be removed if each belongs to a mill + unsigned int i; + bool atLeastOneStoneRemoveAble = false; + if (field->stoneMustBeRemoved) for (i=0; isize; i++) if (field->stonePartOfMill[i] == 0 && field->field[i] == field->oppPlayer->id) { atLeastOneStoneRemoveAble = true; break; } + if (!atLeastOneStoneRemoveAble) field->stoneMustBeRemoved = 0; +} + +//----------------------------------------------------------------------------- +// Name: updatePossibleMoves() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::updatePossibleMoves(unsigned int stone, playerStruct *stoneOwner, bool stoneRemoved, unsigned int ignoreStone) +{ + // locals + unsigned int neighbor, direction; + + // look into every direction + for (direction=0; direction<4; direction++) { + + neighbor = field->connectedSquare[stone][direction]; + + // neighbor must exist + if (neighbor < field->size) { + + // relevant when moving from one square to another connected square + if (ignoreStone == neighbor) continue; + + // if there is no neighbour stone than it only affects the actual stone + if (field->field[neighbor] == field->squareIsFree) { + + if (stoneRemoved) stoneOwner->numPossibleMoves--; + else stoneOwner->numPossibleMoves++; + + // if there is a neighbour stone than it effects only this one + } else if (field->field[neighbor] == field->curPlayer->id) { + + if (stoneRemoved) field->curPlayer->numPossibleMoves++; + else field->curPlayer->numPossibleMoves--; + + } else { + + if (stoneRemoved) field->oppPlayer->numPossibleMoves++; + else field->oppPlayer->numPossibleMoves--; + } + }} + + // only 3 stones resting + if (field->curPlayer->numStones <= 3 && !field->settingPhase) field->curPlayer->numPossibleMoves = field->curPlayer->numStones * (field->size - field->curPlayer->numStones - field->oppPlayer->numStones); + if (field->oppPlayer->numStones <= 3 && !field->settingPhase) field->oppPlayer->numPossibleMoves = field->oppPlayer->numStones * (field->size - field->curPlayer->numStones - field->oppPlayer->numStones); +} + +//----------------------------------------------------------------------------- +// Name: setStone() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::setStone(unsigned int to, backupStruct *backup) +{ + // backup + backup->from = field->size; + backup->to = to; + backup->fieldFrom = field->size; + backup->fieldTo = field->field[to]; + + // set stone into field + field->field[to] = field->curPlayer->id; + field->curPlayer->numStones++; + field->stonesSet++; + + // setting phase finished ? + if (field->stonesSet == 18) field->settingPhase = false; + + // update possible moves + updatePossibleMoves(to, field->curPlayer, false, field->size); + + // update warnings + updateWarning(to, field->size); +} + +//----------------------------------------------------------------------------- +// Name: normalMove() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::normalMove(unsigned int from, unsigned int to, backupStruct *backup) +{ + // backup + backup->from = from; + backup->to = to; + backup->fieldFrom = field->field[from]; + backup->fieldTo = field->field[to ]; + + // set stone into field + field->field[from] = field->squareIsFree; + field->field[to] = field->curPlayer->id; + + // update possible moves + updatePossibleMoves(from, field->curPlayer, true, to); + updatePossibleMoves(to, field->curPlayer, false, from); + + // update warnings + updateWarning(from, to); +} + +//----------------------------------------------------------------------------- +// Name: removeStone() +// Desc: +//----------------------------------------------------------------------------- +inline void minMaxKI::removeStone(unsigned int from, backupStruct *backup) +{ + // backup + backup->from = from; + backup->to = field->size; + backup->fieldFrom = field->field[from]; + backup->fieldTo = field->size; + + // remove stone + field->field[from] = field->squareIsFree; + field->oppPlayer->numStones--; + field->oppPlayer->numStonesMissing++; + field->stoneMustBeRemoved--; + + // update possible moves + updatePossibleMoves(from, field->oppPlayer, true, field->size); + + // update warnings + updateWarning(from, field->size); + + // end of game ? + if ((field->oppPlayer->numStones < 3) && (!field->settingPhase)) gameHasFinished = true; +} + +//----------------------------------------------------------------------------- +// Name: move() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::move(unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void **pBackup, void *pPossibilities) +{ + // locals + backupStruct *oldState = &oldStates[curSearchDepth]; + possibilityStruct *tmpPossibility = (possibilityStruct*) pPossibilities; + playerStruct *tmpPlayer; + unsigned int i; + + // calculate place of stone + *pBackup = (void*) oldState; + oldState->value = currentValue; + oldState->gameHasFinished = gameHasFinished; + oldState->curPlayer = field->curPlayer; + oldState->oppPlayer = field->oppPlayer; + oldState->curNumStones = field->curPlayer->numStones; + oldState->oppNumStones = field->oppPlayer->numStones; + oldState->curPosMoves = field->curPlayer->numPossibleMoves; + oldState->oppPosMoves = field->oppPlayer->numPossibleMoves; + oldState->curMissStones = field->curPlayer->numStonesMissing; + oldState->oppMissStones = field->oppPlayer->numStonesMissing; + oldState->settingPhase = field->settingPhase; + oldState->stonesSet = field->stonesSet; + oldState->stoneMustBeRemoved= field->stoneMustBeRemoved; + curSearchDepth++; + + // very expensive + for (i=0; isize; i++) { + oldState->stonePartOfMill[i] = field->stonePartOfMill[i]; + oldState->warnings[i] = field->warnings[i]; + } + + // move + if (field->stoneMustBeRemoved) { removeStone(idPossibility, oldState); } + else if (field->settingPhase) { setStone(idPossibility, oldState); } + else { normalMove(tmpPossibility->from[idPossibility], tmpPossibility->to[idPossibility], oldState); } + + // when opponent is unable to move than current player has won + if ((!field->oppPlayer->numPossibleMoves) && (!field->settingPhase) && (!field->stoneMustBeRemoved) && (field->oppPlayer->numStones > 3)) gameHasFinished = true; + + // calc value + if (!opponentsMove) currentValue = (float) field->oppPlayer->numStonesMissing - field->curPlayer->numStonesMissing + field->stoneMustBeRemoved + field->curPlayer->numPossibleMoves * 0.1f - field->oppPlayer->numPossibleMoves * 0.1f; + else currentValue = (float) field->curPlayer->numStonesMissing - field->oppPlayer->numStonesMissing - field->stoneMustBeRemoved + field->oppPlayer->numPossibleMoves * 0.1f - field->curPlayer->numPossibleMoves * 0.1f; + + // when game has finished - perfect for the current player + if (gameHasFinished && !opponentsMove) currentValue = VALUE_GAME_WON - curSearchDepth; + if (gameHasFinished && opponentsMove) currentValue = VALUE_GAME_LOST + curSearchDepth; + + // set next player + if (!field->stoneMustBeRemoved) { + tmpPlayer = field->curPlayer; + field->curPlayer = field->oppPlayer; + field->oppPlayer = tmpPlayer; + } +} + +//----------------------------------------------------------------------------- +// Name: printMoveInformation() +// Desc: +//----------------------------------------------------------------------------- +void minMaxKI::printMoveInformation(unsigned int threadNo, unsigned int idPossibility, void *pPossibilities) +{ + // locals + possibilityStruct *tmpPossibility = (possibilityStruct*) pPossibilities; + + // move + if (field->stoneMustBeRemoved) cout << "remove stone from " << (char) (idPossibility + 97); + else if (field->settingPhase) cout << "set stone to " << (char) (idPossibility + 97); + else cout << "move from " << (char) (tmpPossibility->from[idPossibility] + 97) << " to " << (char) (tmpPossibility->to[idPossibility] + 97); +} diff --git a/src/perfect/minMaxKI.h b/src/perfect/minMaxKI.h new file mode 100644 index 00000000..71983cc9 --- /dev/null +++ b/src/perfect/minMaxKI.h @@ -0,0 +1,110 @@ +/*********************************************************************\ + minMaxKI.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef MINIMAXKI_H +#define MINIMAXKI_H + +#include +#include +#include +#include "muehleKI.h" +#include "miniMax.h" + +//using namespace std; + +#define VALUE_GAME_LOST -1000.0f +#define VALUE_GAME_WON 1000.0f + +/*** Klassen *********************************************************/ +class minMaxKI : public muehleKI, miniMax +{ +protected: + + // structs + struct possibilityStruct + { + unsigned int from[MAX_NUM_POS_MOVES]; + unsigned int to [MAX_NUM_POS_MOVES]; + }; + + struct backupStruct + { + float value; + bool gameHasFinished; + bool settingPhase; + int fieldFrom, fieldTo; // value of field + unsigned int from, to; // index of field + unsigned int curNumStones, oppNumStones; + unsigned int curPosMoves, oppPosMoves; + unsigned int curMissStones, oppMissStones; + unsigned int stonesSet; + unsigned int stoneMustBeRemoved; + unsigned int stonePartOfMill[fieldStruct::size]; + unsigned int warnings[fieldStruct::size]; + playerStruct *curPlayer, *oppPlayer; + }; + + // Variables + fieldStruct *field; // pointer of the current field [changed by move()] + float currentValue; // value of current situation for field->currentPlayer + bool gameHasFinished; // someone has won or current field is full + + int ownId; // id of the player who called the play()-function + unsigned int curSearchDepth; // current level + unsigned int depthOfFullTree; // search depth where the whole tree is explored + unsigned int *idPossibilities; // returned pointer of getPossibilities()-function + backupStruct *oldStates; // for undo()-function + possibilityStruct *possibilities; // for getPossNormalMove()-function + + // Functions + unsigned int * getPossSettingPhase (unsigned int *numPossibilities, void **pPossibilities); + unsigned int * getPossNormalMove (unsigned int *numPossibilities, void **pPossibilities); + unsigned int * getPossStoneRemove (unsigned int *numPossibilities, void **pPossibilities); + + // move functions + inline void updatePossibleMoves (unsigned int stone, playerStruct *stoneOwner, bool stoneRemoved, unsigned int ignoreStone); + inline void updateWarning (unsigned int firstStone, unsigned int secondStone); + inline void setWarning (unsigned int stoneOne, unsigned int stoneTwo, unsigned int stoneThree); + inline void removeStone (unsigned int from, backupStruct *backup); + inline void setStone (unsigned int to, backupStruct *backup); + inline void normalMove (unsigned int from, unsigned int to, backupStruct *backup); + + // Virtual Functions + void prepareBestChoiceCalculation (); + unsigned int * getPossibilities (unsigned int threadNo, unsigned int *numPossibilities, bool *opponentsMove, void **pPossibilities); + void deletePossibilities (unsigned int threadNo, void *pPossibilities); + void move (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void **pBackup, void *pPossibilities); + void undo (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void *pBackup, void *pPossibilities); + void getValueOfSituation (unsigned int threadNo, float &floatValue, twoBit &shortValue); + void printMoveInformation (unsigned int threadNo, unsigned int idPossibility, void *pPossibilities); + + unsigned int getNumberOfLayers () { return 0; }; + unsigned int getNumberOfKnotsInLayer (unsigned int layerNum) { return 0; }; + void getSuccLayers (unsigned int layerNum, unsigned int *amountOfSuccLayers, unsigned int *succLayers) { }; + unsigned int getPartnerLayer (unsigned int layerNum) { return 0; }; + string getOutputInformation (unsigned int layerNum) { return string("");}; + void setOpponentLevel (unsigned int threadNo, bool isOpponentLevel) { }; + bool setSituation (unsigned int threadNo, unsigned int layerNum, unsigned int stateNumber) { return false; }; + bool getOpponentLevel (unsigned int threadNo) { return false; }; + unsigned int getLayerAndStateNumber (unsigned int threadNo, unsigned int &layerNum, unsigned int &stateNumber) { return 0; }; + unsigned int getLayerNumber (unsigned int threadNo) { return 0; }; + void getSymStateNumWithDoubles (unsigned int threadNo, unsigned int *numSymmetricStates, unsigned int **symStateNumbers) { }; + void getPredecessors (unsigned int threadNo, unsigned int *amountOfPred, retroAnalysisPredVars *predVars) { }; + void printField (unsigned int threadNo, unsigned char value) { }; + void prepareDatabaseCalculation () { }; + void wrapUpDatabaseCalculation (bool calculationAborted) { }; + +public: + // Constructor / destructor + minMaxKI(); + ~minMaxKI(); + + // Functions + void play (fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo); + void setSearchDepth (unsigned int depth); +}; + +#endif \ No newline at end of file diff --git a/src/perfect/miniMax.cpp b/src/perfect/miniMax.cpp new file mode 100644 index 00000000..789b59d8 --- /dev/null +++ b/src/perfect/miniMax.cpp @@ -0,0 +1,289 @@ +/********************************************************************* + miniMax.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: miniMax() +// Desc: miniMax class constructor +//----------------------------------------------------------------------------- +miniMax::miniMax() +{ + // init default values + hFileShortKnotValues = NULL; + hFilePlyInfo = NULL; + memoryUsed2 = 0; + arrayInfos.c = this; + arrayInfos.arrayInfosToBeUpdated.clear(); + arrayInfos.listArrays.clear(); + onlyPrepareLayer = false; + curCalculatedLayer = 0; + osPrint = &cout; + verbosity = 3; + stopOnCriticalError = true; + pDataForUserPrintFunc = NULL; + userPrintFunc = NULL; + layerStats = NULL; + plyInfos = NULL; + fileDirectory.assign(""); + InitializeCriticalSection(&csDatabase); + InitializeCriticalSection(&csOsPrint); + + // Tausender-Trennzeichen + locale locale("German_Switzerland"); + cout.imbue(locale); + + // for io operations per second measurement + QueryPerformanceFrequency(&frequency); + numReadSkvOperations = 0; + numWriteSkvOperations = 0; + numReadPlyOperations = 0; + numWritePlyOperations = 0; + if (MEASURE_ONLY_IO) { + readSkvInterval.QuadPart = 0; + writeSkvInterval.QuadPart = 0; + readPlyInterval.QuadPart = 0; + writePlyInterval.QuadPart = 0; + } else { + QueryPerformanceCounter(&readSkvInterval ); + QueryPerformanceCounter(&writeSkvInterval); + QueryPerformanceCounter(&readPlyInterval ); + QueryPerformanceCounter(&writePlyInterval); + } + + // The algorithm assumes that each player does only one move. + // That means closing a mill and removing a stone should be one move. + // PL_TO_MOVE_CHANGED means that in the predecessor state the player to move has changed to the other player. + // PL_TO_MOVE_UNCHANGED means that the player to move is still the one who shall move. + unsigned char skvPerspectiveMatrixTmp[4][2] = { + // PL_TO_MOVE_UNCHANGED PL_TO_MOVE_CHANGED + SKV_VALUE_INVALID, SKV_VALUE_INVALID, // SKV_VALUE_INVALID + SKV_VALUE_GAME_WON, SKV_VALUE_GAME_LOST, // SKV_VALUE_GAME_LOST + SKV_VALUE_GAME_DRAWN, SKV_VALUE_GAME_DRAWN, // SKV_VALUE_GAME_DRAWN + SKV_VALUE_GAME_LOST, SKV_VALUE_GAME_WON // SKV_VALUE_GAME_WON + }; + + memcpy(skvPerspectiveMatrix, skvPerspectiveMatrixTmp, 4 * 2); +} + +//----------------------------------------------------------------------------- +// Name: ~miniMax() +// Desc: miniMax class destructor +//----------------------------------------------------------------------------- +miniMax::~miniMax() +{ + closeDatabase(); + DeleteCriticalSection(&csOsPrint); + DeleteCriticalSection(&csDatabase); +} + +//----------------------------------------------------------------------------- +// Name: falseOrStop() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::falseOrStop() +{ + if (stopOnCriticalError) WaitForSingleObject(GetCurrentProcess(), INFINITE); + return false; +} + +//----------------------------------------------------------------------------- +// Name: getBestChoice() +// Desc: Returns the best choice if the database has been opened and calculates the best choice for that if database is not open. +//----------------------------------------------------------------------------- +void *miniMax::getBestChoice(unsigned int tilLevel, unsigned int *choice, unsigned int maximumNumberOfBranches) +{ + // set global vars + depthOfFullTree = tilLevel; + maxNumBranches = maximumNumberOfBranches; + layerInDatabase = isCurrentStateInDatabase(0); + calcDatabase = false; + + // Locals + knotStruct root; + alphaBetaGlobalVars alphaBetaVars(this, getLayerNumber(0)); + runAlphaBetaVars tva(this, &alphaBetaVars, alphaBetaVars.layerNumber); + srand((unsigned int)time(NULL)); + tva.curThreadNo = 0; + + // prepare the situation + prepareBestChoiceCalculation(); + + // First make a tree until the desired level + letTheTreeGrow(&root, &tva, depthOfFullTree, FPKV_MIN_VALUE, FPKV_MAX_VALUE); + + // pass best choice and close database + *choice = root.bestMoveId; + + // Return the best branch of the root + return pRootPossibilities; +} + +//----------------------------------------------------------------------------- +// Name: calculateDatabase() +// Desc: Calculates the database, which must be already open. +//----------------------------------------------------------------------------- +void miniMax::calculateDatabase(unsigned int maxDepthOfTree, bool onlyPrepareLayer) +{ + // locals + bool abortCalculation = false; + this->onlyPrepareLayer = onlyPrepareLayer; + lastCalculatedLayer.clear(); + + PRINT(1, this, "*************************"); + PRINT(1, this, "* Calculate Database *"); + PRINT(1, this, "*************************"); + + // call preparation function of parent class + prepareDatabaseCalculation(); + + // when database not completed then do it + if (hFileShortKnotValues != NULL && skvfHeader.completed == false) { + + // reserve memory + lastCalculatedLayer.clear(); + depthOfFullTree = maxDepthOfTree; + layerInDatabase = false; + calcDatabase = true; + threadManager.uncancelExecution(); + arrayInfos.vectorArrays.resize(arrayInfoStruct::numArrayTypes*skvfHeader.numLayers, arrayInfos.listArrays.end()); + + // calc layer after layer, beginning with the last one + for (curCalculatedLayer=0; curCalculatedLayer layersToCalculate; + + // moves can be done reverse, leading to too depth searching trees + if (shallRetroAnalysisBeUsed(layerNumber)) { + + // calc values for all states of layer + layersToCalculate.push_back(layerNumber); + if (layerNumber != layerStats[layerNumber].partnerLayer) layersToCalculate.push_back(layerStats[layerNumber].partnerLayer); + if (!calcKnotValuesByRetroAnalysis(layersToCalculate)) return false; + + // save partner layer + if (layerStats[layerNumber].partnerLayer != layerNumber) { + saveLayerToFile(layerStats[layerNumber].partnerLayer); + } + + // use minimax-algorithm + } else { + if (!calcKnotValuesByAlphaBeta(layerNumber)) return false; + } + + // save layer + saveLayerToFile(layerNumber); + + // test layer + if (!testLayer(layerNumber)) { + PRINT(0, this, "ERROR: Layer calculation cancelled or failed!" << endl); + return false; + } + + // test partner layer if retro-analysis has been used + if (shallRetroAnalysisBeUsed(layerNumber) && layerStats[layerNumber].partnerLayer != layerNumber) { + if (!testLayer(layerStats[layerNumber].partnerLayer)) { + PRINT(0, this, "ERROR: Layer calculation cancelled or failed!" << endl); + return false; + } + } + + // update output information + EnterCriticalSection(&csOsPrint); + if (shallRetroAnalysisBeUsed(layerNumber) && layerNumber != layerStats[layerNumber].partnerLayer) { + lastCalculatedLayer.push_back(layerStats[layerNumber].partnerLayer); + } + lastCalculatedLayer.push_back(layerNumber); + LeaveCriticalSection(&csOsPrint); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: pauseDatabaseCalculation() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::pauseDatabaseCalculation() +{ + threadManager.pauseExecution(); +} + +//----------------------------------------------------------------------------- +// Name: cancelDatabaseCalculation() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::cancelDatabaseCalculation() +{ + // when returning from executeParallelLoop() all function shall quit immediatelly up to calculateDatabase() + threadManager.cancelExecution(); +} + +//----------------------------------------------------------------------------- +// Name: wasDatabaseCalculationCancelled() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::wasDatabaseCalculationCancelled() +{ + return threadManager.wasExecutionCancelled(); +} diff --git a/src/perfect/miniMax.h b/src/perfect/miniMax.h new file mode 100644 index 00000000..04183bcf --- /dev/null +++ b/src/perfect/miniMax.h @@ -0,0 +1,604 @@ +/*************************************************************************************************************************** + miniMax.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +***************************************************************************************************************************/ +#ifndef MINIMAX_H +#define MINIMAX_H + +#include +#include +#include +#include +#include +#include "Shlwapi.h" +#include +#include +#include +#include +#include "cyclicArray.h" +#include "strLib.h" +#include "threadManager.h" +#include "bufferedFile.h" + +#pragma intrinsic(_rotl8, _rotr8) // for shifting bits + +using namespace std; // use standard library namespace + +/*** Wiki *************************************************************************************************************************** +player: +layer: The states are divided in layers. For example depending on number of stones on the field. +state: A unique game state reprensiting a current game situation. +situation: Used as synonym to state. +knot: Each knot of the graph corresponds to a game state. The knots are connected by possible valid moves. +ply info: Number of plies/moves necessary to win the game. +state adress: A state is identified by the corresponding layer and the state number within the layer. +short knot value: Each knot/state can have the value SKV_VALUE_INVALID, SKV_VALUE_GAME_LOST, SKV_VALUE_GAME_DRAWN or SKV_VALUE_GAME_WON. +float point knot value: Each knot/state can be evaluated by a floating point value. High positive values represents winning situations. Negative values stand for loosing situations. +database: The database contains the arrays with the short knot values and the ply infos. + +/*** Constants ***************************************************************************************************************************/ +#define FPKV_MIN_VALUE -100000.0f // minimum float point knot value +#define FPKV_MAX_VALUE 100000.0f // maximum float point knot value +#define FPKV_THRESHOLD 0.001f // threshold used when choosing best move. knot values differing less than this threshold will be regarded as egal + +#define SKV_VALUE_INVALID 0 // short knot value: knot value is invalid +#define SKV_VALUE_GAME_LOST 1 // game lost means that there is no perfect move possible +#define SKV_VALUE_GAME_DRAWN 2 // the perfect move leads at least to a drawn game +#define SKV_VALUE_GAME_WON 3 // the perfect move will lead to a won game +#define SKV_MAX_VALUE 3 // highest short knot value +#define SKV_NUM_VALUES 4 // number of different short knot values +#define SKV_WHOLE_BYTE_IS_INVALID 0 // four short knot values are stored in one byte. so all four knot values are invalid + +#define PLYINFO_EXP_VALUE 1000 // expected maximum number of plies -> user for vector initialization +#define PLYINFO_VALUE_DRAWN 65001 // knot value is drawn. since drawn means a never ending game, this is a special ply info +#define PLYINFO_VALUE_UNCALCULATED 65002 // ply info is not calculated yet for this game state +#define PLYINFO_VALUE_INVALID 65003 // ply info is invalid, since knot value is invalid + +#define MAX_NUM_PRED_LAYERS 2 // each layer must have at maximum two preceding layers + +#define SKV_FILE_HEADER_CODE 0xF4F5 // constant to identify the header +#define PLYINFO_HEADER_CODE 0xF3F2 // '' + +#define OUTPUT_EVERY_N_STATES 10000000 // print progress every n-th processed knot +#define BLOCK_SIZE_IN_CYCLIC_ARRAY 10000 // BLOCK_SIZE_IN_CYCLIC_ARRAY*sizeof(stateAdressStruct) = block size in bytes for the cyclic arrays +#define MAX_NUM_PREDECESSORS 10000 // maximum number of predecessors. important for array sizes +#define FILE_BUFFER_SIZE 1000000 // size in bytes + +#define PL_TO_MOVE_CHANGED 1 // player to move changed - second index of the 2D-array skvPerspectiveMatrix[][] +#define PL_TO_MOVE_UNCHANGED 0 // player to move is still the same - second index of the 2D-array skvPerspectiveMatrix[][] + +#define MEASURE_TIME_FREQUENCY 100000 // for io operations per second: measure time every n-th operations +#define MEASURE_IOPS false // true or false - for measurement of the input/output operations per second +#define MEASURE_ONLY_IO false // true or false - to indicate if only the io-operation shall be considered or also the calculating time inbetween + +#define MM_ACTION_INIT_RETRO_ANAL 1 +#define MM_ACTION_PREPARE_COUNT_ARRAY 2 +#define MM_ACTION_PERFORM_RETRO_ANAL 3 +#define MM_ACTION_PERFORM_ALPHA_BETA 4 +#define MM_ACTION_TESTING_LAYER 5 +#define MM_ACTION_SAVING_LAYER_TO_FILE 6 +#define MM_ACTION_CALC_LAYER_STATS 7 +#define MM_ACTION_NONE 8 + +/*** Macros ***************************************************************************************************************************/ +#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } +#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } } + +// here a macro is used instead of a function because the text 't' is passed like "blabla" << endl << aVariable +#define PRINT(v, c, t) \ +{ \ + if (c->verbosity > v) { \ + EnterCriticalSection(&c->csOsPrint); \ + *c->osPrint << endl << t; \ + if (c->userPrintFunc != NULL) { \ + c->userPrintFunc(c->pDataForUserPrintFunc); \ + } \ + LeaveCriticalSection(&c->csOsPrint); \ + } \ +} + +/*** Klassen ***************************************************************************************************************************/ +class miniMax +{ + friend class miniMaxWinInspectDb; + friend class miniMaxWinCalcDb; + +public: + + /*** typedefines ***************************************************************************************************************************/ + typedef unsigned char twoBit; // 2-Bit variable ranging from 0 to 3 + typedef unsigned short plyInfoVarType; // 2 Bytes for saving the ply info + typedef unsigned char countArrayVarType; // 1 Byte for counting predesseccors + typedef unsigned int stateNumberVarType; // 4 Bytes for addressing states within a layer + + /*** protected structures ********************************************************************************************************************/ + + struct skvFileHeaderStruct // header of the short knot value file + { + bool completed; // true if all states have been calculated + unsigned int numLayers; // number of layers + unsigned int headerCode; // = SKV_FILE_HEADER_CODE + unsigned int headerAndStatsSize; // size in bytes of this struct plus the stats + }; + + struct plyInfoFileHeaderStruct + { + bool plyInfoCompleted; // true if ply innformation has been calculated for all game states + unsigned int numLayers; // number of layers + unsigned int headerCode; // = PLYINFO_HEADER_CODE + unsigned int headerAndPlyInfosSize; // size in bytes of this struct plus ... + }; + + struct plyInfoStruct // this struct is created for each layer + { + bool plyInfoIsLoaded; // the array plyInfo[] exists in memory. does not necessary mean that it contains only valid values + bool plyInfoIsCompletedAndInFile; // the array plyInfo[] contains only fully calculated valid values + long long layerOffset; // position of this struct in the ply info file + unsigned int sizeInBytes; // size of this struct plus the array plyInfo[] + stateNumberVarType knotsInLayer; // number of knots of the corresponding layer + plyInfoVarType * plyInfo; // array of size [knotsInLayer] containing the ply info for each knot in this layer + // compressorClass::compressedArrayClass * plyInfoCompressed; // compressed array containing the ply info for each knot in this layer + void* plyInfoCompressed; // dummy pointer for padding + }; + + struct layerStatsStruct + { + bool layerIsLoaded; // the array shortKnotValueByte[] exists in memory. does not necessary mean that it contains only valid values + bool layerIsCompletedAndInFile; // the array shortKnotValueByte[] contains only fully calculated valid values + long long layerOffset; // position of this struct in the short knot value file + unsigned int numSuccLayers; // number of succeding layers. states of other layers are connected by a move of a player + unsigned int succLayers[MAX_NUM_PRED_LAYERS];// array containg the layer ids of the succeding layers + unsigned int partnerLayer; // layer id relevant when switching current and opponent player + stateNumberVarType knotsInLayer; // number of knots of the corresponding layer + stateNumberVarType numWonStates; // number of won states in this layer + stateNumberVarType numLostStates; // number of lost states in this layer + stateNumberVarType numDrawnStates; // number of drawn states in this layer + stateNumberVarType numInvalidStates; // number of invalid states in this layer + unsigned int sizeInBytes; // (knotsInLayer + 3) / 4 + twoBit * shortKnotValueByte; // array of size [sizeInBytes] containg the short knot values + //compressorClass::compressedArrayClass * skvCompressed; // compressed array containing the short knot values + void* skvCompressed; // dummy pointer for padding + }; + + struct stateAdressStruct + { + stateNumberVarType stateNumber; // state id within the corresponding layer + unsigned char layerNumber; // layer id + }; + + struct knotStruct + { + bool isOpponentLevel; // the current considered knot belongs to an opponent game state + float floatValue; // Value of knot (for normal mode) + twoBit shortValue; // Value of knot (for database) + unsigned int bestMoveId; // for calling class + unsigned int bestBranch; // branch with highest value + unsigned int numPossibilities; // number of branches + plyInfoVarType plyInfo; // number of moves till win/lost + knotStruct * branches; // pointer to branches + }; + + struct retroAnalysisPredVars + { + unsigned int predStateNumbers; // + unsigned int predLayerNumbers; // + unsigned int predSymOperation; // + bool playerToMoveChanged; // + }; + + struct arrayInfoStruct + { + unsigned int type; // + long long sizeInBytes; // + long long compressedSizeInBytes; // + unsigned int belongsToLayer; // + unsigned int updateCounter; + + static const unsigned int arrayType_invalid = 0; + static const unsigned int arrayType_knotAlreadyCalculated = 1; + static const unsigned int arrayType_countArray = 2; + static const unsigned int arrayType_plyInfos = 3; + static const unsigned int arrayType_layerStats = 4; + static const unsigned int numArrayTypes = 5; + + static const unsigned int updateCounterThreshold = 100; + }; + + struct arrayInfoChange + { + unsigned int itemIndex; // + arrayInfoStruct * arrayInfo; // + }; + + struct arrayInfoContainer + { + miniMax* c; + list arrayInfosToBeUpdated; // + list listArrays; // [itemIndex] + vector::iterator> vectorArrays; // [layerNumber*arrayInfoStruct::numArrayTypes + type] + + void addArray (unsigned int layerNumber, unsigned int type, long long size, long long compressedSize); + void removeArray (unsigned int layerNumber, unsigned int type, long long size, long long compressedSize); + void updateArray (unsigned int layerNumber, unsigned int type); + }; + + + /*** public functions ***************************************************************************************************************************/ + + // Constructor / destructor + miniMax(); + ~miniMax(); + + // Testing functions + bool testState (unsigned int layerNumber, unsigned int stateNumber); + bool testLayer (unsigned int layerNumber); + bool testIfSymStatesHaveSameValue (unsigned int layerNumber); + bool testSetSituationAndGetPoss (unsigned int layerNumber); + + // Statistics + bool calcLayerStatistics (char *statisticsFileName); + void showMemoryStatus (); + unsigned int getNumThreads (); + bool anyFreshlyCalculatedLayer (); + unsigned int getLastCalculatedLayer (); + stateNumberVarType getNumWonStates (unsigned int layerNum); + stateNumberVarType getNumLostStates (unsigned int layerNum); + stateNumberVarType getNumDrawnStates (unsigned int layerNum); + stateNumberVarType getNumInvalidStates (unsigned int layerNum); + bool isLayerInDatabase (unsigned int layerNum); + long long getLayerSizeInBytes (unsigned int layerNum); + void setOutputStream (ostream * theStream, void(*printFunc)(void *pUserData), void *pUserData); + bool anyArrawInfoToUpdate (); + arrayInfoChange getArrayInfoForUpdate (); + void getCurrentCalculatedLayer (vector &layers); + LPWSTR getCurrentActionStr (); + + // Main function for getting the best choice + void * getBestChoice (unsigned int tilLevel, unsigned int *choice, unsigned int maximumNumberOfBranches); + + // Database functions + bool openDatabase (const char *directory, unsigned int maximumNumberOfBranches); + void calculateDatabase (unsigned int maxDepthOfTree, bool onlyPrepareLayer); + bool isCurrentStateInDatabase (unsigned int threadNo); + void closeDatabase (); + void unloadAllLayers (); + void unloadAllPlyInfos (); + void pauseDatabaseCalculation (); + void cancelDatabaseCalculation (); + bool wasDatabaseCalculationCancelled (); + + // Virtual Functions + virtual void prepareBestChoiceCalculation () { while (true); }; // is called once before building the tree + virtual unsigned int * getPossibilities (unsigned int threadNo, unsigned int *numPossibilities, bool *opponentsMove, void **pPossibilities) { while (true); return 0; }; // returns a pointer to the possibility-IDs + virtual void deletePossibilities (unsigned int threadNo, void *pPossibilities) { while (true); }; + virtual void storeValueOfMove (unsigned int threadNo, unsigned int idPossibility, void *pPossibilities, twoBit value, unsigned int *freqValuesSubMoves, plyInfoVarType plyInfo) {}; + virtual void move (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void **pBackup, void *pPossibilities) { while (true); }; + virtual void undo (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void *pBackup, void *pPossibilities) { while (true); }; + + virtual bool shallRetroAnalysisBeUsed (unsigned int layerNum) { return false; }; + virtual unsigned int getNumberOfLayers () { while (true); return 0; }; + virtual unsigned int getNumberOfKnotsInLayer (unsigned int layerNum) { while (true); return 0; }; + virtual void getSuccLayers (unsigned int layerNum, unsigned int *amountOfSuccLayers, unsigned int *succLayers) { while (true); }; + virtual unsigned int getPartnerLayer (unsigned int layerNum) { while (true); return 0; }; + virtual string getOutputInformation (unsigned int layerNum) { while (true); return string(""); }; + + virtual void setOpponentLevel (unsigned int threadNo, bool isOpponentLevel) { while (true); }; + virtual bool setSituation (unsigned int threadNo, unsigned int layerNum, unsigned int stateNumber) { while (true); return false; }; + + virtual void getValueOfSituation (unsigned int threadNo, float &floatValue, twoBit &shortValue) { while (true); }; // value of situation for the initial current player + virtual bool getOpponentLevel (unsigned int threadNo) { while (true); return false; }; + virtual unsigned int getLayerAndStateNumber (unsigned int threadNo, unsigned int &layerNum, unsigned int &stateNumber) { while (true); return 0; }; + virtual unsigned int getLayerNumber (unsigned int threadNo) { while (true); return 0; }; + virtual void getSymStateNumWithDoubles (unsigned int threadNo, unsigned int *numSymmetricStates, unsigned int **symStateNumbers) { while (true); }; + virtual void getPredecessors (unsigned int threadNo, unsigned int *amountOfPred, retroAnalysisPredVars *predVars) { while (true); }; + + virtual void printField (unsigned int threadNo, unsigned char value) { while (true); }; + virtual void printMoveInformation (unsigned int threadNo, unsigned int idPossibility, void *pPossibilities) { while (true); }; + + virtual void prepareDatabaseCalculation () { while (true); }; + virtual void wrapUpDatabaseCalculation (bool calculationAborted) { while (true); }; + +private: + + /*** classes for testing ********************************************************************************************************************/ + + struct testLayersVars { + miniMax * pMiniMax; + unsigned int curThreadNo; + unsigned int layerNumber; + LONGLONG statesProcessed; + twoBit * subValueInDatabase; + plyInfoVarType * subPlyInfos; + bool * hasCurPlayerChanged; + }; + + /*** classes for the alpha beta algorithmn ********************************************************************************************************************/ + + struct alphaBetaThreadVars // thread specific variables for each thread in the alpha beta algorithmn + { + long long numStatesToProcess; // Number of states in 'statesToProcess' which have to be processed + unsigned int threadNo; + }; + + struct alphaBetaGlobalVars // constant during calculation + { + unsigned int layerNumber; // layer number of the current process layer + long long totalNumKnots; // total numbers of knots which have to be stored in memory + long long numKnotsToCalc; // number of knots of all layers to be calculated + vector thread; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + miniMax * pMiniMax; + + alphaBetaGlobalVars(miniMax *pMiniMax, unsigned int layerNumber) + { + this->thread.resize(pMiniMax->threadManager.getNumThreads()); + for (unsigned int threadNo=0; threadNothreadManager.getNumThreads(); threadNo++) { + this->thread[threadNo].numStatesToProcess = 0; + this->thread[threadNo].threadNo = threadNo; + } + this->layerNumber = layerNumber; + this->pMiniMax = pMiniMax; + if (pMiniMax->layerStats) { + this->numKnotsToCalc = pMiniMax->layerStats[layerNumber].knotsInLayer; + this->totalNumKnots = pMiniMax->layerStats[layerNumber].knotsInLayer; + } + this->statsValueCounter[SKV_VALUE_GAME_WON ] = 0; + this->statsValueCounter[SKV_VALUE_GAME_LOST ] = 0; + this->statsValueCounter[SKV_VALUE_GAME_DRAWN] = 0; + this->statsValueCounter[SKV_VALUE_INVALID ] = 0; + } + }; + + struct alphaBetaDefaultThreadVars + { + miniMax * pMiniMax; + alphaBetaGlobalVars * alphaBetaVars; + unsigned int layerNumber; + LONGLONG statesProcessed; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + + alphaBetaDefaultThreadVars() {}; + alphaBetaDefaultThreadVars(miniMax *pMiniMax, alphaBetaGlobalVars * alphaBetaVars, unsigned int layerNumber) + { + this->statesProcessed = 0; + this->layerNumber = layerNumber; + this->pMiniMax = pMiniMax; + this->alphaBetaVars = alphaBetaVars; + for (unsigned int curStateValue=0; curStateValuestatsValueCounter[curStateValue] = 0; + } + }; + void reduceDefault () + { + pMiniMax->numStatesProcessed += this->statesProcessed; + for (unsigned int curStateValue=0; curStateValuestatsValueCounter[curStateValue] += this->statsValueCounter[curStateValue]; + } + }; + }; + + struct initAlphaBetaVars : public threadManagerClass::threadVarsArrayItem, public alphaBetaDefaultThreadVars + { + bufferedFileClass * bufferedFile; + bool initAlreadyDone; + + initAlphaBetaVars() {}; + initAlphaBetaVars(miniMax *pMiniMax, alphaBetaGlobalVars * alphaBetaVars, unsigned int layerNumber, bufferedFileClass * initArray, bool initAlreadyDone) : alphaBetaDefaultThreadVars(pMiniMax, alphaBetaVars, layerNumber) + { + this->bufferedFile = initArray; + this->initAlreadyDone = initAlreadyDone; + }; + void initializeElement (initAlphaBetaVars &master) { *this = master; }; + void reduce () { reduceDefault(); }; + }; + + struct runAlphaBetaVars : public threadManagerClass::threadVarsArrayItem, public alphaBetaDefaultThreadVars + { + knotStruct * branchArray = NULL; // array of size [(depthOfFullTree - tilLevel) * maxNumBranches] for storage of the branches at each search depth + unsigned int * freqValuesSubMovesBranchWon = NULL; // ... + unsigned int freqValuesSubMoves[4]; // ... + + runAlphaBetaVars () {}; + runAlphaBetaVars (miniMax* pMiniMax, alphaBetaGlobalVars* alphaBetaVars, unsigned int layerNumber) : alphaBetaDefaultThreadVars(pMiniMax, alphaBetaVars, layerNumber) { initializeElement(*this); }; + ~runAlphaBetaVars () { SAFE_DELETE_ARRAY(branchArray); SAFE_DELETE_ARRAY(freqValuesSubMovesBranchWon); } + void reduce () { reduceDefault(); }; + void initializeElement (runAlphaBetaVars &master) + { + *this = master; + branchArray = new knotStruct [alphaBetaVars->pMiniMax->maxNumBranches * alphaBetaVars->pMiniMax->depthOfFullTree]; + freqValuesSubMovesBranchWon = new unsigned int[alphaBetaVars->pMiniMax->maxNumBranches]; + }; + }; + + /*** classes for the retro analysis ***************************************************************************************************************************/ + + struct retroAnalysisQueueState + { + stateNumberVarType stateNumber; // state stored in the retro analysis queue. the queue is a buffer containing states to be passed to 'retroAnalysisThreadVars::statesToProcess' + plyInfoVarType numPliesTillCurState; // ply number for the stored state + }; + + struct retroAnalysisThreadVars // thread specific variables for each thread in the retro analysis + { + vector statesToProcess; // vector-queue containing the states, whose short knot value are known for sure. they have to be processed. if processed the state will be removed from list. indexing: [threadNo][plyNumber] + vector> stateQueue; // Queue containing states, whose 'count value' shall be increased by one. Before writing 'count value' to 'count array' the writing positions are sorted for faster processing. + long long numStatesToProcess; // Number of states in 'statesToProcess' which have to be processed + unsigned int threadNo; + }; + + struct retroAnalysisGlobalVars // constant during calculation + { + vector countArrays; // One count array for each layer in 'layersToCalculate'. (For the nine men's morris game two layers have to considered at once.) + vector layerInitialized; // + vector layersToCalculate; // layers which shall be calculated + long long totalNumKnots; // total numbers of knots which have to be stored in memory + long long numKnotsToCalc; // number of knots of all layers to be calculated + vector thread; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + miniMax * pMiniMax; + }; + + struct retroAnalysisDefaultThreadVars + { + miniMax * pMiniMax; + retroAnalysisGlobalVars * retroVars; + unsigned int layerNumber; + LONGLONG statesProcessed; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + + retroAnalysisDefaultThreadVars() {}; + retroAnalysisDefaultThreadVars(miniMax *pMiniMax, retroAnalysisGlobalVars * retroVars, unsigned int layerNumber) + { + this->statesProcessed = 0; + this->layerNumber = layerNumber; + this->pMiniMax = pMiniMax; + this->retroVars = retroVars; + for (unsigned int curStateValue=0; curStateValuestatsValueCounter[curStateValue] = 0; + } + }; + void reduceDefault () + { + pMiniMax->numStatesProcessed += this->statesProcessed; + for (unsigned int curStateValue=0; curStateValuestatsValueCounter[curStateValue] += this->statsValueCounter[curStateValue]; + } + }; + }; + + struct initRetroAnalysisVars : public threadManagerClass::threadVarsArrayItem, public retroAnalysisDefaultThreadVars + { + bufferedFileClass * bufferedFile; + bool initAlreadyDone; + + initRetroAnalysisVars() {}; + initRetroAnalysisVars(miniMax *pMiniMax, retroAnalysisGlobalVars * retroVars, unsigned int layerNumber, bufferedFileClass * initArray, bool initAlreadyDone) : retroAnalysisDefaultThreadVars(pMiniMax, retroVars, layerNumber) + { + this->bufferedFile = initArray; + this->initAlreadyDone = initAlreadyDone; + }; + void initializeElement (initRetroAnalysisVars &master) { *this = master; }; + void reduce () { reduceDefault(); }; + }; + + struct addNumSuccedorsVars : public threadManagerClass::threadVarsArrayItem, public retroAnalysisDefaultThreadVars + { + retroAnalysisPredVars predVars[MAX_NUM_PREDECESSORS]; + + addNumSuccedorsVars() {}; + addNumSuccedorsVars(miniMax *pMiniMax, retroAnalysisGlobalVars * retroVars, unsigned int layerNumber) : retroAnalysisDefaultThreadVars(pMiniMax, retroVars, layerNumber) + { + }; + void initializeElement (addNumSuccedorsVars &master) { *this = master; }; + void reduce () { reduceDefault(); }; + }; + + + /*** private variables ***************************************************************************************************************************/ + + // variables, which are constant during database calculation + int verbosity = 2; // output detail level. default is 2 + unsigned char skvPerspectiveMatrix[4][2]; // [short knot value][current or opponent player] - A winning situation is a loosing situation for the opponent and so on ... + bool calcDatabase = false; // true, if the database is currently beeing calculated + HANDLE hFileShortKnotValues = NULL; // handle of the file for the short knot value + HANDLE hFilePlyInfo = NULL; // handle of the file for the ply info + skvFileHeaderStruct skvfHeader; // short knot value file header + plyInfoFileHeaderStruct plyInfoHeader; // header of the ply info file + string fileDirectory; // path of the folder where the database files are located + ostream * osPrint = NULL; // stream for output. default is cout + list lastCalculatedLayer; // + vector layersToCalculate; // used in calcLayer() and getCurrentCalculatedLayers() + bool onlyPrepareLayer = false; // + bool stopOnCriticalError = true; // if true then process will stay in while loop + threadManagerClass threadManager; // + CRITICAL_SECTION csDatabase; // + CRITICAL_SECTION csOsPrint; // for thread safety when output is passed to osPrint + void (*userPrintFunc)(void *) = NULL; // called every time output is passed to osPrint + void * pDataForUserPrintFunc = NULL; // pointer passed when calling userPrintFunc + arrayInfoContainer arrayInfos; // information about the arrays in memory + + // thread specific or non-constant variables + LONGLONG memoryUsed2 = 0; // memory in bytes used for storing: ply information, short knot value and ... + LONGLONG numStatesProcessed = 0; // + unsigned int maxNumBranches = 0; // maximum number of branches/moves + unsigned int depthOfFullTree = 0; // maxumim search depth + unsigned int curCalculatedLayer = 0; // id of the currently calculated layer + unsigned int curCalculationActionId = 0; // one of ... + bool layerInDatabase = false; // true if the current considered layer has already been calculated and stored in the database + void * pRootPossibilities = NULL; // pointer to the structure passed by getPossibilities() for the state at which getBestChoice() has been called + layerStatsStruct * layerStats = NULL; // array of size [] containing general layer information and the skv of all layers + plyInfoStruct * plyInfos = NULL; // array of size [] containing ply information + + // variables concerning the compression of the database + // compressorClass * compressor = NULL; + // unsigned int compressionAlgorithmnId = 0; // 0 or one of the COMPRESSOR_ALG_... constants + + // database io operations per second + long long numReadSkvOperations = 0; // number of read operations done since start of the programm + long long numWriteSkvOperations = 0; // number of write operations done since start of the programm + long long numReadPlyOperations = 0; // number of read operations done since start of the programm + long long numWritePlyOperations = 0; // number of write operations done since start of the programm + LARGE_INTEGER readSkvInterval; // time of interval for read operations + LARGE_INTEGER writeSkvInterval; // '' + LARGE_INTEGER readPlyInterval; // '' + LARGE_INTEGER writePlyInterval; // '' + LARGE_INTEGER frequency; // performance-counter frequency, in counts per second + + /*** private functions ***************************************************************************************************************************/ + + // database functions + void openSkvFile (const char *path, unsigned int maximumNumberOfBranches); + void openPlyInfoFile (const char *path); + bool calcLayer (unsigned int layerNumber); + void unloadPlyInfo (unsigned int layerNumber); + void unloadLayer (unsigned int layerNumber); + void saveHeader (skvFileHeaderStruct *dbH, layerStatsStruct *lStats); + void saveHeader (plyInfoFileHeaderStruct *piH, plyInfoStruct *pInfo); + void readKnotValueFromDatabase (unsigned int threadNo, unsigned int &layerNumber, unsigned int &stateNumber, twoBit &knotValue, bool &invalidLayerOrStateNumber, bool &layerInDatabaseAndCompleted); + void readKnotValueFromDatabase (unsigned int layerNumber, unsigned int stateNumber, twoBit &knotValue); + void readPlyInfoFromDatabase (unsigned int layerNumber, unsigned int stateNumber, plyInfoVarType &value); + void saveKnotValueInDatabase (unsigned int layerNumber, unsigned int stateNumber, twoBit knotValue); + void savePlyInfoInDatabase (unsigned int layerNumber, unsigned int stateNumber, plyInfoVarType value); + void loadBytesFromFile (HANDLE hFile, long long offset, unsigned int numBytes, void *pBytes); + void saveBytesToFile (HANDLE hFile, long long offset, unsigned int numBytes, void *pBytes); + void saveLayerToFile (unsigned int layerNumber); + inline void measureIops (long long &numOperations, LARGE_INTEGER &interval, LARGE_INTEGER &curTimeBefore, char text[]); + + // Testing functions + static DWORD testLayerThreadProc (void* pParameter, int index); + static DWORD testSetSituationThreadProc (void* pParameter, int index); + + // Alpha-Beta-Algorithmn + bool calcKnotValuesByAlphaBeta (unsigned int layerNumber); + bool initAlphaBeta (alphaBetaGlobalVars &retroVars); + bool runAlphaBeta (alphaBetaGlobalVars &retroVars); + void letTheTreeGrow (knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, float alpha, float beta); + bool alphaBetaTryDataBase (knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int &layerNumber, unsigned int &stateNumber); + void alphaBetaTryPossibilites (knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int *idPossibility, void *pPossibilities, unsigned int &maxWonfreqValuesSubMoves, float &alpha, float &beta); + void alphaBetaCalcPlyInfo (knotStruct *knot); + void alphaBetaCalcKnotValue (knotStruct *knot); + void alphaBetaChooseBestMove (knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int *idPossibility, unsigned int maxWonfreqValuesSubMoves); + void alphaBetaSaveInDatabase (unsigned int threadNo, unsigned int layerNumber, unsigned int stateNumber, twoBit knotValue, plyInfoVarType plyValue, bool invertValue); + static DWORD initAlphaBetaThreadProc (void* pParameter, int index); + static DWORD runAlphaBetaThreadProc (void* pParameter, int index); + + // Retro Analysis + bool calcKnotValuesByRetroAnalysis (vector &layersToCalculate); + bool initRetroAnalysis (retroAnalysisGlobalVars &retroVars); + bool prepareCountArrays (retroAnalysisGlobalVars &retroVars); + bool calcNumSuccedors (retroAnalysisGlobalVars &retroVars); + bool performRetroAnalysis (retroAnalysisGlobalVars &retroVars); + bool addStateToProcessQueue (retroAnalysisGlobalVars &retroVars, retroAnalysisThreadVars &threadVars, unsigned int plyNumber, stateAdressStruct* pState); + static bool retroAnalysisQueueStateComp (const retroAnalysisQueueState &a, const retroAnalysisQueueState &b) {return a.stateNumber < b.stateNumber; }; + static DWORD initRetroAnalysisThreadProc (void* pParameter, int index); + static DWORD addNumSuccedorsThreadProc (void* pParameter, int index); + static DWORD performRetroAnalysisThreadProc (void* pParameter); + + // Progress report functions + void showLayerStats (unsigned int layerNumber); + bool falseOrStop (); +}; + +#endif diff --git a/src/perfect/miniMaxWin.h b/src/perfect/miniMaxWin.h new file mode 100644 index 00000000..4c528cbf --- /dev/null +++ b/src/perfect/miniMaxWin.h @@ -0,0 +1,158 @@ +/********************************************************************* + miniMaxWin.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef MINIMAXWIN_H +#define MINIMAXWIN_H + +// Windows Header Files: +#include "miniMax\\miniMax.h" +#include +#include +#include +#include + +class miniMaxGuiField +{ +public: + virtual void setAlignment (wildWeasel::alignment& newAlignment) {}; + virtual void setVisibility (bool visible) {}; + virtual void setState (unsigned int curShowedLayer, miniMax::stateNumberVarType curShowedState) {}; +}; + +/*------------------------------------------------------------------------------------ + +| ------------------------------------- --------------------------------- | +| | | | | | +| | | | | | +| | pTreeViewInspect | | miniMaxGuiField | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| ------------------------------------- --------------------------------- | +| | +-----------------------------------------------------------------------------------*/ + +class miniMaxWinInspectDb +{ +protected: + + // General Variables + miniMax * pMiniMax = nullptr; // pointer to perfect KI class granting the access to the database + miniMaxGuiField* pGuiField = nullptr; + bool showingInspectionControls = false; + unsigned int curShowedLayer = 0; // current showed layer + miniMax::stateNumberVarType curShowedState = 0; // current showed state + const unsigned int scrollBarWidth = 20; + +public: + + // Constructor / destructor + miniMaxWinInspectDb (wildWeasel::masterMind* ww, miniMax* pMiniMax, wildWeasel::alignment& amInspectDb, wildWeasel::font2D* font, wildWeasel::texture* textureLine, miniMaxGuiField& guiField); + ~miniMaxWinInspectDb (); + + // Generals Functions + bool createControls (); + bool showControls (bool visible); + void resize (wildWeasel::alignment &rcNewArea); +}; + +/*------------------------------------------------------------------------------------ +| ----------------------------------------------------------------------------- | +| | | | +| | | | +| | hListViewLayer | | +| | | | +| | | | +| | | | +| | | | +| ----------------------------------------------------------------------------- | +| | +| ------------------------------------- --------------------------------- | +| | | | | | +| | | | hEditOutputBox | | +| | hListViewArray | | | | +| | | | | | +| | | | | | +| | | | | | +| | | | | | +| ------------------------------------- --------------------------------- | +| | +| hLabelCalculationRunning hLabelCalculatingLayer hLabelCalculationAction | +| | +| ------------------- ----------------- ---------------- --------------- | +| hButtonCalcContinue hButtonCalcCancel hButtonCalcPause hButtonCalcTest | +| ------------------- ----------------- ---------------- --------------- | +-----------------------------------------------------------------------------------*/ + +class miniMaxWinCalcDb +{ +protected: + + // Calculation variables + wildWeasel::masterMind * ww = nullptr; // pointer to engine + miniMax * pMiniMax = nullptr; // pointer to perfect KI class granting the access to the database + ostream * outputStream = nullptr; // pointer to a stream for the console output of the calculation done by the class miniMax + stringbuf outputStringBuf; // buffer linked to the stream, for reading out of the stream into the buffer + locale myLocale; // for formatting the output + queue layersToTest; // layer numbers to be tested + thread hThreadSolve; + thread hThreadTestLayer; + bool showingCalculationControls = false; + bool threadSolveIsRunning = false; + bool threadTestLayerIsRunning = false; + condition_variable threadConditionVariable; + mutex threadMutex; + + // positions, metrics, sizes, dimensions + unsigned int listViewRowHeight = 20; // height in pixel of a single row + const float defPixelDist = 15; // + const float labelHeight = 30; // + const float buttonHeight = 30; // + + + + // Calculation Functions + void buttonFuncCalcStartOrContinue (void* pUser); + void buttonFuncCalcCancel (void* pUser); + void buttonFuncCalcPause (void* pUser); + void buttonFuncCalcTest (); + void buttonFuncCalcTestAll (void* pUser); + void buttonFuncCalcTestLayer (void* pUser); + void lvSelectedLayerChanged (unsigned int row, unsigned int col, wildWeasel::guiElemEvFol* guiElem, void* pUser); + static void updateOutputControls (void* pUser); + void updateListItemLayer (unsigned int layerNumber); + void updateListItemArray (miniMax::arrayInfoChange infoChange); + void threadSolve (); + void threadProcTestLayer (); + +public: + + // Constructor / destructor + miniMaxWinCalcDb (wildWeasel::masterMind* ww, miniMax* pMiniMax, wildWeasel::alignment& amCalculation, wildWeasel::font2D* font, wildWeasel::texture* textureLine); + ~miniMaxWinCalcDb (); + + // Generals Functions + bool createControls (); + void resize (wildWeasel::alignment &amNewArea); + bool showControls (bool visible); + bool isCalculationOngoing (); + miniMax * getMinimaxPointer () { return pMiniMax; }; + CRITICAL_SECTION * getCriticalSectionOutput () { return &pMiniMax->csOsPrint; }; +}; + +#endif \ No newline at end of file diff --git a/src/perfect/miniMax_alphaBetaAlgorithmn.cpp b/src/perfect/miniMax_alphaBetaAlgorithmn.cpp new file mode 100644 index 00000000..1882eba9 --- /dev/null +++ b/src/perfect/miniMax_alphaBetaAlgorithmn.cpp @@ -0,0 +1,669 @@ +/********************************************************************* + miniMax_alphaBetaAlgorithmn.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: calcKnotValuesByAlphaBeta() +// Desc: return value is true if calculation is stopped either by user or by an error +//----------------------------------------------------------------------------- +bool miniMax::calcKnotValuesByAlphaBeta(unsigned int layerNumber) +{ + // locals + alphaBetaGlobalVars alphaBetaVars(this, layerNumber); // multi-thread vars + + // Version 10: + PRINT(1, this, "*** Calculate layer " << layerNumber << " by alpha-beta-algorithmn ***" << endl); + curCalculationActionId = MM_ACTION_PERFORM_ALPHA_BETA; + + // initialization + PRINT(2, this, " Bytes in memory: " << memoryUsed2 << endl); + if (!initAlphaBeta(alphaBetaVars)) { return false; } + + // run alpha-beta algorithmn + PRINT(2, this, " Bytes in memory: " << memoryUsed2 << endl); + if (!runAlphaBeta(alphaBetaVars)) { return false; } + + // update layerStats[].numWonStates, etc. + PRINT(2, this, " Bytes in memory: " << memoryUsed2 << endl); + showLayerStats(layerNumber); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: saveKnotValueInDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::alphaBetaSaveInDatabase(unsigned int threadNo, unsigned int layerNumber, unsigned int stateNumber, twoBit knotValue, plyInfoVarType plyValue, bool invertValue) +{ + // locals + unsigned int * symStateNumbers = NULL; + unsigned int numSymmetricStates; + unsigned int sysStateNumber; + unsigned int i; + + // invert value ? + if (knotValue > SKV_VALUE_GAME_WON) while (true); + if (invertValue) knotValue = skvPerspectiveMatrix[knotValue][PL_TO_MOVE_UNCHANGED]; + + // get numbers of symmetric states + getSymStateNumWithDoubles(threadNo, &numSymmetricStates, &symStateNumbers); + + // save + saveKnotValueInDatabase(layerNumber, stateNumber, knotValue); + savePlyInfoInDatabase (layerNumber, stateNumber, plyValue); + + // save value for all symmetric states + for (i=0; igetFileSize() == (LONGLONG) layerStats[alphaBetaVars.layerNumber].knotsInLayer) { + PRINT(2, this, " Loading invalid states from file: " << ssInvArrayFilePath.str()); + initAlreadyDone = true; + } + + // prepare parameters + numStatesProcessed = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_WON ] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_LOST ] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_DRAWN] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_INVALID ] = 0; + threadManagerClass::threadVarsArray tva(threadManager.getNumThreads(), initAlphaBetaVars(this, &alphaBetaVars, alphaBetaVars.layerNumber, invalidArray, initAlreadyDone)); + + // process each state in the current layer + switch (threadManager.executeParallelLoop(initAlphaBetaThreadProc, tva.getPointerToArray(), tva.getSizeOfArray(), TM_SCHEDULE_STATIC, 0, layerStats[alphaBetaVars.layerNumber].knotsInLayer - 1, 1)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + SAFE_DELETE(invalidArray); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + PRINT(0,this, "\n****************************************\nMain thread: Invalid or unexpected param!\n****************************************\n"); + return falseOrStop(); + } + + // reduce and delete thread specific data + tva.reduce(); + if (numStatesProcessed < layerStats[alphaBetaVars.layerNumber].knotsInLayer) { + SAFE_DELETE(invalidArray); + return falseOrStop(); + } + invalidArray->flushBuffers(); + SAFE_DELETE(invalidArray); + + // when init file was created new then save it now + PRINT(2, this, " Saved initialized states to file: " << ssInvArrayFilePath.str()); + + // show statistics + PRINT(2, this, " won states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_WON ]); + PRINT(2, this, " lost states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_LOST ]); + PRINT(2, this, " draw states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_DRAWN]); + PRINT(2, this, " invalid states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_INVALID ]); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: initAlphaBetaThreadProc() +// Desc: set short knot value to SKV_VALUE_INVALID, ply info to PLYINFO_VALUE_INVALID and knotAlreadyCalculated to true or false, whether setSituation() returns true or false +//----------------------------------------------------------------------------- +DWORD miniMax::initAlphaBetaThreadProc(void* pParameter, int index) +{ + // locals + initAlphaBetaVars * iabVars = (initAlphaBetaVars *) pParameter; + miniMax * m = iabVars->pMiniMax; + float floatValue; // dummy variable for calls of getValueOfSituation() + stateAdressStruct curState; // current state counter for loops + twoBit curStateValue = 0; // for calls of getValueOfSituation() + plyInfoVarType plyInfo; // depends on the curStateValue + + curState.layerNumber = iabVars->layerNumber; + curState.stateNumber = index; + iabVars->statesProcessed++; + + // print status + if (iabVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(2, m, "Already initialized " << m->numStatesProcessed << " of " << m->layerStats[curState.layerNumber].knotsInLayer << " states"); + } + + // layer initialization already done ? if so, then read from file + if (iabVars->initAlreadyDone) { + if (!iabVars->bufferedFile->readBytes(iabVars->curThreadNo, index * sizeof(twoBit), sizeof(twoBit), (unsigned char*) &curStateValue)) { + PRINT(0, m, "ERROR: initArray->takeBytes() failed"); + return m->falseOrStop(); + } + // initialization not done + } else { + // set current selected situation + if (!m->setSituation(iabVars->curThreadNo, curState.layerNumber, curState.stateNumber)) { + curStateValue = SKV_VALUE_INVALID; + } else { + // get value of current situation + m->getValueOfSituation(iabVars->curThreadNo, floatValue, curStateValue); + } + } + + // calc ply info + if (curStateValue == SKV_VALUE_GAME_WON || curStateValue == SKV_VALUE_GAME_LOST) { + plyInfo = 0; + } else if (curStateValue == SKV_VALUE_INVALID) { + plyInfo = PLYINFO_VALUE_INVALID; + } else { + plyInfo = PLYINFO_VALUE_UNCALCULATED; + } + + // save short knot value & ply info (m->alphaBetaSaveInDatabase(iabVars->curThreadNo, curStateValue, plyInfo, false); ???) + m->saveKnotValueInDatabase(curState.layerNumber, curState.stateNumber, curStateValue); + m->savePlyInfoInDatabase (curState.layerNumber, curState.stateNumber, plyInfo); + + // write data to file + if (!iabVars->initAlreadyDone) { + if (!iabVars->bufferedFile->writeBytes(iabVars->curThreadNo, index * sizeof(twoBit), sizeof(twoBit), (unsigned char*) &curStateValue)) { + PRINT(0, m, "ERROR: bufferedFile->writeBytes failed!"); + return m->falseOrStop(); + } + } + iabVars->statsValueCounter[curStateValue]++; + + return TM_RETURN_VALUE_OK; +} + +//----------------------------------------------------------------------------- +// Name: runAlphaBeta() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::runAlphaBeta(alphaBetaGlobalVars &alphaBetaVars) +{ + // prepare parameters + PRINT(1, this, " Calculate layer " << alphaBetaVars.layerNumber << " with function letTheTreeGrow():"); + numStatesProcessed = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_WON ] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_LOST ] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_DRAWN] = 0; + alphaBetaVars.statsValueCounter[SKV_VALUE_INVALID ] = 0; + threadManagerClass::threadVarsArray tva(threadManager.getNumThreads(), runAlphaBetaVars(this, &alphaBetaVars, alphaBetaVars.layerNumber)); + + // so far no multi-threadin implemented + threadManager.setNumThreads(1); + + // process each state in the current layer + switch (threadManager.executeParallelLoop(runAlphaBetaThreadProc, tva.getPointerToArray(), tva.getSizeOfArray(), TM_SCHEDULE_STATIC, 0, layerStats[alphaBetaVars.layerNumber].knotsInLayer - 1, 1)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + return falseOrStop(); + } + threadManager.setNumThreads(4); + + // reduce and delete thread specific data + tva.reduce(); + if (numStatesProcessed < layerStats[alphaBetaVars.layerNumber].knotsInLayer) return falseOrStop(); + + // show statistics + PRINT(2, this, " won states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_WON ]); + PRINT(2, this, " lost states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_LOST ]); + PRINT(2, this, " draw states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_GAME_DRAWN]); + PRINT(2, this, " invalid states: " << alphaBetaVars.statsValueCounter[SKV_VALUE_INVALID ]); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: runAlphaBetaThreadProc() +// Desc: +//----------------------------------------------------------------------------- +DWORD miniMax::runAlphaBetaThreadProc(void* pParameter, int index) +{ + // locals + runAlphaBetaVars * rabVars = (runAlphaBetaVars *) pParameter; + miniMax * m = rabVars->pMiniMax; + stateAdressStruct curState; // current state counter for loops + knotStruct root; // + plyInfoVarType plyInfo; // depends on the curStateValue + + curState.layerNumber = rabVars->layerNumber; + curState.stateNumber = index; + rabVars->statesProcessed++; + + // print status + if (rabVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(2, m, " Processed " << m->numStatesProcessed << " of " << m->layerStats[curState.layerNumber].knotsInLayer << " states"); + } + + // Version 10: state already calculated? if so leave. + m->readPlyInfoFromDatabase(curState.layerNumber, curState.stateNumber, plyInfo); + if (plyInfo != PLYINFO_VALUE_UNCALCULATED) return TM_RETURN_VALUE_OK; + + // set current selected situation + if (m->setSituation(rabVars->curThreadNo, curState.layerNumber, curState.stateNumber)) { + + // calc value of situation + m->letTheTreeGrow(&root, rabVars, m->depthOfFullTree, SKV_VALUE_GAME_LOST, SKV_VALUE_GAME_WON); + + } else { + // should not occur, because already tested by plyInfo == PLYINFO_VALUE_UNCALCULATED + MessageBoxW(NULL, L"This event should never occur. if (!m->setSituation())", L"ERROR", MB_OK); + } + return TM_RETURN_VALUE_OK; +} + +//----------------------------------------------------------------------------- +// Name: letTheTreeGrow() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::letTheTreeGrow(knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, float alpha, float beta) +{ + // Locals + void *pPossibilities; + unsigned int *idPossibility; + unsigned int layerNumber = 0; // layer number of current state + unsigned int stateNumber = 0; // state number of current state + unsigned int maxWonfreqValuesSubMoves = 0; + + // standard values + knot->branches = &rabVars->branchArray[(depthOfFullTree - tilLevel) * maxNumBranches]; + knot->numPossibilities = 0; + knot->bestBranch = 0; + knot->bestMoveId = 0; + knot->isOpponentLevel = getOpponentLevel(rabVars->curThreadNo); + knot->plyInfo = PLYINFO_VALUE_UNCALCULATED; + knot->shortValue = SKV_VALUE_GAME_DRAWN; + knot->floatValue = (float) knot->shortValue; + + // evaluate situation, musn't occur while calculating database + if (tilLevel == 0) { + if (calcDatabase) { + // if tilLevel is equal zero it means that memory is gone out, since each recursive step needs memory + PRINT(0, this, "ERROR: tilLevel == 0"); + knot->shortValue = SKV_VALUE_INVALID; + knot->plyInfo = PLYINFO_VALUE_INVALID; + knot->floatValue = (float) knot->shortValue; + falseOrStop(); + } else { + getValueOfSituation(rabVars->curThreadNo, knot->floatValue, knot->shortValue); + } + // investigate branches + } else { + + // get layer and state number of current state and look if short knot value can be found in database or in an array + if (alphaBetaTryDataBase(knot, rabVars, tilLevel, layerNumber, stateNumber)) return; + + // get number of possiblities + idPossibility = getPossibilities(rabVars->curThreadNo, &knot->numPossibilities, &knot->isOpponentLevel, &pPossibilities); + + // unable to move + if (knot->numPossibilities == 0) { + + // if unable to move a final state is reached + knot->plyInfo = 0; + getValueOfSituation(rabVars->curThreadNo, knot->floatValue, knot->shortValue); + if (tilLevel == depthOfFullTree - 1) rabVars->freqValuesSubMoves[knot->shortValue]++; + + // if unable to move an invalid state was reached if nobody has won + if (calcDatabase && knot->shortValue == SKV_VALUE_GAME_DRAWN) { + knot->shortValue = SKV_VALUE_INVALID; + knot->plyInfo = PLYINFO_VALUE_INVALID; + knot->floatValue = (float) knot->shortValue; + } + + // movement is possible + } else { + + // move, letTreeGroe, undo + alphaBetaTryPossibilites(knot, rabVars, tilLevel, idPossibility, pPossibilities, maxWonfreqValuesSubMoves, alpha, beta); + + // calculate value of knot - its the value of the best branch + alphaBetaCalcKnotValue(knot); + + // calc ply info + alphaBetaCalcPlyInfo(knot); + + // select randomly one of the best moves, if they are equivalent + alphaBetaChooseBestMove(knot, rabVars, tilLevel, idPossibility, maxWonfreqValuesSubMoves); + } + + // save value and best branch into database and set value as valid + if (calcDatabase && hFileShortKnotValues && hFilePlyInfo) alphaBetaSaveInDatabase(rabVars->curThreadNo, layerNumber, stateNumber, knot->shortValue, knot->plyInfo, knot->isOpponentLevel); + } +} + +//----------------------------------------------------------------------------- +// Name: alphaBetaTryDataBase() +// Desc: +// 1 - Determines layerNumber and stateNumber for the given game situation. +// 2 - Look into database if knot value and ply info are already calculated. If so sets knot->shortValue, knot->floatValue and knot->plyInfo. +// CAUTION: knot->isOpponentLevel must be set and valid. +//----------------------------------------------------------------------------- +bool miniMax::alphaBetaTryDataBase(knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int &layerNumber, unsigned int &stateNumber) +{ + // locals + bool invalidLayerOrStateNumber; + bool subLayerInDatabaseAndCompleted; + twoBit shortKnotValue = SKV_VALUE_INVALID; + plyInfoVarType plyInfo = PLYINFO_VALUE_UNCALCULATED; + + // use database ? + if (hFilePlyInfo != NULL && hFileShortKnotValues != NULL && (calcDatabase || layerInDatabase)) { + + // situation already existend in database ? + readKnotValueFromDatabase(rabVars->curThreadNo, layerNumber, stateNumber, shortKnotValue, invalidLayerOrStateNumber, subLayerInDatabaseAndCompleted); + readPlyInfoFromDatabase(layerNumber, stateNumber, plyInfo); + + // it was possible to achieve an invalid state using move(), + // so the original state was an invalid one + if ((tilLevel < depthOfFullTree && invalidLayerOrStateNumber) + || (tilLevel < depthOfFullTree && shortKnotValue == SKV_VALUE_INVALID && subLayerInDatabaseAndCompleted) + || (tilLevel < depthOfFullTree && shortKnotValue == SKV_VALUE_INVALID && plyInfo != PLYINFO_VALUE_UNCALCULATED)) { // version 22: replaced: curCalculatedLayer == layerNumber && knot->plyInfo != PLYINFO_VALUE_UNCALCULATED)) { + knot->shortValue = SKV_VALUE_INVALID; + knot->plyInfo = PLYINFO_VALUE_INVALID; + knot->floatValue = (float) knot->shortValue; + return true; + } + + // print out put, if not calculating database, but requesting a knot value + if (shortKnotValue != SKV_VALUE_INVALID && tilLevel == depthOfFullTree && !calcDatabase && subLayerInDatabaseAndCompleted) { + PRINT(2, this, "This state is marked as " << ((shortKnotValue == SKV_VALUE_GAME_WON) ? "WON" : ((shortKnotValue == SKV_VALUE_GAME_LOST) ? "LOST" : ((shortKnotValue == SKV_VALUE_GAME_DRAWN) ? "DRAW" : "INVALID"))) << endl); + } + + // when knot value is valid then return best branch + if (calcDatabase && tilLevel < depthOfFullTree && shortKnotValue != SKV_VALUE_INVALID && plyInfo != PLYINFO_VALUE_UNCALCULATED + || !calcDatabase && tilLevel < depthOfFullTree - 1 && shortKnotValue != SKV_VALUE_INVALID) { + + // switch if is not opponent level + if (knot->isOpponentLevel) knot->shortValue = skvPerspectiveMatrix[shortKnotValue][PL_TO_MOVE_UNCHANGED]; + else knot->shortValue = skvPerspectiveMatrix[shortKnotValue][PL_TO_MOVE_CHANGED ]; + knot->floatValue = (float) knot->shortValue; + knot->plyInfo = plyInfo; + return true; + } + } + return false; +} + +//----------------------------------------------------------------------------- +// Name: alphaBetaTryPossibilites() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::alphaBetaTryPossibilites(knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int *idPossibility, void *pPossibilities, unsigned int &maxWonfreqValuesSubMoves, float &alpha, float &beta) +{ + // locals + void * pBackup; + unsigned int curPoss; + + for (curPoss=0; curPossnumPossibilities; curPoss++) { + + // output + if (tilLevel == depthOfFullTree && !calcDatabase) { + printMoveInformation(rabVars->curThreadNo, idPossibility[curPoss], pPossibilities); + rabVars->freqValuesSubMoves[SKV_VALUE_INVALID ] = 0; + rabVars->freqValuesSubMoves[SKV_VALUE_GAME_LOST ] = 0; + rabVars->freqValuesSubMoves[SKV_VALUE_GAME_DRAWN] = 0; + rabVars->freqValuesSubMoves[SKV_VALUE_GAME_WON ] = 0; + } + + // move + move(rabVars->curThreadNo, idPossibility[curPoss], knot->isOpponentLevel, &pBackup, pPossibilities); + + // recursive call + letTheTreeGrow(&knot->branches[curPoss], rabVars, tilLevel - 1, alpha, beta); + + // undo move + undo(rabVars->curThreadNo, idPossibility[curPoss], knot->isOpponentLevel, pBackup, pPossibilities); + + // output + if (tilLevel == depthOfFullTree && !calcDatabase) { + rabVars->freqValuesSubMovesBranchWon[curPoss] = rabVars->freqValuesSubMoves[SKV_VALUE_GAME_WON]; + if (rabVars->freqValuesSubMoves[SKV_VALUE_GAME_WON] > maxWonfreqValuesSubMoves && knot->branches[curPoss].shortValue == SKV_VALUE_GAME_DRAWN) { + maxWonfreqValuesSubMoves = rabVars->freqValuesSubMoves[SKV_VALUE_GAME_WON]; + } + if (hFileShortKnotValues != NULL && layerInDatabase) { + storeValueOfMove(rabVars->curThreadNo, idPossibility[curPoss], pPossibilities, knot->branches[curPoss].shortValue, rabVars->freqValuesSubMoves, knot->branches[curPoss].plyInfo); + PRINT(0, this, "\t: " << ((knot->branches[curPoss].shortValue == SKV_VALUE_GAME_WON) ? "WON" : ((knot->branches[curPoss].shortValue == SKV_VALUE_GAME_LOST) ? "LOST" : ((knot->branches[curPoss].shortValue == SKV_VALUE_GAME_DRAWN) ? "DRAW" : "INVALID"))) << endl); + } else { + PRINT(0, this, "\t: " << knot->branches[curPoss].floatValue << endl); + } + } else if (tilLevel == depthOfFullTree - 1 && !calcDatabase) { + rabVars->freqValuesSubMoves[knot->branches[curPoss].shortValue]++; + } + + // don't use alpha beta if using database + if (hFileShortKnotValues != NULL && calcDatabase) continue; + if (hFileShortKnotValues != NULL && tilLevel + 1 >= depthOfFullTree) continue; + + // alpha beta algorithmn + if (!knot->isOpponentLevel) { + if (knot->branches[curPoss].floatValue >= beta ) { + knot->numPossibilities = curPoss + 1; + break; + } else if (knot->branches[curPoss].floatValue > alpha) { + alpha = knot->branches[curPoss].floatValue; + } + } else { + if (knot->branches[curPoss].floatValue <= alpha) { + knot->numPossibilities = curPoss + 1; + break; + } else if (knot->branches[curPoss].floatValue < beta ) { + beta = knot->branches[curPoss].floatValue; + } + } + } + + // let delete pPossibilities + if (tilLevel < depthOfFullTree) { + deletePossibilities(rabVars->curThreadNo, pPossibilities); + } else { + pRootPossibilities = pPossibilities; + } +} + +//----------------------------------------------------------------------------- +// Name: alphaBetaCalcKnotValue() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::alphaBetaCalcKnotValue(knotStruct *knot) +{ + // locals + float maxValue = knot->branches[0].floatValue; + unsigned int maxBranch = 0; + unsigned int i; + + // opponent tries to minimize the value + if (knot->isOpponentLevel) { + for (i=1; inumPossibilities; i++) { + // version 21: it should be impossible that knot->shortValue is equal SKV_VALUE_INVALID + if (/*knot->shortValue != SKV_VALUE_INVALID && */knot->branches[i].floatValue < maxValue) { + maxValue = knot->branches[i].floatValue; + maxBranch = i; + } + } + // maximize the value + } else { + for (i=1; inumPossibilities; i++) { + if (/*knot->shortValue != SKV_VALUE_INVALID && */knot->branches[i].floatValue > maxValue) { + maxValue = knot->branches[i].floatValue; + maxBranch = i; + } + } + } + + // set value + knot->floatValue = knot->branches[maxBranch].floatValue; + knot->shortValue = knot->branches[maxBranch].shortValue; +} + +//----------------------------------------------------------------------------- +// Name: alphaBetaCalcPlyInfo() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::alphaBetaCalcPlyInfo(knotStruct *knot) +{ + // locals + unsigned int i; + unsigned int maxBranch; + plyInfoVarType maxPlyInfo; + twoBit shortKnotValue; + + // + if (knot->shortValue == SKV_VALUE_GAME_DRAWN) { + knot->plyInfo = PLYINFO_VALUE_DRAWN; + } else if (knot->shortValue == SKV_VALUE_INVALID) { + knot->plyInfo = PLYINFO_VALUE_INVALID; + } else { + + // calculate value of knot + shortKnotValue = (knot->isOpponentLevel) ? skvPerspectiveMatrix[knot->shortValue][PL_TO_MOVE_UNCHANGED] : knot->shortValue; + maxPlyInfo = (shortKnotValue == SKV_VALUE_GAME_WON) ? PLYINFO_VALUE_DRAWN : 0; + maxBranch = 0; + + // when current knot is a won state + if (shortKnotValue == SKV_VALUE_GAME_WON) { + + for (i=0; inumPossibilities; i++) { + + // invert knot value if necessary + shortKnotValue = (knot->branches[i].isOpponentLevel) ? skvPerspectiveMatrix[knot->branches[i].shortValue][PL_TO_MOVE_UNCHANGED] : knot->branches[i].shortValue; + + // take the minimum of the lost states (negative float values) + if ((knot->branches[i].plyInfo < maxPlyInfo && shortKnotValue == SKV_VALUE_GAME_LOST && knot->isOpponentLevel != knot->branches[i].isOpponentLevel) + + // after this move the same player will continue, so take the minimum of the won states + || (knot->branches[i].plyInfo < maxPlyInfo && shortKnotValue == SKV_VALUE_GAME_WON && knot->isOpponentLevel == knot->branches[i].isOpponentLevel)) { + + maxPlyInfo = knot->branches[i].plyInfo; + maxBranch = i; + } + } + + // current state is a lost state + } else { + + for (i=0; inumPossibilities; i++) { + + // invert knot value if necessary + shortKnotValue = (knot->branches[i].isOpponentLevel) ? skvPerspectiveMatrix[knot->branches[i].shortValue][PL_TO_MOVE_UNCHANGED] : knot->branches[i].shortValue; + + // after this move the same player will continue, so take the maximum of the lost states (negative float values) + if ((knot->branches[i].plyInfo > maxPlyInfo && shortKnotValue == SKV_VALUE_GAME_WON && knot->isOpponentLevel != knot->branches[i].isOpponentLevel) + + // take the maximum of the won states, since that's the longest path + || (knot->branches[i].plyInfo > maxPlyInfo && shortKnotValue == SKV_VALUE_GAME_LOST && knot->isOpponentLevel == knot->branches[i].isOpponentLevel)) { + + maxPlyInfo = knot->branches[i].plyInfo; + maxBranch = i; + } + } + } + + // set value + knot->plyInfo = knot->branches[maxBranch].plyInfo + 1; + } +} + +//----------------------------------------------------------------------------- +// Name: alphaBetaChooseBestMove() +// Desc: select randomly one of the best moves, if they are equivalent +//----------------------------------------------------------------------------- +void miniMax::alphaBetaChooseBestMove(knotStruct *knot, runAlphaBetaVars *rabVars, unsigned int tilLevel, unsigned int *idPossibility, unsigned int maxWonfreqValuesSubMoves) +{ + // locals + float dif; + unsigned int numBestChoices = 0; + unsigned int *bestBranches = new unsigned int[maxNumBranches]; + unsigned int i; + unsigned int maxBranch; + + // select randomly one of the best moves, if they are equivalent + if (tilLevel == depthOfFullTree && !calcDatabase) { + + // check every possible move + for (numBestChoices=0, i=0; inumPossibilities; i++) { + + // use information in database + if (layerInDatabase && hFileShortKnotValues != NULL) { + + // selected move with equal knot value + if (knot->branches[i].shortValue == knot->shortValue) { + + // best move lead to drawn state + if (knot->shortValue == SKV_VALUE_GAME_DRAWN) { + if (maxWonfreqValuesSubMoves == rabVars->freqValuesSubMovesBranchWon[i]) { + bestBranches[numBestChoices] = i; + numBestChoices++; + } + + // best move lead to lost or won state + } else { + if (knot->plyInfo == knot->branches[i].plyInfo + 1) { + bestBranches[numBestChoices] = i; + numBestChoices++; + } + } + } + // conventionell mini-max algorithm + } else { + dif = knot->branches[i].floatValue - knot->floatValue; + dif = (dif > 0) ? dif : -1.0f * dif; + if (dif < FPKV_THRESHOLD) { + bestBranches[numBestChoices] = i; + numBestChoices++; + } + } + } + } + + // set value + maxBranch = (numBestChoices ? bestBranches[rand() % numBestChoices] : 0); + knot->bestMoveId = idPossibility[maxBranch]; + knot->bestBranch = maxBranch; + SAFE_DELETE_ARRAY(bestBranches); +} diff --git a/src/perfect/miniMax_database.cpp b/src/perfect/miniMax_database.cpp new file mode 100644 index 00000000..9ac399ac --- /dev/null +++ b/src/perfect/miniMax_database.cpp @@ -0,0 +1,673 @@ +/********************************************************************* + miniMax_database.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: ~miniMax() +// Desc: miniMax class destructor +//----------------------------------------------------------------------------- +void miniMax::closeDatabase() +{ + // close database + if (hFileShortKnotValues != NULL) { + unloadAllLayers(); + SAFE_DELETE_ARRAY(layerStats); + CloseHandle(hFileShortKnotValues); + hFileShortKnotValues = NULL; + } + + // close ply information file + if (hFilePlyInfo != NULL) { + unloadAllPlyInfos(); + SAFE_DELETE_ARRAY(plyInfos); + CloseHandle(hFilePlyInfo); + hFilePlyInfo = NULL; + } +} + +//----------------------------------------------------------------------------- +// Name: unloadPlyInfo() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::unloadPlyInfo(unsigned int layerNumber) +{ + plyInfoStruct * myPis = &plyInfos[layerNumber]; + memoryUsed2 -= myPis->sizeInBytes; + arrayInfos.removeArray(layerNumber, arrayInfoStruct::arrayType_plyInfos, myPis->sizeInBytes, 0); + SAFE_DELETE_ARRAY(myPis->plyInfo); + myPis->plyInfoIsLoaded = false; +} + +//----------------------------------------------------------------------------- +// Name: unloadLayer() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::unloadLayer(unsigned int layerNumber) +{ + layerStatsStruct * myLss = &layerStats[layerNumber]; + SAFE_DELETE_ARRAY(myLss->shortKnotValueByte); + memoryUsed2 -= myLss->sizeInBytes; + arrayInfos.removeArray(layerNumber, arrayInfoStruct::arrayType_layerStats, myLss->sizeInBytes, 0); + myLss->layerIsLoaded = false; +} + +//----------------------------------------------------------------------------- +// Name: unloadAllPlyInfos() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::unloadAllPlyInfos() +{ + for (unsigned int i=0; i 0) { + if (WriteFile(hFile, myPointer, restingBytes, &dwBytesWritten, NULL) == TRUE) { + restingBytes -= dwBytesWritten; + myPointer = (void*) (((unsigned char*) myPointer) + dwBytesWritten); + if (restingBytes > 0) PRINT(2, this, "Still " << restingBytes << " to write!"); + } else { + if (!errorPrint) PRINT(0, this, "ERROR: WriteFile Failed!"); + errorPrint = true; + } + } +} + +//----------------------------------------------------------------------------- +// Name: loadBytesFromFile() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::loadBytesFromFile(HANDLE hFile, long long offset, unsigned int numBytes, void *pBytes) +{ + DWORD dwBytesRead; + LARGE_INTEGER liDistanceToMove; + unsigned int restingBytes = numBytes; + void * myPointer = pBytes; + bool errorPrint = false; + + liDistanceToMove.QuadPart = offset; + + while (errorPrint = !SetFilePointerEx(hFile, liDistanceToMove, NULL, FILE_BEGIN)) { + if (!errorPrint) PRINT(0, this, "ERROR: SetFilePointerEx failed!"); + } + + while (restingBytes > 0) { + if (ReadFile(hFile, pBytes, restingBytes, &dwBytesRead, NULL) == TRUE) { + restingBytes -= dwBytesRead; + myPointer = (void*) (((unsigned char*) myPointer) + dwBytesRead); + if (restingBytes > 0) { PRINT(2, this, "Still " << restingBytes << " bytes to read!"); } + } else { + if (!errorPrint) PRINT(0, this, "ERROR: ReadFile Failed!"); + errorPrint = true; + } + } +} + +//----------------------------------------------------------------------------- +// Name: isCurrentStateInDatabase() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::isCurrentStateInDatabase(unsigned int threadNo) +{ + unsigned int layerNum, stateNumber; + + if (hFileShortKnotValues == NULL) { + return false; + } else { + getLayerAndStateNumber(threadNo, layerNum, stateNumber); + return layerStats[layerNum].layerIsCompletedAndInFile; + } +} + +//----------------------------------------------------------------------------- +// Name: saveHeader() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::saveHeader(skvFileHeaderStruct *dbH, layerStatsStruct *lStats) +{ + DWORD dwBytesWritten; + SetFilePointer(hFileShortKnotValues, 0, NULL, FILE_BEGIN); + WriteFile(hFileShortKnotValues, dbH, sizeof(skvFileHeaderStruct), &dwBytesWritten, NULL); + WriteFile(hFileShortKnotValues, lStats, sizeof(layerStatsStruct) * dbH->numLayers, &dwBytesWritten, NULL); +} + +//----------------------------------------------------------------------------- +// Name: saveHeader() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::saveHeader(plyInfoFileHeaderStruct *piH, plyInfoStruct *pInfo) +{ + DWORD dwBytesWritten; + SetFilePointer(hFilePlyInfo, 0, NULL, FILE_BEGIN); + WriteFile(hFilePlyInfo, piH, sizeof(plyInfoFileHeaderStruct), &dwBytesWritten, NULL); + WriteFile(hFilePlyInfo, pInfo, sizeof(plyInfoStruct) * piH->numLayers, &dwBytesWritten, NULL); +} + +//----------------------------------------------------------------------------- +// Name: openDatabase() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::openDatabase(const char *directory, unsigned int maximumNumberOfBranches) +{ + if (strlen(directory) && !PathFileExistsA(directory)) { + PRINT(0, this, "ERROR: Database path " << directory << " not valid!"); + return falseOrStop(); + } + openSkvFile (directory, maximumNumberOfBranches); + openPlyInfoFile (directory); + return true; +} + +//----------------------------------------------------------------------------- +// Name: openSkvFile() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::openSkvFile(const char *directory, unsigned int maximumNumberOfBranches) +{ + // locals + stringstream ssDatabaseFile; + DWORD dwBytesRead; + unsigned int i; + + // don't open file twice + if (hFileShortKnotValues != NULL) return; + + // remember directory name + fileDirectory.assign(directory); + ssDatabaseFile << fileDirectory << (strlen(directory)?"\\":"") << "shortKnotValue.dat"; + PRINT(2, this, "Open short knot value file: " << fileDirectory << (strlen(directory)?"\\":"") << "shortKnotValue.dat" << endl); + + // Open Database-File (FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_RANDOM_ACCESS) + hFileShortKnotValues = CreateFileA(ssDatabaseFile.str().c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + + // opened file succesfully + if (hFileShortKnotValues == INVALID_HANDLE_VALUE) { + hFileShortKnotValues = NULL; + return; + } + + // set header to invalid + skvfHeader.headerCode = 0; + maxNumBranches = maximumNumberOfBranches; + + // database complete ? + ReadFile(hFileShortKnotValues, &skvfHeader, sizeof(skvFileHeaderStruct), &dwBytesRead, NULL); + + // invalid file ? + if (dwBytesRead != sizeof(skvFileHeaderStruct) || skvfHeader.headerCode != SKV_FILE_HEADER_CODE) { + + // create default header + skvfHeader.completed = false; + skvfHeader.numLayers = getNumberOfLayers(); + skvfHeader.headerCode = SKV_FILE_HEADER_CODE; + skvfHeader.headerAndStatsSize = sizeof(layerStatsStruct) * skvfHeader.numLayers + sizeof(skvFileHeaderStruct); + layerStats = new layerStatsStruct[skvfHeader.numLayers]; + layerStats[0].layerOffset = 0; + + for (i=0; isizeInBytes) { + + // short knot values & ply info + curCalculationActionId = MM_ACTION_SAVING_LAYER_TO_FILE; + saveBytesToFile(hFileShortKnotValues, skvfHeader.headerAndStatsSize + myLss->layerOffset, myLss->sizeInBytes, myLss->shortKnotValueByte); + saveBytesToFile(hFilePlyInfo, plyInfoHeader.headerAndPlyInfosSize + myPis->layerOffset, myPis->sizeInBytes, myPis->plyInfo); + } + + // mark layer as completed + myLss->layerIsCompletedAndInFile = true; + myPis->plyInfoIsCompletedAndInFile = true; +} + +//----------------------------------------------------------------------------- +// Name: measureIops() +// Desc: +//----------------------------------------------------------------------------- +inline void miniMax::measureIops(long long &numOperations, LARGE_INTEGER &interval, LARGE_INTEGER &curTimeBefore, char text[]) +{ + // locals + LARGE_INTEGER curTimeAfter; + + if (!MEASURE_IOPS) return; + numOperations++; // ... not thread-safe !!! + + // only the time for the io-operation is considered and accumulated + if (MEASURE_ONLY_IO) { + QueryPerformanceCounter(&curTimeAfter); + interval.QuadPart += curTimeAfter.QuadPart - curTimeBefore.QuadPart; // ... not thread-safe !!! + double totalTimeGone = (double) interval.QuadPart / frequency.QuadPart; // ... not thread-safe !!! + if (totalTimeGone >= 5.0) { + PRINT(0, this, text << "operations per second for last interval: " << (int) (numOperations / totalTimeGone)); + interval.QuadPart = 0; // ... not thread-safe !!! + numOperations = 0; // ... not thread-safe !!! + } + // the whole time passed since the beginning of the interval is considered + } else if (numOperations >= MEASURE_TIME_FREQUENCY) { + QueryPerformanceCounter(&curTimeAfter); + double totalTimeGone = (double) (curTimeAfter.QuadPart - interval.QuadPart) / frequency.QuadPart; // ... not thread-safe !!! + PRINT(0, this, text << "operations per second for last interval: " << numOperations / totalTimeGone); + interval.QuadPart = curTimeAfter.QuadPart; // ... not thread-safe !!! + numOperations = 0; // ... not thread-safe !!! + } +} + +//----------------------------------------------------------------------------- +// Name: readKnotValueFromDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::readKnotValueFromDatabase(unsigned int threadNo, unsigned int &layerNumber, unsigned int &stateNumber, twoBit &knotValue, bool &invalidLayerOrStateNumber, bool &layerInDatabaseAndCompleted) +{ + // get state number, since this is the address, where the value is saved + getLayerAndStateNumber(threadNo, layerNumber, stateNumber); + + // layer in database and completed ? + layerStatsStruct * myLss = &layerStats[layerNumber]; + layerInDatabaseAndCompleted = myLss->layerIsCompletedAndInFile; + + // valid state and layer number ? + if (layerNumber > skvfHeader.numLayers || stateNumber > myLss->knotsInLayer) { + invalidLayerOrStateNumber = true; + } else { + invalidLayerOrStateNumber = false; // checkStateIntegrity(); + } + + if (invalidLayerOrStateNumber) { + knotValue = SKV_VALUE_INVALID; + return; + } + + // read + readKnotValueFromDatabase(layerNumber, stateNumber, knotValue); +} + +//----------------------------------------------------------------------------- +// Name: readKnotValueFromDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::readKnotValueFromDatabase(unsigned int layerNumber, unsigned int stateNumber, twoBit &knotValue) +{ + // locals + twoBit databaseByte; + long long bytesAllocated; + twoBit defValue = SKV_WHOLE_BYTE_IS_INVALID; + layerStatsStruct * myLss = &layerStats[layerNumber]; + + // valid state and layer number ? + if (layerNumber > skvfHeader.numLayers || stateNumber > myLss->knotsInLayer) { + PRINT(0, this, "ERROR: INVALID layerNumber OR stateNumber in readKnotValueFromDatabase()!"); + knotValue = SKV_VALUE_INVALID; + return; + } + + // if database is complete get whole byte from file + if (skvfHeader.completed || layerInDatabase || myLss->layerIsCompletedAndInFile) { + EnterCriticalSection(&csDatabase); + loadBytesFromFile(hFileShortKnotValues, skvfHeader.headerAndStatsSize + myLss->layerOffset + stateNumber / 4, 1, &databaseByte); + LeaveCriticalSection(&csDatabase); + } else { + + // is layer already loaded + if (!myLss->layerIsLoaded) { + + EnterCriticalSection(&csDatabase); + if (!myLss->layerIsLoaded) { + // if layer is in database and completed, then load layer from file into memory, set default value otherwise + myLss->shortKnotValueByte = new unsigned char[myLss->sizeInBytes]; + if (myLss->layerIsCompletedAndInFile) { + loadBytesFromFile(hFileShortKnotValues, skvfHeader.headerAndStatsSize + myLss->layerOffset, myLss->sizeInBytes, myLss->shortKnotValueByte); + } else { + memset(myLss->shortKnotValueByte, SKV_WHOLE_BYTE_IS_INVALID, myLss->sizeInBytes); + } + bytesAllocated = myLss->sizeInBytes; + arrayInfos.addArray(layerNumber, arrayInfoStruct::arrayType_layerStats, myLss->sizeInBytes, 0); + + // output + myLss->layerIsLoaded = true; + memoryUsed2 += bytesAllocated; + PRINT(3, this, "Allocated " << bytesAllocated << " bytes in memory for knot values of layer " << layerNumber << ", which is " << (myLss->layerIsCompletedAndInFile?"":" NOT ") << " fully calculated, due to read operation."); + } + LeaveCriticalSection(&csDatabase); + } + + // measure io-operations per second + LARGE_INTEGER curTimeBefore; + if (MEASURE_IOPS && MEASURE_ONLY_IO) { + QueryPerformanceCounter(&curTimeBefore); + } + + // read ply info from array + databaseByte = myLss->shortKnotValueByte[stateNumber / 4]; + + // measure io-operations per second + measureIops(numReadSkvOperations, readSkvInterval, curTimeBefore, "Read knot value "); + } + + // make half byte + knotValue = _rotr8(databaseByte, 2 * (stateNumber % 4)) & 3; +} + +//----------------------------------------------------------------------------- +// Name: readPlyInfoFromDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::readPlyInfoFromDatabase(unsigned int layerNumber, unsigned int stateNumber, plyInfoVarType &value) +{ + // locals + unsigned int curKnot; + plyInfoVarType defValue = PLYINFO_VALUE_UNCALCULATED; + long long bytesAllocated; + plyInfoStruct * myPis = &plyInfos[layerNumber]; + + // valid state and layer number ? + if (layerNumber > plyInfoHeader.numLayers || stateNumber > myPis->knotsInLayer) { + PRINT(0, this, "ERROR: INVALID layerNumber OR stateNumber in readPlyInfoFromDatabase()!"); + value = PLYINFO_VALUE_INVALID; + return; + } + + // if database is complete get whole byte from file + if (plyInfoHeader.plyInfoCompleted || layerInDatabase || myPis->plyInfoIsCompletedAndInFile) { + EnterCriticalSection(&csDatabase); + loadBytesFromFile(hFilePlyInfo, plyInfoHeader.headerAndPlyInfosSize + myPis->layerOffset + sizeof(plyInfoVarType) * stateNumber, sizeof(plyInfoVarType), &value); + LeaveCriticalSection(&csDatabase); + } else { + + // is layer already in memory? + if (!myPis->plyInfoIsLoaded) { + EnterCriticalSection(&csDatabase); + if (!myPis->plyInfoIsLoaded) { + // if layer is in database and completed, then load layer from file into memory; set default value otherwise + myPis->plyInfo = new plyInfoVarType[myPis->knotsInLayer]; + if (myPis->plyInfoIsCompletedAndInFile) { + loadBytesFromFile(hFilePlyInfo, plyInfoHeader.headerAndPlyInfosSize + myPis->layerOffset, myPis->sizeInBytes, myPis->plyInfo); + } else { + for (curKnot=0; curKnotknotsInLayer; curKnot++) { myPis->plyInfo[curKnot] = defValue; } + } + bytesAllocated = myPis->sizeInBytes; + arrayInfos.addArray(layerNumber, arrayInfoStruct::arrayType_plyInfos, myPis->sizeInBytes, 0); + myPis->plyInfoIsLoaded = true; + memoryUsed2 += bytesAllocated; + PRINT(3, this, "Allocated " << bytesAllocated << " bytes in memory for ply info of layer " << layerNumber << ", which is " << (myPis->plyInfoIsCompletedAndInFile?"":" NOT ") << " fully calculated, due to read operation."); + } + LeaveCriticalSection(&csDatabase); + } + + // measure io-operations per second + LARGE_INTEGER curTimeBefore; + if (MEASURE_IOPS && MEASURE_ONLY_IO) { + QueryPerformanceCounter(&curTimeBefore); + } + + // read ply info from array + value = myPis->plyInfo[stateNumber]; + + // measure io-operations per second + measureIops(numReadPlyOperations, readPlyInterval, curTimeBefore, "Read ply info "); + } +} + +//----------------------------------------------------------------------------- +// Name: saveKnotValueInDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::saveKnotValueInDatabase(unsigned int layerNumber, unsigned int stateNumber, twoBit knotValue) +{ + // locals + long long bytesAllocated; + twoBit defValue = SKV_WHOLE_BYTE_IS_INVALID; + layerStatsStruct * myLss = &layerStats[layerNumber]; + + // valid state and layer number ? + if (layerNumber > skvfHeader.numLayers || stateNumber > myLss->knotsInLayer) { + PRINT(0, this, "ERROR: INVALID layerNumber OR stateNumber in saveKnotValueInDatabase()!"); + return; + } + + // is layer already completed ? + if (myLss->layerIsCompletedAndInFile) { + PRINT(0, this, "ERROR: layer already completed and in file! function: saveKnotValueInDatabase()!"); + return; + } + + // is layer already loaded? + if (!myLss->layerIsLoaded) { + + EnterCriticalSection(&csDatabase); + if (!myLss->layerIsLoaded) { + // reserve memory for this layer & create array for ply info with default value + myLss->shortKnotValueByte = new twoBit[myLss->sizeInBytes]; + memset(myLss->shortKnotValueByte, SKV_WHOLE_BYTE_IS_INVALID, myLss->sizeInBytes); + bytesAllocated = myLss->sizeInBytes; + arrayInfos.addArray(layerNumber, arrayInfoStruct::arrayType_layerStats, myLss->sizeInBytes, 0); + + // output + memoryUsed2 += bytesAllocated; + PRINT(3, this, "Allocated " << bytesAllocated << " bytes in memory for knot values of layer " << layerNumber << " due to write operation!"); + myLss->layerIsLoaded = true; + } + LeaveCriticalSection(&csDatabase); + } + + // measure io-operations per second + LARGE_INTEGER curTimeBefore; + if (MEASURE_IOPS && MEASURE_ONLY_IO) { + QueryPerformanceCounter(&curTimeBefore); + } + + // set value + long * pShortKnotValue = ((long*) myLss->shortKnotValueByte) + stateNumber / ((sizeof(long)*8) / 2); + long numBitsToShift = 2 * (stateNumber % ((sizeof(long)*8) / 2)); // little-endian byte-order + long mask = 0x00000003 << numBitsToShift; + long curShortKnotValueLong, newShortKnotValueLong; + + do { + curShortKnotValueLong = *pShortKnotValue; + newShortKnotValueLong = (curShortKnotValueLong & (~mask)) + (knotValue << numBitsToShift); + } while (InterlockedCompareExchange(pShortKnotValue, newShortKnotValueLong, curShortKnotValueLong) != curShortKnotValueLong); + + // measure io-operations per second + measureIops(numWriteSkvOperations, writeSkvInterval, curTimeBefore, "Write knot value "); +} + +//----------------------------------------------------------------------------- +// Name: savePlyInfoInDatabase() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::savePlyInfoInDatabase(unsigned int layerNumber, unsigned int stateNumber, plyInfoVarType value) +{ + // locals + unsigned int curKnot; + plyInfoVarType defValue = PLYINFO_VALUE_UNCALCULATED; + long long bytesAllocated; + plyInfoStruct * myPis = &plyInfos[layerNumber]; + + // valid state and layer number ? + if (layerNumber > plyInfoHeader.numLayers || stateNumber > myPis->knotsInLayer) { + PRINT(0, this, "ERROR: INVALID layerNumber OR stateNumber in savePlyInfoInDatabase()!"); + return; + } + + // is layer already completed ? + if (myPis->plyInfoIsCompletedAndInFile) { + PRINT(0, this, "ERROR: layer already completed and in file! function: savePlyInfoInDatabase()!"); + return; + } + + // is layer already loaded + if (!myPis->plyInfoIsLoaded) { + + EnterCriticalSection(&csDatabase); + if (!myPis->plyInfoIsLoaded) { + // reserve memory for this layer & create array for ply info with default value + myPis->plyInfo = new plyInfoVarType[myPis->knotsInLayer]; + for (curKnot=0; curKnotknotsInLayer; curKnot++) { myPis->plyInfo[curKnot] = defValue; } + bytesAllocated = myPis->sizeInBytes; + arrayInfos.addArray(layerNumber, arrayInfoStruct::arrayType_plyInfos, myPis->sizeInBytes, 0); + myPis->plyInfoIsLoaded = true; + memoryUsed2 += bytesAllocated; + PRINT(3, this, "Allocated " << bytesAllocated << " bytes in memory for ply info of layer " << layerNumber << " due to write operation!"); + } + LeaveCriticalSection(&csDatabase); + } + + // measure io-operations per second + LARGE_INTEGER curTimeBefore; + if (MEASURE_IOPS && MEASURE_ONLY_IO) { + QueryPerformanceCounter(&curTimeBefore); + } + + // set value + myPis->plyInfo[stateNumber] = value; + + // measure io-operations per second + measureIops(numWritePlyOperations, writePlyInterval, curTimeBefore, "Write ply info "); +} diff --git a/src/perfect/miniMax_retroAnalysis.cpp b/src/perfect/miniMax_retroAnalysis.cpp new file mode 100644 index 00000000..6960fec4 --- /dev/null +++ b/src/perfect/miniMax_retroAnalysis.cpp @@ -0,0 +1,749 @@ +/********************************************************************* + miniMax_retroAnalysis.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +s\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: calcKnotValuesByRetroAnalysis() +// Desc: +// The COUNT-ARRAY is the main element of the algorithmn. It contains the number of succeding states for the drawn gamestates, +// whose short knot value has to be determined. If all succeding states (branches representing possible moves) are for example won than, +// a state can be marked as lost, since no branch will lead to a drawn or won situation any more. +// Each time the short knot value of a game state has been determined, the state will be added to 'statesToProcess'. +// This list is like a queue of states, which still has to be processed. +//----------------------------------------------------------------------------- +bool miniMax::calcKnotValuesByRetroAnalysis(vector &layersToCalculate) +{ + // locals + bool abortCalculation = false; + unsigned int curLayer = 0; // Counter variable + unsigned int curSubLayer = 0; // Counter variable + unsigned int plyCounter = 0; // Counter variable + unsigned int threadNo; + stringstream ssLayers; + retroAnalysisGlobalVars retroVars; + + // init retro vars + retroVars.thread.resize(threadManager.getNumThreads()); + for (threadNo=0; threadNogetFileSize() == (LONGLONG) layerStats[layerNumber].knotsInLayer) { + PRINT(2, this, " Loading init states from file: " << ssInitArrayFilePath.str()); + initAlreadyDone = true; + } + + // don't add layers twice + if (retroVars.layerInitialized[layerNumber]) continue; + else retroVars.layerInitialized[layerNumber] = true; + + // prepare parameters + numStatesProcessed = 0; + retroVars.statsValueCounter[SKV_VALUE_GAME_WON ] = 0; + retroVars.statsValueCounter[SKV_VALUE_GAME_LOST ] = 0; + retroVars.statsValueCounter[SKV_VALUE_GAME_DRAWN] = 0; + retroVars.statsValueCounter[SKV_VALUE_INVALID ] = 0; + threadManagerClass::threadVarsArray tva(threadManager.getNumThreads(), initRetroAnalysisVars(this, &retroVars, layerNumber, initArray, initAlreadyDone)); + + // process each state in the current layer + switch (threadManager.executeParallelLoop(initRetroAnalysisThreadProc, tva.getPointerToArray(), tva.getSizeOfArray(), TM_SCHEDULE_STATIC, 0, layerStats[layerNumber].knotsInLayer - 1, 1)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + SAFE_DELETE(initArray); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + return falseOrStop(); + } + + // reduce and delete thread specific data + tva.reduce(); + initAlreadyDone = false; + initArray->flushBuffers(); + SAFE_DELETE(initArray); + if (numStatesProcessed < layerStats[layerNumber].knotsInLayer) return falseOrStop(); + + // when init file was created new then save it now + PRINT(2, this, " Saved initialized states to file: " << ssInitArrayFilePath.str()); + + // show statistics + PRINT(2, this, " won states: " << retroVars.statsValueCounter[SKV_VALUE_GAME_WON ]); + PRINT(2, this, " lost states: " << retroVars.statsValueCounter[SKV_VALUE_GAME_LOST ]); + PRINT(2, this, " draw states: " << retroVars.statsValueCounter[SKV_VALUE_GAME_DRAWN]); + PRINT(2, this, " invalid states: " << retroVars.statsValueCounter[SKV_VALUE_INVALID ]); + } + return true; +} + +//----------------------------------------------------------------------------- +// Name: initRetroAnalysisParallelSub() +// Desc: +//----------------------------------------------------------------------------- +DWORD miniMax::initRetroAnalysisThreadProc(void* pParameter, int index) +{ + // locals + initRetroAnalysisVars * iraVars = (initRetroAnalysisVars *) pParameter; + miniMax * m = iraVars->pMiniMax; + float floatValue; // dummy variable for calls of getValueOfSituation() + stateAdressStruct curState; // current state counter for loops + twoBit curStateValue; // for calls of getValueOfSituation() + + curState.layerNumber = iraVars->layerNumber; + curState.stateNumber = index; + iraVars->statesProcessed++; + + // print status + if (iraVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(2, m, "Already initialized " << m->numStatesProcessed << " of " << m->layerStats[curState.layerNumber].knotsInLayer << " states"); + } + + // layer initialization already done ? if so, then read from file + if (iraVars->initAlreadyDone) { + if (!iraVars->bufferedFile->readBytes(iraVars->curThreadNo, index * sizeof(twoBit), sizeof(twoBit), (unsigned char*) &curStateValue)) { + PRINT(0, m, "ERROR: initArray->takeBytes() failed"); + return m->falseOrStop(); + } + + // initialization not done + } else { + + // set current selected situation + if (!m->setSituation(iraVars->curThreadNo, curState.layerNumber, curState.stateNumber)) { + curStateValue = SKV_VALUE_INVALID; + } else { + // get value of current situation + m->getValueOfSituation(iraVars->curThreadNo, floatValue, curStateValue); + } + } + + // save init value + if (curStateValue != SKV_VALUE_INVALID) { + + // save short knot value + m->saveKnotValueInDatabase(curState.layerNumber, curState.stateNumber, curStateValue); + + // put in list if state is final + if (curStateValue == SKV_VALUE_GAME_WON || curStateValue == SKV_VALUE_GAME_LOST) { + + // ply info + m->savePlyInfoInDatabase(curState.layerNumber, curState.stateNumber, 0); + + // add state to list + m->addStateToProcessQueue(*iraVars->retroVars, iraVars->retroVars->thread[iraVars->curThreadNo], 0, &curState); + } + } + + // write data to file + if (!iraVars->initAlreadyDone) { + // curStateValue sollte 2 sein bei index == 1329322 + if (!iraVars->bufferedFile->writeBytes(iraVars->curThreadNo, index * sizeof(twoBit), sizeof(twoBit), (unsigned char*) &curStateValue)) { + PRINT(0, m, "ERROR: bufferedFile->writeBytes failed!"); + return m->falseOrStop(); + } + } + iraVars->statsValueCounter[curStateValue]++; + + return TM_RETURN_VALUE_OK; +} + +//----------------------------------------------------------------------------- +// Name: prepareCountArrays() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::prepareCountArrays(retroAnalysisGlobalVars &retroVars) +{ + // locals + unsigned int numKnotsInCurLayer; + stateAdressStruct curState; // current state counter for loops + unsigned int curLayer = 0; // Counter variable + countArrayVarType defValue = 0; // default counter array value + DWORD dwWritten; + DWORD dwRead; + LARGE_INTEGER fileSize; + HANDLE hFileCountArray = NULL; // file handle for loading and saving the arrays in 'countArrays' + stringstream ssCountArrayPath; + stringstream ssCountArrayFilePath; + stringstream ssLayers; + + // output & filenames + for (curLayer=0; curLayer succCalculated(skvfHeader.numLayers, false); // + + // process each layer + for (curLayerId=0; curLayerId tva(threadManager.getNumThreads(), addNumSuccedorsVars(this, &retroVars, layerNumber)); + + // process each state in the current layer + switch (threadManager.executeParallelLoop(addNumSuccedorsThreadProc, tva.getPointerToArray(), tva.getSizeOfArray(), TM_SCHEDULE_STATIC, 0, layerStats[layerNumber].knotsInLayer - 1, 1)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + return falseOrStop(); + } + + // reduce and delete thread specific data + tva.reduce(); + if (numStatesProcessed < layerStats[layerNumber].knotsInLayer) return falseOrStop(); + + // don't calc layers twice + } else { + return falseOrStop(); + } + + // ... and process succeding layers + for (curState.layerNumber=0; curState.layerNumber tva(threadManager.getNumThreads(), addNumSuccedorsVars(this, &retroVars, succState.layerNumber)); + + // process each state in the current layer + switch (threadManager.executeParallelLoop(addNumSuccedorsThreadProc, tva.getPointerToArray(), tva.getSizeOfArray(), TM_SCHEDULE_STATIC, 0, layerStats[succState.layerNumber].knotsInLayer - 1, 1)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + return falseOrStop(); + } + + // reduce and delete thread specific data + tva.reduce(); + if (numStatesProcessed < layerStats[succState.layerNumber].knotsInLayer) return falseOrStop(); + } + } + + // everything fine + return true; +} + +//----------------------------------------------------------------------------- +// Name: addNumSuccedorsThreadProc() +// Desc: +//----------------------------------------------------------------------------- +DWORD miniMax::addNumSuccedorsThreadProc(void* pParameter, int index) +{ + // locals + addNumSuccedorsVars * ansVars = (addNumSuccedorsVars *) pParameter; + miniMax * m = ansVars->pMiniMax; + unsigned int numLayersToCalculate = (unsigned int) ansVars->retroVars->layersToCalculate.size(); + unsigned int curLayerId; // current processed layer within 'layersToCalculate' + unsigned int amountOfPred; + unsigned int curPred; + countArrayVarType countValue; + stateAdressStruct predState; + stateAdressStruct curState; + twoBit curStateValue; + plyInfoVarType numPlies; // number of plies of the current considered succeding state + bool cuStateAddedToProcessQueue = false; + + curState.layerNumber = ansVars->layerNumber; + curState.stateNumber = (stateNumberVarType) index; + + // print status + ansVars->statesProcessed++; + if (ansVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(2, m, " Already processed " << m->numStatesProcessed << " of " << m->layerStats[curState.layerNumber].knotsInLayer << " states"); + } + + // invalid state ? + m->readKnotValueFromDatabase(curState.layerNumber, curState.stateNumber, curStateValue); + if (curStateValue == SKV_VALUE_INVALID) return TM_RETURN_VALUE_OK; + + // set current selected situation + if (!m->setSituation(ansVars->curThreadNo, curState.layerNumber, curState.stateNumber)) { + PRINT(0, m, "ERROR: setSituation() returned false!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + + // get list with statenumbers of predecessors + m->getPredecessors(ansVars->curThreadNo, &amountOfPred, ansVars->predVars); + + // iteration + for (curPred=0; curPredpredVars[curPred].predLayerNumbers; + predState.stateNumber = ansVars->predVars[curPred].predStateNumbers; + + // don't calculate states from layers above yet + for (curLayerId=0; curLayerIdretroVars->layersToCalculate[curLayerId] == predState.layerNumber) break; + } + if (curLayerId == numLayersToCalculate) continue; + + // put in list (with states to be processed) if state is final + if (!cuStateAddedToProcessQueue && (curStateValue == SKV_VALUE_GAME_WON || curStateValue == SKV_VALUE_GAME_LOST)) { + m->readPlyInfoFromDatabase(curState.layerNumber, curState.stateNumber, numPlies); + m->addStateToProcessQueue(*ansVars->retroVars, ansVars->retroVars->thread[ansVars->curThreadNo], numPlies, &curState); + cuStateAddedToProcessQueue = true; + } + + // add this state as possible move + long * pCountValue = ((long*) ansVars->retroVars->countArrays[curLayerId]) + predState.stateNumber / (sizeof(long) / sizeof(countArrayVarType)); + long numBitsToShift = sizeof(countArrayVarType) * 8 * (predState.stateNumber % (sizeof(long) / sizeof(countArrayVarType))); // little-endian byte-order + long mask = 0x000000ff << numBitsToShift; + long curCountLong, newCountLong; + + do { + curCountLong = *pCountValue; + countValue = (countArrayVarType) ((curCountLong & mask) >> numBitsToShift); + if (countValue == 255) { + PRINT(0, m, "ERROR: maximum value for Count[] reached!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } else { + countValue++; + newCountLong = (curCountLong & (~mask)) + (countValue << numBitsToShift); + } + } while (InterlockedCompareExchange(pCountValue, newCountLong, curCountLong) != curCountLong); + } + + // everything is fine + return TM_RETURN_VALUE_OK; +} + +//----------------------------------------------------------------------------- +// Name: performRetroAnalysis() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::performRetroAnalysis(retroAnalysisGlobalVars &retroVars) +{ + // locals + stateAdressStruct curState; // current state counter for loops + twoBit curStateValue; // current state value + unsigned int curLayerId; // current processed layer within 'layersToCalculate' + + PRINT(2, this, " *** Begin Iteration ***"); + numStatesProcessed = 0; + curCalculationActionId = MM_ACTION_PERFORM_RETRO_ANAL; + + // process each state in the current layer + switch (threadManager.executeInParallel(performRetroAnalysisThreadProc, (void**) &retroVars, 0)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_EXECUTION_CANCELLED: + PRINT(0,this, "\n****************************************\nMain thread: Execution cancelled by user!\n****************************************\n"); + return false; + default: + case TM_RETURN_VALUE_INVALID_PARAM: + case TM_RETURN_VALUE_UNEXPECTED_ERROR: + return falseOrStop(); + } + + // if there are still states to process, than something went wrong + for (unsigned int curThreadNo=0; curThreadNopMiniMax; + unsigned int threadNo = m->threadManager.getThreadNumber(); + retroAnalysisThreadVars * threadVars = &retroVars->thread[threadNo]; + + twoBit predStateValue; + unsigned int curLayerId; // current processed layer within 'layersToCalculate' + unsigned int amountOfPred; // total numbers of predecessors and current considered one + unsigned int curPred; + unsigned int threadCounter; + long long numStatesProcessed; + long long totalNumStatesToProcess; + plyInfoVarType curNumPlies; + plyInfoVarType numPliesTillCurState; + plyInfoVarType numPliesTillPredState; + countArrayVarType countValue; + stateAdressStruct predState; + stateAdressStruct curState; // current state counter for while-loop + twoBit curStateValue; // current state value + retroAnalysisPredVars predVars[MAX_NUM_PREDECESSORS]; + + for (numStatesProcessed = 0, curNumPlies=0; curNumPliesstatesToProcess.size(); curNumPlies++) { + + // skip empty and uninitialized cyclic arrays + if (threadVars->statesToProcess[curNumPlies] != NULL) { + + if (threadNo==0) { + PRINT(0, m, " Current number of plies: " << (unsigned int) curNumPlies << "/" << threadVars->statesToProcess.size()); + for (threadCounter=0; threadCounterthreadManager.getNumThreads(); threadCounter++) { + PRINT(0, m, " States to process for thread " << threadCounter << ": " << retroVars->thread[threadCounter].numStatesToProcess); + } + } + + while (threadVars->statesToProcess[curNumPlies]->takeBytes(sizeof(stateAdressStruct), (unsigned char*) &curState)) { + + // execution cancelled by user? + if (m->threadManager.wasExecutionCancelled()) { + PRINT(0,m, "\n****************************************\nSub-thread no. " << threadNo << ": Execution cancelled by user!\n****************************************\n"); + return TM_RETURN_VALUE_EXECUTION_CANCELLED; + } + + // get value of current state + m->readKnotValueFromDatabase(curState.layerNumber, curState.stateNumber, curStateValue); + m->readPlyInfoFromDatabase (curState.layerNumber, curState.stateNumber, numPliesTillCurState); + + if (numPliesTillCurState != curNumPlies) { + PRINT(0,m,"ERROR: numPliesTillCurState != curNumPlies"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + + // console output + numStatesProcessed++; + threadVars->numStatesToProcess--; + if (numStatesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + for (totalNumStatesToProcess=0, threadCounter=0; threadCounterthreadManager.getNumThreads(); threadCounter++) { + totalNumStatesToProcess += retroVars->thread[threadCounter].numStatesToProcess; + } + PRINT(2, m, " states already processed: " << m->numStatesProcessed << " \t states still in list: " << totalNumStatesToProcess); + } + + // set current selected situation + if (!m->setSituation(threadNo, curState.layerNumber, curState.stateNumber)) { + PRINT(0,m,"ERROR: setSituation() returned false!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + + // get list with statenumbers of predecessors + m->getPredecessors(threadNo, &amountOfPred, predVars); + + // iteration + for (curPred=0; curPredlayersToCalculate.size(); curLayerId++) { + if (retroVars->layersToCalculate[curLayerId] == predState.layerNumber) break; + } + if (curLayerId == retroVars->layersToCalculate.size()) continue; + + // get value of predecessor + m->readKnotValueFromDatabase(predState.layerNumber, predState.stateNumber, predStateValue); + + // only drawn states are relevant here, since the other are already calculated + if (predStateValue == SKV_VALUE_GAME_DRAWN) { + + // if current considered state is a lost game then all predecessors are a won game + if (curStateValue == m->skvPerspectiveMatrix[SKV_VALUE_GAME_LOST][predVars[curPred].playerToMoveChanged ? PL_TO_MOVE_CHANGED : PL_TO_MOVE_UNCHANGED]) { + m->saveKnotValueInDatabase(predState.layerNumber, predState.stateNumber, SKV_VALUE_GAME_WON); + m->savePlyInfoInDatabase (predState.layerNumber, predState.stateNumber, numPliesTillCurState + 1); // (requirement: curNumPlies == numPliesTillCurState) + if (numPliesTillCurState + 1 < curNumPlies) { + PRINT(0,m,"ERROR: Current number of plies is bigger than numPliesTillCurState + 1!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + m->addStateToProcessQueue(*retroVars, *threadVars, numPliesTillCurState + 1, &predState); + // if current state is a won game, then this state is not an option any more for all predecessors + } else { + // reduce count value by one + long * pCountValue = ((long*) retroVars->countArrays[curLayerId]) + predState.stateNumber / (sizeof(long) / sizeof(countArrayVarType)); + long numBitsToShift = sizeof(countArrayVarType) * 8 * (predState.stateNumber % (sizeof(long) / sizeof(countArrayVarType))); // little-endian byte-order + long mask = 0x000000ff << numBitsToShift; + long curCountLong, newCountLong; + + do { + curCountLong = *pCountValue; + countValue = (countArrayVarType) ((curCountLong & mask) >> numBitsToShift); + if (countValue > 0) { + countValue--; + newCountLong = (curCountLong & (~mask)) + (countValue << numBitsToShift); + } else { + PRINT(0,m,"ERROR: Count is already zero!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + } while (InterlockedCompareExchange(pCountValue, newCountLong, curCountLong) != curCountLong); + + // ply info (requirement: curNumPlies == numPliesTillCurState) + m->readPlyInfoFromDatabase(predState.layerNumber, predState.stateNumber, numPliesTillPredState); + if (numPliesTillPredState == PLYINFO_VALUE_UNCALCULATED || numPliesTillCurState + 1 > numPliesTillPredState) { + m->savePlyInfoInDatabase(predState.layerNumber, predState.stateNumber, numPliesTillCurState + 1); + } + + // when all successor are won states then this is a lost state (this should only be the case for one thread) + if (countValue == 0) { + m->saveKnotValueInDatabase(predState.layerNumber, predState.stateNumber, SKV_VALUE_GAME_LOST); + if (numPliesTillCurState + 1 < curNumPlies) { + PRINT(0,m,"ERROR: Current number of plies is bigger than numPliesTillCurState + 1!"); + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; + } + m->addStateToProcessQueue(*retroVars, *threadVars, numPliesTillCurState + 1, &predState); + } + } + } + } + } + } + + // there might be other threads still processing states with this ply number + m->threadManager.waitForOtherThreads(threadNo); + } + + // every thing ok + return TM_RETURN_VALUE_OK; +} + +//----------------------------------------------------------------------------- +// Name: addStateToProcessQueue() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::addStateToProcessQueue(retroAnalysisGlobalVars &retroVars, retroAnalysisThreadVars &threadVars, unsigned int plyNumber, stateAdressStruct* pState) +{ + // resize vector if too small + if (plyNumber >= threadVars.statesToProcess.size()) { + threadVars.statesToProcess.resize(max(plyNumber+1, 10*threadVars.statesToProcess.size()), NULL); + PRINT(4, this, " statesToProcess resized to " << threadVars.statesToProcess.size()); + } + + // initialize cyclic array if necessary + if (threadVars.statesToProcess[plyNumber] == NULL) { + stringstream ssStatesToProcessFilePath; + stringstream ssStatesToProcessPath; + ssStatesToProcessPath << fileDirectory << (fileDirectory.size()?"\\":"") << "statesToProcess"; + CreateDirectoryA(ssStatesToProcessPath.str().c_str(), NULL); + ssStatesToProcessFilePath.str(""); + ssStatesToProcessFilePath << ssStatesToProcessPath.str() << "\\statesToProcessWithPlyCounter=" << plyNumber << "andThread=" << threadVars.threadNo << ".dat"; + threadVars.statesToProcess[plyNumber] = new cyclicArray(BLOCK_SIZE_IN_CYCLIC_ARRAY * sizeof(stateAdressStruct), (unsigned int)(retroVars.totalNumKnots / BLOCK_SIZE_IN_CYCLIC_ARRAY) + 1, ssStatesToProcessFilePath.str().c_str()); + PRINT(4, this, " Created cyclic array: " << ssStatesToProcessFilePath.str()); + } + + // add state + if (!threadVars.statesToProcess[plyNumber]->addBytes(sizeof(stateAdressStruct), (unsigned char*) pState)) { + PRINT(0, this, "ERROR: Cyclic list to small! numStatesToProcess:" << threadVars.numStatesToProcess); + return falseOrStop(); + } + + // everything was fine + threadVars.numStatesToProcess++; + return true; +} diff --git a/src/perfect/miniMax_retroAnalysis.h b/src/perfect/miniMax_retroAnalysis.h new file mode 100644 index 00000000..232a500b --- /dev/null +++ b/src/perfect/miniMax_retroAnalysis.h @@ -0,0 +1,70 @@ +/*********************************************************************\ + strLib.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + + struct retroAnalysisQueueState + { + stateNumberVarType stateNumber; // state stored in the retro analysis queue. the queue is a buffer containing states to be passed to 'retroAnalysisThreadVars::statesToProcess' + plyInfoVarType numPliesTillCurState; // ply number for the stored state + }; + + struct retroAnalysisThreadVars // thread specific variables for each thread in the retro analysis + { + vector statesToProcess; // vector-queue containing the states, whose short knot value are known for sure. they have to be processed. if processed the state will be removed from list. indexing: [threadNo][plyNumber] + vector> stateQueue; // Queue containing states, whose 'count value' shall be increased by one. Before writing 'count value' to 'count array' the writing positions are sorted for faster processing. + long long numStatesToProcess; // Number of states in 'statesToProcess' which have to be processed + unsigned int threadNo; + }; + + struct retroAnalysisVars // constant during calculation + { + vector countArrays; // One count array for each layer in 'layersToCalculate'. (For the nine men's morris game two layers have to considered at once.) + vector countArraysCompr; // '' but compressed + vector layerInitialized; // + vector layersToCalculate; // layers which shall be calculated + long long totalNumKnots; // total numbers of knots which have to be stored in memory + long long numKnotsToCalc; // number of knots of all layers to be calculated + vector thread; + }; + + struct initRetroAnalysisVars + { + miniMax * pMiniMax; + unsigned int curThreadNo; + unsigned int layerNumber; + LONGLONG statesProcessed; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + bufferedFileClass * bufferedFile; + retroAnalysisVars * retroVars; + bool initAlreadyDone; // true if the initialization information is already available in a file + }; + + struct addSuccLayersVars + { + miniMax * pMiniMax; + unsigned int curThreadNo; + unsigned int statsValueCounter[SKV_NUM_VALUES]; + unsigned int layerNumber; + retroAnalysisVars * retroVars; + }; + + struct retroAnalysisPredVars + { + unsigned int predStateNumbers; + unsigned int predLayerNumbers; + unsigned int predSymOperation; + bool playerToMoveChanged; + }; + + struct addNumSuccedorsVars + { + miniMax * pMiniMax; + unsigned int curThreadNo; + unsigned int layerNumber; + LONGLONG statesProcessed; + retroAnalysisVars * retroVars; + retroAnalysisPredVars * predVars; + }; \ No newline at end of file diff --git a/src/perfect/miniMax_statistics.cpp b/src/perfect/miniMax_statistics.cpp new file mode 100644 index 00000000..0e231f02 --- /dev/null +++ b/src/perfect/miniMax_statistics.cpp @@ -0,0 +1,410 @@ +/********************************************************************* + miniMax_statistics.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: showMemoryStatus() +// Desc: +//----------------------------------------------------------------------------- +unsigned int miniMax::getNumThreads() +{ + return threadManager.getNumThreads(); +} + +//----------------------------------------------------------------------------- +// Name: anyFreshlyCalculatedLayer() +// Desc: called by MAIN-thread in pMiniMax->csOsPrint critical-section +//----------------------------------------------------------------------------- +bool miniMax::anyFreshlyCalculatedLayer() +{ + return (lastCalculatedLayer.size()>0); +} + +//----------------------------------------------------------------------------- +// Name: getLastCalculatedLayer() +// Desc: called by MAIN-thread in pMiniMax->csOsPrint critical-section +//----------------------------------------------------------------------------- +unsigned int miniMax::getLastCalculatedLayer() +{ + unsigned int tmp = lastCalculatedLayer.front(); + lastCalculatedLayer.pop_front(); + return tmp; +} + +//----------------------------------------------------------------------------- +// Name: isLayerInDatabase() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::isLayerInDatabase(unsigned int layerNum) +{ + if (layerStats == NULL) return false; + return layerStats[layerNum].layerIsCompletedAndInFile; +} + +//----------------------------------------------------------------------------- +// Name: getLayerSizeInBytes() +// Desc: +//----------------------------------------------------------------------------- +long long miniMax::getLayerSizeInBytes(unsigned int layerNum) +{ + if (plyInfos == NULL || layerStats == NULL) return 0; + return (long long) layerStats[layerNum].sizeInBytes + (long long) plyInfos[layerNum].sizeInBytes; +} + +//----------------------------------------------------------------------------- +// Name: getNumWonStates() +// Desc: +//----------------------------------------------------------------------------- +miniMax::stateNumberVarType miniMax::getNumWonStates(unsigned int layerNum) +{ + if (layerStats == NULL) return 0; + return layerStats[layerNum].numWonStates; +} + +//----------------------------------------------------------------------------- +// Name: getNumLostStates() +// Desc: +//----------------------------------------------------------------------------- +miniMax::stateNumberVarType miniMax::getNumLostStates(unsigned int layerNum) +{ + if (layerStats == NULL) return 0; + return layerStats[layerNum].numLostStates; +} + +//----------------------------------------------------------------------------- +// Name: getNumDrawnStates() +// Desc: +//----------------------------------------------------------------------------- +miniMax::stateNumberVarType miniMax::getNumDrawnStates(unsigned int layerNum) +{ + if (layerStats == NULL) return 0; + return layerStats[layerNum].numDrawnStates; +} + +//----------------------------------------------------------------------------- +// Name: getNumInvalidStates() +// Desc: +//----------------------------------------------------------------------------- +miniMax::stateNumberVarType miniMax::getNumInvalidStates(unsigned int layerNum) +{ + if (layerStats == NULL) return 0; + return layerStats[layerNum].numInvalidStates; +} + +//----------------------------------------------------------------------------- +// Name: showMemoryStatus() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::showMemoryStatus() +{ + MEMORYSTATUSEX memStatus; + memStatus.dwLength = sizeof (memStatus); + GlobalMemoryStatusEx(&memStatus); + + cout << endl << "dwMemoryLoad : " << memStatus.dwMemoryLoad; + cout << endl << "ullAvailExtendedVirtual: " << memStatus.ullAvailExtendedVirtual; + cout << endl << "ullAvailPageFile : " << memStatus.ullAvailPageFile; + cout << endl << "ullAvailPhys : " << memStatus.ullAvailPhys; + cout << endl << "ullAvailVirtual : " << memStatus.ullAvailVirtual; + cout << endl << "ullTotalPageFile : " << memStatus.ullTotalPageFile; + cout << endl << "ullTotalPhys : " << memStatus.ullTotalPhys; + cout << endl << "ullTotalVirtual : " << memStatus.ullTotalVirtual; +} + +//----------------------------------------------------------------------------- +// Name: setOutputStream() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::setOutputStream(ostream* theStream, void(*printFunc)(void *pUserData), void *pUserData) +{ + osPrint = theStream; + pDataForUserPrintFunc = pUserData; + userPrintFunc = printFunc; +} + +//----------------------------------------------------------------------------- +// Name: showLayerStats() +// Desc: +//----------------------------------------------------------------------------- +void miniMax::showLayerStats(unsigned int layerNumber) +{ + // locals + stateAdressStruct curState; + unsigned int statsValueCounter[] = {0,0,0,0}; + twoBit curStateValue; + + // calc and show statistics + for (curState.layerNumber=layerNumber, curState.stateNumber=0; curState.stateNumbercsOsPrint critical-section +//----------------------------------------------------------------------------- +bool miniMax::anyArrawInfoToUpdate() +{ + return (arrayInfos.arrayInfosToBeUpdated.size()>0); +} + +//----------------------------------------------------------------------------- +// Name: getArrayInfoForUpdate() +// Desc: called by MAIN-thread in pMiniMax->csOsPrint critical-section +//----------------------------------------------------------------------------- +miniMax::arrayInfoChange miniMax::getArrayInfoForUpdate() +{ + miniMax::arrayInfoChange tmp = arrayInfos.arrayInfosToBeUpdated.front(); + arrayInfos.arrayInfosToBeUpdated.pop_front(); + return tmp; +} + +//----------------------------------------------------------------------------- +// Name: getCurrentActionStr() +// Desc: called by MAIN-thread in pMiniMax->csOsPrint critical-section +//----------------------------------------------------------------------------- +LPWSTR miniMax::getCurrentActionStr() +{ + switch (curCalculationActionId) + { + case MM_ACTION_INIT_RETRO_ANAL : return L"initiating retro-analysis"; + case MM_ACTION_PREPARE_COUNT_ARRAY : return L"preparing count arrays"; + case MM_ACTION_PERFORM_RETRO_ANAL : return L"performing retro analysis"; + case MM_ACTION_PERFORM_ALPHA_BETA : return L"performing alpha-beta-algorithmn"; + case MM_ACTION_TESTING_LAYER : return L"testing calculated layer"; + case MM_ACTION_SAVING_LAYER_TO_FILE : return L"saving layer to file"; + case MM_ACTION_CALC_LAYER_STATS : return L"making layer statistics"; + case MM_ACTION_NONE : return L"none"; + default: return L"undefined"; + } +} + +//----------------------------------------------------------------------------- +// Name: getCurrentCalculatedLayer() +// Desc: called by MAIN-thread in pMiniMax->csOsPrint critical-section +//----------------------------------------------------------------------------- +void miniMax::getCurrentCalculatedLayer(vector &layers) +{ + // when retro-analysis is used than two layers are calculated at the same time + if (shallRetroAnalysisBeUsed(curCalculatedLayer) && layerStats[curCalculatedLayer].partnerLayer != curCalculatedLayer) { + layers.resize(2); + layers[0] = curCalculatedLayer; + layers[1] = layerStats[curCalculatedLayer].partnerLayer; + } else { + layers.resize(1); + layers[0] = curCalculatedLayer; + } +} + +//----------------------------------------------------------------------------- +// Name: arrayInfoContainer::addArray() +// Desc: Caution: layerNumber and type must be a unique pair! +// called by single CALCULATION-thread +//----------------------------------------------------------------------------- +void miniMax::arrayInfoContainer::addArray(unsigned int layerNumber, unsigned int type, long long size, long long compressedSize) +{ + // create new info object and add to list + EnterCriticalSection(&c->csOsPrint); + arrayInfoStruct ais; + ais.belongsToLayer = layerNumber; + ais.compressedSizeInBytes = compressedSize; + ais.sizeInBytes = size; + ais.type = type; + ais.updateCounter = 0; + listArrays.push_back(ais); + + // notify cahnge + arrayInfoChange aic; + aic.arrayInfo = &listArrays.back(); + aic.itemIndex = (unsigned int) listArrays.size() - 1; + arrayInfosToBeUpdated.push_back(aic); + + // save pointer of info in vector for direct access + vectorArrays[layerNumber*arrayInfoStruct::numArrayTypes + type] = (--listArrays.end()); + + // update GUI + if (c->userPrintFunc != NULL) { + c->userPrintFunc(c->pDataForUserPrintFunc); + } + LeaveCriticalSection(&c->csOsPrint); +} + +//----------------------------------------------------------------------------- +// Name: arrayInfoContainer::removeArray() +// Desc: called by single CALCULATION-thread +//----------------------------------------------------------------------------- +void miniMax::arrayInfoContainer::removeArray(unsigned int layerNumber, unsigned int type, long long size, long long compressedSize) +{ + // find info object in list + EnterCriticalSection(&c->csOsPrint); + + if (vectorArrays.size() > layerNumber*arrayInfoStruct::numArrayTypes + type) { + list::iterator itr = vectorArrays[layerNumber*arrayInfoStruct::numArrayTypes + type]; + if (itr != listArrays.end()) { + + // does sizes fit? + if (itr->belongsToLayer != layerNumber || itr->type!=type || itr->sizeInBytes!=size || itr->compressedSizeInBytes!=compressedSize) { + c->falseOrStop(); + } + + // notify cahnge + arrayInfoChange aic; + aic.arrayInfo = NULL; + aic.itemIndex = (unsigned int) std::distance(listArrays.begin(), itr); + arrayInfosToBeUpdated.push_back(aic); + + // delete tem from list + listArrays.erase(itr); + } + } + + // update GUI + if (c->userPrintFunc != NULL) { + c->userPrintFunc(c->pDataForUserPrintFunc); + } + LeaveCriticalSection(&c->csOsPrint); +} + +//----------------------------------------------------------------------------- +// Name: arrayInfoContainer::updateArray() +// Desc: called by mltiple CALCULATION-thread +//----------------------------------------------------------------------------- +void miniMax::arrayInfoContainer::updateArray(unsigned int layerNumber, unsigned int type) +{ + // find info object in list + list::iterator itr = vectorArrays[layerNumber*arrayInfoStruct::numArrayTypes + type]; + + itr->updateCounter++; + if (itr->updateCounter>arrayInfoStruct::updateCounterThreshold) { + + // notify cahnge + EnterCriticalSection(&c->csOsPrint); + arrayInfoChange aic; + aic.arrayInfo = &(*itr); + aic.itemIndex = (unsigned int) std::distance(listArrays.begin(), itr); + arrayInfosToBeUpdated.push_back(aic); + + // update GUI + if (c->userPrintFunc != NULL) { + c->userPrintFunc(c->pDataForUserPrintFunc); + } + itr->updateCounter = 0; + LeaveCriticalSection(&c->csOsPrint); + } +} \ No newline at end of file diff --git a/src/perfect/miniMax_test.cpp b/src/perfect/miniMax_test.cpp new file mode 100644 index 00000000..b891c5db --- /dev/null +++ b/src/perfect/miniMax_test.cpp @@ -0,0 +1,550 @@ +/********************************************************************* + miniMax_test.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "miniMax.h" + +//----------------------------------------------------------------------------- +// Name: testLayer() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::testLayer(unsigned int layerNumber) +{ + // Locals + unsigned int curThreadNo; + unsigned int returnValue; + + // database open? + if (hFileShortKnotValues == NULL || hFilePlyInfo == NULL) { + PRINT(0, this, "ERROR: Database file not open!"); + return falseOrStop(); + } + + // output + PRINT(1, this, endl << "*** Test each state in layer: " << layerNumber << " ***"); + PRINT(1, this, (getOutputInformation(layerNumber))); + + // prepare parameters for multithreading + skvfHeader.completed = false; + layerInDatabase = false; + numStatesProcessed = 0; + curCalculatedLayer = layerNumber; + curCalculationActionId = MM_ACTION_TESTING_LAYER; + testLayersVars *tlVars = new testLayersVars[threadManager.getNumThreads()]; + for (curThreadNo=0; curThreadNopMiniMax; + unsigned int layerNumber = tlVars->layerNumber; + unsigned int stateNumber = index; + unsigned int threadNo = tlVars->curThreadNo; + twoBit * subValueInDatabase = tlVars->subValueInDatabase; + plyInfoVarType * subPlyInfos = tlVars->subPlyInfos; + bool * hasCurPlayerChanged = tlVars->hasCurPlayerChanged; + twoBit shortValueInDatabase; + plyInfoVarType numPliesTillCurState; + twoBit shortValueInGame; + float floatValueInGame; + plyInfoVarType min, max; + unsigned int numPossibilities; + unsigned int i, j; + unsigned int tmpStateNumber, tmpLayerNumber; + unsigned int * idPossibility; + void * pPossibilities; + void * pBackup; + bool isOpponentLevel; + bool invalidLayerOrStateNumber; + bool layerInDatabaseAndCompleted; + + // output + tlVars->statesProcessed++; + if (tlVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(0, m, m->numStatesProcessed << " states of " << m->layerStats[layerNumber].knotsInLayer << " tested"); + } + + // situation already existend in database ? + m->readKnotValueFromDatabase(layerNumber, stateNumber, shortValueInDatabase); + m->readPlyInfoFromDatabase (layerNumber, stateNumber, numPliesTillCurState); + + // prepare the situation + if (!m->setSituation(threadNo, layerNumber, stateNumber)) { + + // when situation cannot be constructed then state must be marked as invalid in database + if (shortValueInDatabase != SKV_VALUE_INVALID || numPliesTillCurState != PLYINFO_VALUE_INVALID) { + PRINT(0, m, "ERROR: DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Could not set situation, but value is not invalid."); + goto errorInDatabase; + } else { + return TM_RETURN_VALUE_OK; + } + } + + // debug information + if (m->verbosity > 5) { + PRINT(5, m, "layer: " << layerNumber << " state: " << stateNumber); + m->printField(threadNo, shortValueInDatabase); + } + + // get number of possiblities + m->setOpponentLevel(threadNo, false); + idPossibility = m->getPossibilities(threadNo, &numPossibilities, &isOpponentLevel, &pPossibilities); + + // unable to move + if (numPossibilities == 0) { + + // get ingame value + m->getValueOfSituation(threadNo, floatValueInGame, shortValueInGame); + + // compare database with game + if (shortValueInDatabase != shortValueInGame || numPliesTillCurState != 0) { PRINT(0,m, "ERROR: DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Number of possibilities is zero, but knot value is not invalid or ply info equal zero."); goto errorInDatabase; } + if (shortValueInDatabase == SKV_VALUE_INVALID) { PRINT(0,m, "ERROR: DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Number of possibilities is zero, but knot value is invalid."); goto errorInDatabase; } + + } else { + + // check each possible move + for (i=0; imove(threadNo, idPossibility[i], isOpponentLevel, &pBackup, pPossibilities); + + // get database value + m->readKnotValueFromDatabase(threadNo, tmpLayerNumber, tmpStateNumber, subValueInDatabase[i], invalidLayerOrStateNumber, layerInDatabaseAndCompleted); + m->readPlyInfoFromDatabase (tmpLayerNumber, tmpStateNumber, subPlyInfos[i]); + hasCurPlayerChanged[i] = (m->getOpponentLevel(threadNo) == true); + + // debug information + if (m->verbosity > 5) { + PRINT(5, m, "layer: " << tmpLayerNumber << " state: " << tmpStateNumber << " value: " << (int) subValueInDatabase[i]); + m->printField(threadNo, subValueInDatabase[i]); + } + + // if layer or state number is invalid then value of testes state must be invalid + if (invalidLayerOrStateNumber && shortValueInDatabase != SKV_VALUE_INVALID) { PRINT(0,m, "ERROR: DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Succeding state has invalid layer (" << tmpLayerNumber << ")or state number (" << tmpStateNumber << "), but tested state is not marked as invalid."); goto errorInDatabase; } + // BUG: Does not work because, layer 101 is calculated before 105, although removing a stone does need this jump. + // if (!layerInDatabaseAndCompleted) { PRINT(0,m, "ERROR: DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Succeding state " << tmpStateNumber << " in an uncalculated layer " << tmpLayerNumber << "! Calc layer first!"); goto errorInDatabase; } + + // undo move + m->undo(threadNo, idPossibility[i], isOpponentLevel, pBackup, pPossibilities); + } + + // value possible? + switch (shortValueInDatabase) { + case SKV_VALUE_GAME_LOST : + + // all possible moves must be lost for the current player or won for the opponent + for (i=0; i max) { + max = subPlyInfos[i] + 1; + } + } + } + if (numPliesTillCurState>PLYINFO_VALUE_DRAWN) { + PRINT(0,m, "DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Knot value is LOST, but numPliesTillCurState is bigger than PLYINFO_MAX_VALUE."); + goto errorInDatabase; + } + if (numPliesTillCurState!=max) { + PRINT(0,m, "DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Number of needed plies is not maximal for LOST state."); + goto errorInDatabase; + } + break; + + case SKV_VALUE_GAME_WON : + + // at least one possible move must be lost for the opponent or won for the current player + for (i=0; iPLYINFO_VALUE_DRAWN) { + PRINT(0,m, "DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Knot value is WON, but numPliesTillCurState is bigger than PLYINFO_MAX_VALUE."); + goto errorInDatabase; + } + if (numPliesTillCurState!=min) { + PRINT(0,m, "DATABASE ERROR IN LAYER " << layerNumber << " AND STATE " << stateNumber << ": Number of needed plies is not minimal for WON state."); + goto errorInDatabase; + } + break; + + case SKV_VALUE_GAME_DRAWN: + + // all possible moves must be won for the opponent, lost for the current player or drawn + for (j=0,i=0; ipMiniMax; + unsigned int * idPossibility; + void * pPossibilities; + void * pBackup; + unsigned int curPoss; + float floatValue; + stateAdressStruct curState; + stateAdressStruct subState; + knotStruct knot; + twoBit shortKnotValue = SKV_VALUE_GAME_DRAWN; + curState.layerNumber = tlVars->layerNumber; + curState.stateNumber = index; + + // output + tlVars->statesProcessed++; + if (tlVars->statesProcessed % OUTPUT_EVERY_N_STATES == 0) { + m->numStatesProcessed += OUTPUT_EVERY_N_STATES; + PRINT(0, m, m->numStatesProcessed << " states of " << m->layerStats[curState.layerNumber].knotsInLayer << " tested"); + } + + // set state + if (m->setSituation(tlVars->curThreadNo, curState.layerNumber, curState.stateNumber)) { + m->getValueOfSituation(tlVars->curThreadNo, floatValue, shortKnotValue); + } else { + shortKnotValue = SKV_VALUE_INVALID; + } + + // get number of possiblities + idPossibility = m->getPossibilities(tlVars->curThreadNo, &knot.numPossibilities, &knot.isOpponentLevel, &pPossibilities); + + // unable to move + if (knot.numPossibilities == 0) { + if (shortKnotValue == SKV_VALUE_GAME_DRAWN) { + PRINT(0, m, "ERROR: Layer " << curState.layerNumber << " and state " << curState.stateNumber << ". setSituation() returned true, although getPossibilities() yields no possible moves."); + return m->falseOrStop(); + } + // moving is possible + } else { + if (shortKnotValue == SKV_VALUE_INVALID) { + PRINT(0, m, "ERROR: Moved from layer " << curState.layerNumber << " and state " << curState.stateNumber << " setSituation() returned false, although getPossibilities() yields some possible moves."); + return m->falseOrStop(); + } + + // check each possibility + for (curPoss=0; curPossmove(tlVars->curThreadNo, idPossibility[curPoss], knot.isOpponentLevel, &pBackup, pPossibilities); + + // get state number of succeding state + unsigned int i; + m->getLayerAndStateNumber(tlVars->curThreadNo, i, subState.stateNumber); + subState.layerNumber = i; + + // undo move + m->undo(tlVars->curThreadNo, idPossibility[curPoss], knot.isOpponentLevel, pBackup, pPossibilities); + + // state reached by move() must not be invalid + if (!m->setSituation(tlVars->curThreadNo, subState.layerNumber, subState.stateNumber)) { + PRINT(0, m, "ERROR: Moved from layer " << curState.layerNumber << " and state " << curState.stateNumber << " to invalid situation layer " << curState.layerNumber << " and state " << curState.stateNumber); + return m->falseOrStop(); + } + // set back to current state + m->setSituation(tlVars->curThreadNo, curState.layerNumber, curState.stateNumber); + } + } + return TM_RETURN_VALUE_OK; + +//errorInDatabase: + // terminate all threads + return TM_RETURN_VALUE_TERMINATE_ALL_THREADS; +} + +//----------------------------------------------------------------------------- +// Name: testIfSymStatesHaveSameValue() +// Desc: +//----------------------------------------------------------------------------- +bool miniMax::testIfSymStatesHaveSameValue(unsigned int layerNumber) +{ + // Locals + unsigned int threadNo = 0; + twoBit shortValueInDatabase; + twoBit shortValueOfSymState; + plyInfoVarType numPliesTillCurState; + plyInfoVarType numPliesTillSymState; + unsigned int stateNumber = 0; + unsigned int * symStateNumbers = NULL; + unsigned int numSymmetricStates; + unsigned int i; + + // database open? + if (hFileShortKnotValues == NULL || hFilePlyInfo == NULL) { + PRINT(0, this, "ERROR: Database files not open!"); + layerNumber = 0; + goto errorInDatabase; + } + + // layer completed ? + if (!layerStats[layerNumber].layerIsCompletedAndInFile) { + PRINT(0, this, "ERROR: Layer not in file!"); + layerNumber = 0; + goto errorInDatabase; + } + + // test if each state has symmetric states with the same value + PRINT(1, this, endl << "testIfSymmetricStatesHaveSameValue - TEST EACH STATE IN LAYER: " << layerNumber); + PRINT(1, this, (getOutputInformation(layerNumber))); + skvfHeader.completed = false; + + for (layerInDatabase=false, stateNumber=0; stateNumberid = beginningPlayer; + field.oppPlayer->id = (field.curPlayer->id == field.playerTwo) ? field.playerOne : field.playerTwo; + + winner = 0; + movesDone = 0; + playerOneKI = firstPlayerKI; + playerTwoKI = secondPlayerKI; + moveLogFrom = new unsigned int[MAX_NUM_MOVES]; + moveLogTo = new unsigned int[MAX_NUM_MOVES]; + + // remember initialField + field.copyField(&initialField); +} + +//----------------------------------------------------------------------------- +// Name: startSettingPhase() +// Desc: +//----------------------------------------------------------------------------- +bool muehle::startSettingPhase(muehleKI *firstPlayerKI, muehleKI *secondPlayerKI, int currentPlayer, bool settingPhase) +{ + beginNewGame(firstPlayerKI, secondPlayerKI, currentPlayer); + + field.settingPhase = settingPhase; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: setUpCalcPossibleMoves() +// Desc: Calculates and set the number of possible moves for the passed player considering the game state stored in the 'field' variable. +//----------------------------------------------------------------------------- +void muehle::setUpCalcPossibleMoves(playerStruct *player) +{ + // locals + unsigned int i, j , k, movingDirection; + + for (player->numPossibleMoves=0, i=0; iid) continue; + + // is destination free ? + if (field.field[j] != field.squareIsFree) continue; + + // when current player has only 3 stones he is allowed to spring his stone + if (player->numStones > 3 || field.settingPhase) { + + // determine moving direction + for (k=0, movingDirection=4; k<4; k++) if (field.connectedSquare[i][k] == j) movingDirection = k; + + // are both squares connected ? + if (movingDirection == 4) continue; + } + + // everything is ok + player->numPossibleMoves++; + }} +} + +//----------------------------------------------------------------------------- +// Name: setUpSetWarningAndMill() +// Desc: +//----------------------------------------------------------------------------- +void muehle::setUpSetWarningAndMill(unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour) +{ + // locals + int rowOwner = field.field[stone]; + + // mill closed ? + if (rowOwner != field.squareIsFree && field.field[firstNeighbour] == rowOwner && field.field[secondNeighbour] == rowOwner) { + + field.stonePartOfMill[stone]++; + field.stonePartOfMill[firstNeighbour]++; + field.stonePartOfMill[secondNeighbour]++; + } +} + +//----------------------------------------------------------------------------- +// Name: putStone() +// Desc: Put a stone onto the field during the setting phase. +//----------------------------------------------------------------------------- +bool muehle::putStone(unsigned int pos, int player) +{ + // locals + unsigned int i; + unsigned int numberOfMillsCurrentPlayer = 0, numberOfMillsOpponentPlayer = 0; + playerStruct *myPlayer = (player == field.curPlayer->id) ? field.curPlayer : field.oppPlayer; + + // check parameters + if (player != fieldStruct::playerOne && player != fieldStruct::playerTwo) return false; + if (pos >= fieldStruct::size) return false; + if (field.field[pos] != field.squareIsFree) return false; + + // set stone + field.field[pos] = player; + myPlayer->numStones++; + field.stonesSet++; + + // setting phase finished ? + if (field.stonesSet == 18) field.settingPhase = false; + + // calc possible moves + setUpCalcPossibleMoves(field.curPlayer); + setUpCalcPossibleMoves(field.oppPlayer); + + // zero + for (i=0; iid) numberOfMillsCurrentPlayer += field.stonePartOfMill[i]; + else numberOfMillsOpponentPlayer += field.stonePartOfMill[i]; + } + numberOfMillsCurrentPlayer /= 3; + numberOfMillsOpponentPlayer /= 3; + + // stonesSet & numStonesMissing + if (field.settingPhase) { +// ... This calculation is not correct! It is possible that some mills did not cause a stone removal. + field.curPlayer->numStonesMissing = numberOfMillsOpponentPlayer; + field.oppPlayer->numStonesMissing = numberOfMillsCurrentPlayer - field.stoneMustBeRemoved; + field.stonesSet = field.curPlayer->numStones + field.oppPlayer->numStones + field.curPlayer->numStonesMissing + field.oppPlayer->numStonesMissing; + } else { + field.stonesSet = 18; + field.curPlayer->numStonesMissing = 9 - field.curPlayer->numStones; + field.oppPlayer->numStonesMissing = 9 - field.oppPlayer->numStones; + } + + // when opponent is unable to move than current player has won + if ((!field.curPlayer->numPossibleMoves) && (!field.settingPhase) + && (!field.stoneMustBeRemoved) && (field.curPlayer->numStones > 3)) winner = field.oppPlayer->id; + else if ((field.curPlayer->numStones < 3) && (!field.settingPhase)) winner = field.oppPlayer->id; + else if ((field.oppPlayer->numStones < 3) && (!field.settingPhase)) winner = field.curPlayer->id; + else winner = 0; + + // everything is ok + return true; +} + +//----------------------------------------------------------------------------- +// Name: settingPhaseHasFinished() +// Desc: This function has to be called when the setting phase has finished. +//----------------------------------------------------------------------------- +bool muehle::settingPhaseHasFinished() +{ + // remember initialField + field.copyField(&initialField); + + return true; +} + +//----------------------------------------------------------------------------- +// Name: getField() +// Desc: Copy the current field state into the array 'pField'. +//----------------------------------------------------------------------------- +bool muehle::getField(int *pField) +{ + unsigned int index; + + // if no log is available than no game is in progress and field is invalid + if (moveLogFrom == NULL) return false; + + for (index=0; indexid == field.playerOne) return (playerOneKI == NULL) ? true : false; + else return (playerTwoKI == NULL) ? true : false; +} + +//----------------------------------------------------------------------------- +// Name: isOpponentPlayerHuman() +// Desc: Returns true if the opponent player is not assigned to an AI. +//----------------------------------------------------------------------------- +bool muehle::isOpponentPlayerHuman() +{ + if (field.oppPlayer->id == field.playerOne) return (playerOneKI == NULL) ? true : false; + else return (playerTwoKI == NULL) ? true : false; +} + +//----------------------------------------------------------------------------- +// Name: setKI() +// Desc: Assigns an AI to a player. +//----------------------------------------------------------------------------- +void muehle::setKI(int player, muehleKI *KI) +{ + if (player == field.playerOne) { playerOneKI = KI; } + if (player == field.playerTwo) { playerTwoKI = KI; } +} + +//----------------------------------------------------------------------------- +// Name: getChoiceOfSpecialKI() +// Desc: Returns the move the passed AI would do. +//----------------------------------------------------------------------------- +void muehle::getChoiceOfSpecialKI(muehleKI *KI, unsigned int *pushFrom, unsigned int *pushTo) +{ + fieldStruct theField; + *pushFrom = field.size; + *pushTo = field.size; + theField.createField(); + field.copyField(&theField); + if (KI != NULL && (field.settingPhase || field.curPlayer->numPossibleMoves > 0) && winner == 0) KI->play(&theField, pushFrom, pushTo); + theField.deleteField(); +} + +//----------------------------------------------------------------------------- +// Name: getComputersChoice() +// Desc: Returns the move the AI of the current player would do. +//----------------------------------------------------------------------------- +void muehle::getComputersChoice(unsigned int *pushFrom, unsigned int *pushTo) +{ + fieldStruct theField; + *pushFrom = field.size; + *pushTo = field.size; + theField.createField(); + field.copyField(&theField); + + if ((field.settingPhase || field.curPlayer->numPossibleMoves > 0) && winner == 0) { + if (field.curPlayer->id == field.playerOne) { if (playerOneKI != NULL) playerOneKI->play(&theField, pushFrom, pushTo); } + else { if (playerTwoKI != NULL) playerTwoKI->play(&theField, pushFrom, pushTo); } + } + + theField.deleteField(); +} + +//----------------------------------------------------------------------------- +// Name: isNormalMovePossible() +// Desc: 'Normal' in this context means, by moving the stone along a connection without jumping. +//----------------------------------------------------------------------------- +bool muehle::isNormalMovePossible(unsigned int from, unsigned int to, playerStruct *player) +{ + // locals + unsigned int movingDirection, i; + + // parameter ok ? + if (from >= field.size) return false; + if (to >= field.size) return false; + + // is stone from player ? + if (field.field[from] != player->id) return false; + + // is destination free ? + if (field.field[to] != field.squareIsFree) return false; + + // when current player has only 3 stones he is allowed to spring his stone + if (player->numStones > 3 || field.settingPhase) { + + // determine moving direction + for (i=0, movingDirection=4; i<4; i++) if (field.connectedSquare[from][i] == to) movingDirection = i; + + // are both squares connected ? + if (movingDirection == 4) return false; + } + + // everything is ok + return true; +} + +//----------------------------------------------------------------------------- +// Name: calcPossibleMoves() +// Desc: ... +//----------------------------------------------------------------------------- +void muehle::calcPossibleMoves(playerStruct *player) +{ + // locals + unsigned int i, j; + + // zero + for (i=0; iposTo[i] = field.size; + for (i=0; iposFrom[i] = field.size; + + // calc + for (player->numPossibleMoves=0, i=0; iposFrom[player->numPossibleMoves] = i; + player->posTo [player->numPossibleMoves] = j; + player->numPossibleMoves++; + }}} + + // stoneMoveAble + for (i=0; iid) field.stoneMoveAble[i][j] = isNormalMovePossible(i, field.connectedSquare[i][j], player); + else field.stoneMoveAble[i][j] = false; + }} +} + +//----------------------------------------------------------------------------- +// Name: setWarningAndMill() +// Desc: +//----------------------------------------------------------------------------- +void muehle::setWarningAndMill(unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour, bool isNewStone) +{ + // locals + int rowOwner = field.field[stone]; + unsigned int rowOwnerWarning = (rowOwner == field.playerOne) ? field.playerOneWarning : field.playerTwoWarning; + + // mill closed ? + if (rowOwner != field.squareIsFree && field.field[firstNeighbour] == rowOwner && field.field[secondNeighbour] == rowOwner) { + + field.stonePartOfMill[stone]++; + field.stonePartOfMill[firstNeighbour]++; + field.stonePartOfMill[secondNeighbour]++; + if (isNewStone) field.stoneMustBeRemoved = 1; + } + + //warning ? + if (rowOwner != field.squareIsFree && field.field[firstNeighbour ] == field.squareIsFree && field.field[secondNeighbour] == rowOwner) field.warnings[firstNeighbour ] |= rowOwnerWarning; + if (rowOwner != field.squareIsFree && field.field[secondNeighbour] == field.squareIsFree && field.field[firstNeighbour ] == rowOwner) field.warnings[secondNeighbour] |= rowOwnerWarning; +} + +//----------------------------------------------------------------------------- +// Name: updateMillsAndWarnings() +// Desc: +//----------------------------------------------------------------------------- +void muehle::updateMillsAndWarnings(unsigned int newStone) +{ + // locals + unsigned int i; + bool atLeastOneStoneRemoveAble; + + // zero + for (i=0; iid) atLeastOneStoneRemoveAble = true; + if (!atLeastOneStoneRemoveAble) field.stoneMustBeRemoved = 0; +} + +//----------------------------------------------------------------------------- +// Name: moveStone() +// Desc: +//----------------------------------------------------------------------------- +bool muehle::moveStone(unsigned int pushFrom, unsigned int pushTo) +{ + // avoid index override + if (movesDone >= MAX_NUM_MOVES) + return false; + + // is game still running ? + if (winner) + return false; + + // handle the remove of a stone + if (field.stoneMustBeRemoved) { + + // parameter ok ? + if (pushFrom >= field.size) + return false; + + // is it stone from the opponent ? + if (field.field[pushFrom] != field.oppPlayer->id) + return false; + + // is stone not part of mill? + if (field.stonePartOfMill[pushFrom]) + return false; + + // remove stone + moveLogFrom[movesDone] = pushFrom; + moveLogTo [movesDone] = field.size; + field.field[pushFrom] = field.squareIsFree; + field.oppPlayer->numStonesMissing++; + field.oppPlayer->numStones--; + field.stoneMustBeRemoved--; + movesDone++; + + // is the game finished ? + if ((field.oppPlayer->numStones < 3) && (!field.settingPhase)) winner = field.curPlayer->id; + + // update warnings & mills + updateMillsAndWarnings(field.size); + + // calc possibilities + calcPossibleMoves(field.curPlayer); + calcPossibleMoves(field.oppPlayer); + + // is opponent unable to move ? + if (field.oppPlayer->numPossibleMoves == 0 && !field.settingPhase) winner = field.curPlayer->id; + + // next player + if (!field.stoneMustBeRemoved) setNextPlayer(); + + // everything is ok + return true; + + // handle setting phase + } else if (field.settingPhase) { + + // parameter ok ? + if (pushTo >= field.size) + return false; + + // is destination free ? + if (field.field[pushTo] != field.squareIsFree) + return false; + + // set stone + moveLogFrom[movesDone] = field.size; + moveLogTo [movesDone] = pushTo; + field.field[pushTo] = field.curPlayer->id; + field.curPlayer->numStones++; + field.stonesSet++; + movesDone++; + + // update warnings & mills + updateMillsAndWarnings(pushTo); + + // calc possibilities + calcPossibleMoves(field.curPlayer); + calcPossibleMoves(field.oppPlayer); + + // setting phase finished ? + if (field.stonesSet == 18) field.settingPhase = false; + + // is opponent unable to move ? + if (field.oppPlayer->numPossibleMoves == 0 && !field.settingPhase) winner = field.curPlayer->id; + + // next player + if (!field.stoneMustBeRemoved) setNextPlayer(); + + // everything is ok + return true; + + // normal move + } else { + + // is move possible ? + if (!isNormalMovePossible(pushFrom, pushTo, field.curPlayer)) + return false; + + // move stone + moveLogFrom[movesDone] = pushFrom; + moveLogTo [movesDone] = pushTo; + field.field[pushFrom] = field.squareIsFree; + field.field[pushTo] = field.curPlayer->id; + movesDone++; + + // update warnings & mills + updateMillsAndWarnings(pushTo); + + // calc possibilities + calcPossibleMoves(field.curPlayer); + calcPossibleMoves(field.oppPlayer); + + // is opponent unable to move ? + if (field.oppPlayer->numPossibleMoves == 0 && !field.settingPhase) winner = field.curPlayer->id; + + // next player + if (!field.stoneMustBeRemoved) setNextPlayer(); + + // everything is ok + return true; + } +} + +//----------------------------------------------------------------------------- +// Name: setCurrentGameState() +// Desc: Set an arbitrary game state as the current one. +//----------------------------------------------------------------------------- +bool muehle::setCurrentGameState(fieldStruct *curState) +{ + curState->copyField(&field); + + winner = 0; + movesDone = 0; + + if ((field.curPlayer->numStones < 3) && (!field.settingPhase)) winner = field.oppPlayer->id; + if ((field.oppPlayer->numStones < 3) && (!field.settingPhase)) winner = field.curPlayer->id; + if ((field.curPlayer->numPossibleMoves == 0) && (!field.settingPhase)) winner = field.oppPlayer->id; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: compareWithField() +// Desc: Compares the current 'field' variable with the passed one. 'stoneMoveAble[]' is ignored. +//----------------------------------------------------------------------------- +bool muehle::compareWithField(fieldStruct *compareField) +{ + unsigned int i, j; + bool ret = true; + + if (!comparePlayers(field.curPlayer, compareField->curPlayer)) { cout << "error - curPlayer differs!" << endl; ret = false; } + if (!comparePlayers(field.oppPlayer, compareField->oppPlayer)) { cout << "error - oppPlayer differs!" << endl; ret = false; } + + if (field.stonesSet != compareField->stonesSet) { cout << "error - stonesSet differs!" << endl; ret = false; } + if (field.settingPhase != compareField->settingPhase) { cout << "error - settingPhase differs!" << endl; ret = false; } + if (field.stoneMustBeRemoved != compareField->stoneMustBeRemoved) { cout << "error - stoneMustBeRemoved differs!" << endl; ret = false; } + + for (i=0; ifield[i]) { cout << "error - field[] differs!" << endl; ret = false; } + if (field.warnings[i] != compareField->warnings[i]) { cout << "error - warnings[] differs!" << endl; ret = false; } + if (field.stonePartOfMill[i] != compareField->stonePartOfMill[i]) { cout << "error - stonePart[] differs!" << endl; ret = false; } + + for (j=0; j<4; j++) { + + if (field.connectedSquare[i][j] != compareField->connectedSquare[i][j]) { cout << "error - connectedSquare[] differs!" << endl; ret = false; } +// if (field.stoneMoveAble[i][j] != compareField->stoneMoveAble[i][j]) { cout << "error - stoneMoveAble differs!" << endl; ret = false; } + if (field.neighbour[i][j/2][j%2]!= compareField->neighbour[i][j/2][j%2]){ cout << "error - neighbour differs!" << endl; ret = false; } + }} + + return ret; +} + +//----------------------------------------------------------------------------- +// Name: comparePlayers() +// Desc: Compares the two passed players and returns false if they differ. +//----------------------------------------------------------------------------- +bool muehle::comparePlayers(playerStruct *playerA, playerStruct *playerB) +{ +// unsigned int i; + bool ret = true; + + if (playerA->numStonesMissing != playerB->numStonesMissing) { cout << "error - numStonesMissing differs!" << endl; ret = false; } + if (playerA->numStones != playerB->numStones) { cout << "error - numStones differs!" << endl; ret = false; } + if (playerA->id != playerB->id) { cout << "error - id differs!" << endl; ret = false; } + if (playerA->warning != playerB->warning) { cout << "error - warning differs!" << endl; ret = false; } + if (playerA->numPossibleMoves != playerB->numPossibleMoves) { cout << "error - numPossibleMoves differs!" << endl; ret = false; } + +// for (i=0; iposFrom[i] = playerB->posFrom[i]) return false; +// for (i=0; iposTo [i] = playerB->posTo [i]) return false; + + return ret; +} + +//----------------------------------------------------------------------------- +// Name: printField() +// Desc: Calls the printField() function of the current field. +// Prints the current game state on the screen. +//----------------------------------------------------------------------------- +void muehle::printField() +{ + field.printField(); +} + +//----------------------------------------------------------------------------- +// Name: undoLastMove() +// Desc: Sets the initial field as the current one and apply all (minus one) moves from the move history. +//----------------------------------------------------------------------------- +void muehle::undoLastMove(void) +{ + // locals + unsigned int *moveLogFrom_bak = new unsigned int[movesDone]; + unsigned int *moveLogTo_bak = new unsigned int[movesDone]; + unsigned int movesDone_bak = movesDone; + unsigned int i; + + // at least one move must be done + if (movesDone) { + + // make backup of log + for (i=0; inumStonesMissing - field.curPlayer->numStones; + numBlackStonesResting = fieldStruct::numStonesPerPlayer - field.oppPlayer->numStonesMissing - field.oppPlayer->numStones; + } else { + numWhiteStonesResting = fieldStruct::numStonesPerPlayer - field.oppPlayer->numStonesMissing - field.oppPlayer->numStones; + numBlackStonesResting = fieldStruct::numStonesPerPlayer - field.curPlayer->numStonesMissing - field.curPlayer->numStones; + } +} \ No newline at end of file diff --git a/src/perfect/muehle.h b/src/perfect/muehle.h new file mode 100644 index 00000000..70492463 --- /dev/null +++ b/src/perfect/muehle.h @@ -0,0 +1,90 @@ +/*********************************************************************\ + muehle.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#ifndef MUEHLE_H +#define MUEHLE_H + +#include +#include +#include +#include +#include "muehleKI.h" + +using namespace std; + +/*** Konstanten ******************************************************/ +#define MAX_NUM_MOVES 10000 + +/*** Makros ******************************************************/ +#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } +#define SAFE_DELETE_ARRAY(p) { if(p) { delete[] (p); (p)=NULL; } } + +/*** Klassen *********************************************************/ + +class muehle +{ +private: + // Variables + unsigned int *moveLogFrom, *moveLogTo, movesDone; // array containing the history of moves done + muehleKI *playerOneKI; // class-pointer to the AI of player one + muehleKI *playerTwoKI; // class-pointer to the AI of player two + fieldStruct field; // current field + fieldStruct initialField; // undo of the last move is done by setting the initial field und performing all moves saved in history + int winner; // playerId of the player who has won the game. zero if game is still running. + int beginningPlayer; // playerId of the player who makes the first move + + // Functions + void deleteArrays (); + void setNextPlayer (); + void calcPossibleMoves (playerStruct *player); + void updateMillsAndWarnings (unsigned int newStone); + bool isNormalMovePossible (unsigned int from, unsigned int to, playerStruct *player); + void setWarningAndMill (unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour, bool isNewStone); + +public: + // Constructor / destructor + muehle (); + ~muehle (); + + // Functions + void undoLastMove (); + void beginNewGame (muehleKI *firstPlayerKI, muehleKI *secondPlayerKI, int currentPlayer); + void setKI (int player, muehleKI *KI); + bool moveStone (unsigned int pushFrom, unsigned int pushTo); + void getComputersChoice (unsigned int *pushFrom, unsigned int *pushTo); + bool setCurrentGameState (fieldStruct *curState); + bool compareWithField (fieldStruct *compareField); + bool comparePlayers (playerStruct *playerA, playerStruct *playerB); + void printField (); + bool startSettingPhase (muehleKI *firstPlayerKI, muehleKI *secondPlayerKI, int currentPlayer, bool settingPhase); + bool putStone (unsigned int pos, int player); + bool settingPhaseHasFinished (); + void getChoiceOfSpecialKI (muehleKI *KI, unsigned int *pushFrom, unsigned int *pushTo); + void setUpCalcPossibleMoves (playerStruct *player); + void setUpSetWarningAndMill (unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour); + void calcNumberOfRestingStones (int &numWhiteStonesResting, int &numBlackStonesResting); + + // getter + void getLog (unsigned int &numMovesDone, unsigned int *from, unsigned int *to); + bool getField (int *pField); + bool isCurrentPlayerHuman (); + bool isOpponentPlayerHuman (); + bool inSettingPhase () { return field.settingPhase; } + unsigned int mustStoneBeRemoved () { return field.stoneMustBeRemoved; } + int getWinner () { return winner; } + int getCurrentPlayer () { return field.curPlayer->id; } + unsigned int getLastMoveFrom () { return (movesDone ? moveLogFrom[movesDone-1] : field.size); } + unsigned int getLastMoveTo () { return (movesDone ? moveLogTo [movesDone-1] : field.size); } + unsigned int getMovesDone () { return movesDone; } + unsigned int getNumStonesSet () { return field.stonesSet; } + int getBeginningPlayer () { return beginningPlayer; } + unsigned int getNumStonOfCurPlayer () { return field.curPlayer->numStones; } + unsigned int getNumStonOfOppPlayer () { return field.oppPlayer->numStones; } +}; + +#endif + diff --git a/src/perfect/muehleKI.cpp b/src/perfect/muehleKI.cpp new file mode 100644 index 00000000..69773ebe --- /dev/null +++ b/src/perfect/muehleKI.cpp @@ -0,0 +1,235 @@ +/********************************************************************* + muehleKI.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "muehleKI.h" + +using namespace std; + +//----------------------------------------------------------------------------- +// Name: printField() +// Desc: +//----------------------------------------------------------------------------- +void fieldStruct::printField() +{ + // locals + unsigned int index; + char c[fieldStruct::size]; + + for (index=0; indexfield[index]); + + cout << "current player : " << GetCharFromStone(this->curPlayer->id) << " has " << this->curPlayer->numStones << " stones\n"; + cout << "opponent player : " << GetCharFromStone(this->oppPlayer->id) << " has " << this->oppPlayer->numStones << " stones\n"; + cout << "Num Stones to be removed: " << this->stoneMustBeRemoved << "\n"; + cout << "setting phase : " << (this->settingPhase ? "true" : "false"); + cout << "\n"; + cout << "\n a-----b-----c " << c[0] << "-----" << c[1] << "-----" << c[2]; + cout << "\n | | | " << "| | |"; + cout << "\n | d---e---f | " << "| " << c[3] << "---" << c[4] << "---" << c[5] << " |"; + cout << "\n | | | | | " << "| | | | |"; + cout << "\n | | g-h-i | | " << "| | " << c[6] << "-" << c[7] << "-" << c[8] << " | |"; + cout << "\n | | | | | | | " << "| | | | | |"; + cout << "\n j-k-l m-n-o " << c[9] << "-" << c[10] << "-" << c[11] << " " << c[12] << "-" << c[13] << "-" << c[14]; + cout << "\n | | | | | | | " << "| | | | | |"; + cout << "\n | | p-q-r | | " << "| | " << c[15] << "-" << c[16] << "-" << c[17] << " | |"; + cout << "\n | | | | | " << "| | | | |"; + cout << "\n | s---t---u | " << "| " << c[18] << "---" << c[19] << "---" << c[20] << " |"; + cout << "\n | | | " << "| | |"; + cout << "\n v-----w-----x " << c[21] << "-----" << c[22] << "-----" << c[23]; + cout << "\n"; +} + +//----------------------------------------------------------------------------- +// Name: GetCharFromStone() +// Desc: +//----------------------------------------------------------------------------- +char fieldStruct::GetCharFromStone(int stone) +{ + switch (stone) + { + case fieldStruct::playerOne: return 'o'; + case fieldStruct::playerTwo: return 'x'; + case fieldStruct::playerOneWarning: return '1'; + case fieldStruct::playerTwoWarning: return '2'; + case fieldStruct::playerBothWarning: return '3'; + case fieldStruct::squareIsFree: return ' '; + } + return 'f'; +} + +//----------------------------------------------------------------------------- +// Name: copyField() +// Desc: Only copies the values without array creation. +//----------------------------------------------------------------------------- +void fieldStruct::copyField(fieldStruct *destination) +{ + unsigned int i, j; + + this->curPlayer->copyPlayer(destination->curPlayer); + this->oppPlayer->copyPlayer(destination->oppPlayer); + + destination->stonesSet = this->stonesSet; + destination->settingPhase = this->settingPhase; + destination->stoneMustBeRemoved = this->stoneMustBeRemoved; + + for (i=0; isize; i++) { + + destination->field[i] = this->field[i]; + destination->warnings[i] = this->warnings[i]; + destination->stonePartOfMill[i] = this->stonePartOfMill[i]; + + for (j=0; j<4; j++) { + + destination->connectedSquare[i][j] = this->connectedSquare[i][j]; + destination->stoneMoveAble[i][j] = this->stoneMoveAble[i][j]; + destination->neighbour[i][j/2][j%2] = this->neighbour[i][j/2][j%2]; + }} +} + +//----------------------------------------------------------------------------- +// Name: copyPlayer() +// Desc: Only copies the values without array creation. +//----------------------------------------------------------------------------- +void playerStruct::copyPlayer(playerStruct *destination) +{ + unsigned int i; + + destination->numStonesMissing = this->numStonesMissing; + destination->numStones = this->numStones; + destination->id = this->id; + destination->warning = this->warning; + destination->numPossibleMoves = this->numPossibleMoves; + + for (i=0; iposFrom[i] = this->posFrom[i]; + for (i=0; iposTo [i] = this->posTo [i]; +} + + +//----------------------------------------------------------------------------- +// Name: createField() +// Desc: Creates, but doesn't initialize, the arrays of the of the passed field structure. +//----------------------------------------------------------------------------- +void fieldStruct::createField() +{ + // locals + unsigned int i; + + curPlayer = new playerStruct; + oppPlayer = new playerStruct; + + curPlayer->id = playerOne; + stonesSet = 0; + stoneMustBeRemoved = 0; + settingPhase = true; + curPlayer->warning = (curPlayer->id == playerOne) ? playerOneWarning : playerTwoWarning; + oppPlayer->id = (curPlayer->id == playerOne) ? playerTwo : playerOne; + oppPlayer->warning = (curPlayer->id == playerOne) ? playerTwoWarning : playerOneWarning; + curPlayer->numStones = 0; + oppPlayer->numStones = 0; + curPlayer->numPossibleMoves = 0; + oppPlayer->numPossibleMoves = 0; + curPlayer->numStonesMissing = 0; + oppPlayer->numStonesMissing = 0; + + // zero + for (i=0; i +#include + +//using namespace std; + +/*** Konstanten ******************************************************/ +#define MAX_NUM_POS_MOVES (3 * 18) // not (9 * 4) = 36 since the possibilities with 3 stones are more +#define SAFE_DELETE(p) { if(p) { delete (p); (p)=NULL; } } + +/*** Klassen *********************************************************/ + +class playerStruct +{ +public: + int id; // static + unsigned int warning; // static + unsigned int numStones; // number of stones of this player on the field + unsigned int numStonesMissing; // number of stones, which where stolen by the opponent + unsigned int numPossibleMoves; // amount of possible moves + unsigned int posTo [MAX_NUM_POS_MOVES]; // target field position of a possible move + unsigned int posFrom[MAX_NUM_POS_MOVES]; // source field position of a possible move + + void copyPlayer (playerStruct *destination); +}; + +class fieldStruct +{ +public: + // constants + static const int squareIsFree = 0; // trivial + static const int playerOne = -1; // so rowOwner can be calculated easy + static const int playerTwo = 1; + static const int playerBlack = -1; // so rowOwner can be calculated easy + static const int playerWhite = 1; + static const unsigned int noWarning = 0; // so the bitwise or-operation can be applied, without interacting with playerOne & Two + static const unsigned int playerOneWarning = 2; + static const unsigned int playerTwoWarning = 4; + static const unsigned int playerBothWarning = 6; + static const unsigned int numStonesPerPlayer = 9; + static const unsigned int size = 24; // number of squares + static const int gameDrawn = 3; // only a nonzero value + + // variables + int field[size]; // one of the values above for each field position + unsigned int warnings[size]; // array containing the warnings for each field position + bool stoneMoveAble[size][4]; // true if stone can be moved in this direction + unsigned int stonePartOfMill[size]; // the number of mills, of which this stone is part of + unsigned int connectedSquare[size][4]; // static array containg the index of the neighbour or "size" + unsigned int neighbour[size][2][2]; // static array containing the two neighbours of each squares + unsigned int stonesSet; // number of stones set in the setting phase + bool settingPhase; // true if stonesSet < 18 + unsigned int stoneMustBeRemoved; // number of stones which must be removed by the current player + playerStruct *curPlayer, *oppPlayer; // pointers to the current and opponent player + + // useful functions + void printField (); + void copyField (fieldStruct *destination); + void createField (); + void deleteField (); + +private: + + // helper functions + char GetCharFromStone (int stone); + void setConnection (unsigned int index, int firstDirection, int secondDirection, int thirdDirection, int fourthDirection); + void setNeighbour (unsigned int index, unsigned int firstNeighbour0, unsigned int secondNeighbour0, unsigned int firstNeighbour1, unsigned int secondNeighbour1); +}; + +class muehleKI abstract +{ +protected: + fieldStruct dummyField; + +public: + // Constructor / destructor + muehleKI() { dummyField.createField(); }; + ~muehleKI() { dummyField.deleteField(); }; + + // Functions + virtual void play (fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo) = 0; +}; + +#endif diff --git a/src/perfect/perfect.vcxproj b/src/perfect/perfect.vcxproj new file mode 100644 index 00000000..54a91ecc --- /dev/null +++ b/src/perfect/perfect.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {edb1e279-1476-443b-84fa-150a5d3b5a10} + perfect + 10.0 + + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + Application + true + v142 + Unicode + + + Application + false + v142 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/perfect/perfect.vcxproj.filters b/src/perfect/perfect.vcxproj.filters new file mode 100644 index 00000000..9e8be309 --- /dev/null +++ b/src/perfect/perfect.vcxproj.filters @@ -0,0 +1,105 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/src/perfect/perfectKI.cpp b/src/perfect/perfectKI.cpp new file mode 100644 index 00000000..2afa5f9d --- /dev/null +++ b/src/perfect/perfectKI.cpp @@ -0,0 +1,2542 @@ +/********************************************************************* + perfectKI.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "perfectKI.h" + +unsigned int soTableTurnLeft[] = { + 2, 14, 23, + 5, 13, 20, + 8,12,17, + 1, 4, 7, 16,19,22, + 6,11,15, + 3, 10, 18, + 0, 9, 21 +}; + +unsigned int soTableDoNothing[]= { + 0, 1, 2, + 3, 4, 5, + 6, 7, 8, + 9,10,11, 12,13,14, + 15,16,17, + 18, 19, 20, +21, 22, 23 +}; + +unsigned int soTableMirrorHori[] = { +21, 22, 23, + 18, 19, 20, + 15,16,17, + 9,10,11, 12,13,14, + 6, 7, 8, + 3, 4, 5, + 0, 1, 2 +}; + +unsigned int soTableTurn180[] = { + 23, 22, 21, + 20, 19, 18, + 17,16,15, + 14,13,12, 11,10, 9, + 8, 7, 6, + 5, 4, 3, + 2, 1, 0 +}; + +unsigned int soTableInvert[] = { + 6, 7, 8, + 3, 4, 5, + 0, 1, 2, + 11,10, 9, 14,13,12, + 21,22,23, + 18, 19, 20, + 15, 16, 17 +}; + +unsigned int soTableInvMirHori[] = { + 15, 16, 17, + 18, 19, 20, + 21,22,23, + 11,10, 9, 14,13,12, + 0, 1, 2, + 3, 4, 5, + 6, 7, 8 +}; + +unsigned int soTableInvMirVert[] = { + 8, 7, 6, + 5, 4, 3, + 2, 1, 0, + 12,13,14, 9,10,11, + 23,22,21, + 20, 19, 18, + 17, 16, 15 +}; + +unsigned int soTableInvMirDiag1[] = { + 17, 12, 8, + 20, 13, 5, + 23,14, 2, + 16,19,22, 1, 4, 7, + 21, 9, 0, + 18, 10, 3, + 15, 11, 6 +}; + +unsigned int soTableInvMirDiag2[] = { + 6, 11, 15, + 3, 10, 18, + 0, 9,21, + 7, 4, 1, 22,19,16, + 2,14,23, + 5, 13, 20, + 8, 12, 17 +}; + +unsigned int soTableInvLeft[] = { + 8, 12, 17, + 5, 13, 20, + 2,14,23, + 7, 4, 1, 22,19,16, + 0, 9,21, + 3, 10, 18, + 6, 11, 15 +}; + +unsigned int soTableInvRight[] = { + 15, 11, 6, + 18, 10, 3, + 21, 9, 0, + 16,19,22, 1, 4, 7, + 23,14, 2, + 20, 13, 5, + 17, 12, 8 +}; + +unsigned int soTableInv180[] = { + 17, 16, 15, + 20, 19, 18, + 23,22,21, + 12,13,14, 9,10,11, + 2, 1, 0, + 5, 4, 3, + 8, 7, 6 +}; + +unsigned int soTableMirrorDiag1[] = { + 0, 9, 21, + 3, 10, 18, + 6,11,15, + 1, 4, 7, 16,19,22, + 8,12,17, + 5, 13, 20, + 2, 14, 23 +}; + +unsigned int soTableTurnRight[]= { + 21, 9, 0, + 18, 10, 3, + 15,11, 6, + 22,19,16, 7, 4, 1, + 17,12, 8, + 20, 13, 5, + 23, 14, 2 +}; + +unsigned int soTableMirrorVert[] = { + 2, 1, 0, + 5, 4, 3, + 8, 7, 6, + 14,13,12, 11,10, 9, + 17,16,15, + 20, 19, 18, + 23, 22, 21 +}; + +unsigned int soTableMirrorDiag2[] = { + 23, 14, 2, + 20, 13, 5, + 17,12, 8, + 22,19,16, 7, 4, 1, + 15,11, 6, + 18, 10, 3, + 21, 9, 0 +}; + +// define the four groups +unsigned int squareIndexGroupA[] = { 3, 5, 20, 18 }; +unsigned int squareIndexGroupB[] = { 4, 13, 19, 10 }; +unsigned int squareIndexGroupC[] = { 0, 2, 23, 21, 6, 8, 17, 15 }; +unsigned int squareIndexGroupD[] = { 1, 7, 14, 12, 22, 16, 9, 11 }; + +unsigned int fieldPosIsOfGroup[] = { GROUP_C, GROUP_D, GROUP_C, + GROUP_A, GROUP_B, GROUP_A, + GROUP_C,GROUP_D,GROUP_C, + GROUP_D,GROUP_B,GROUP_D, GROUP_D,GROUP_B,GROUP_D, + GROUP_C,GROUP_D,GROUP_C, + GROUP_A, GROUP_B, GROUP_A, + GROUP_C, GROUP_D, GROUP_C}; + +//----------------------------------------------------------------------------- +// Name: perfectKI() +// Desc: perfectKI class constructor +//----------------------------------------------------------------------------- +perfectKI::perfectKI(const char *directory) +{ + // loacls + unsigned int i, a, b, c, totalNumStones; + unsigned int wCD, bCD, wAB, bAB; + unsigned int stateAB, stateCD, symStateCD, layerNum; + unsigned int myField[fieldStruct::size]; + unsigned int symField[fieldStruct::size]; + unsigned int *originalStateCD_tmp[10][10]; + DWORD dwBytesRead = 0; + DWORD dwBytesWritten = 0; + HANDLE hFilePreCalcVars; + stringstream ssPreCalcVarsFilePath; + preCalcedVarsFileHeaderStruct preCalcVarsHeader; + + // + threadVars = new threadVarsStruct[getNumThreads()]; + for (unsigned int curThread=0; curThread numSquaresGroupA + numSquaresGroupB) continue; + originalStateAB[a][b] = new unsigned int[anzahlStellungenAB[a][b]]; + ReadFile(hFilePreCalcVars, originalStateAB[a][b], sizeof(unsigned int) * anzahlStellungenAB[a][b], &dwBytesRead, NULL); + }} + + // process originalStateCD[][] + for (a=0; a<=NUM_STONES_PER_PLAYER; a++) { for (b=0; b<=NUM_STONES_PER_PLAYER; b++) { + if (a + b > numSquaresGroupC + numSquaresGroupD) continue; + originalStateCD[a][b] = new unsigned int[anzahlStellungenCD[a][b]]; + ReadFile(hFilePreCalcVars, originalStateCD[a][b], sizeof(unsigned int) * anzahlStellungenCD[a][b], &dwBytesRead, NULL); + }} + + // calculate vars and save into file + } else { + + // calc mOverN + for (a=0; a<=fieldStruct::size; a++) { for (b=0; b<=fieldStruct::size; b++) { + mOverN[a][b] = (unsigned int) mOverN_Function(a,b); + }} + + // reset + for (i=0; i numSquaresGroupA + numSquaresGroupB) continue; + + anzahlStellungenAB[a][b] = mOverN[numSquaresGroupA + numSquaresGroupB][a] * mOverN[numSquaresGroupA + numSquaresGroupB - a][b]; + originalStateAB [a][b] = new unsigned int[anzahlStellungenAB[a][b]]; + anzahlStellungenAB[a][b] = 0; + }} + + // mark all indexCD as not indexed + for (stateAB=0; stateAB numSquaresGroupA + numSquaresGroupB) continue; + + // mark original state + indexAB [stateAB] = anzahlStellungenAB[a][b]; + originalStateAB [a][b][anzahlStellungenAB[a][b]] = stateAB; + + // state counter + anzahlStellungenAB[a][b]++; + } + } + + // group C&D // + + // reserve memory + for (a=0; a<=NUM_STONES_PER_PLAYER; a++) { for (b=0; b<=NUM_STONES_PER_PLAYER; b++) { + if (a + b > numSquaresGroupC + numSquaresGroupD) continue; + originalStateCD_tmp[a][b] = new unsigned int[mOverN[numSquaresGroupC+numSquaresGroupD][a] * mOverN[numSquaresGroupC+numSquaresGroupD-a][b]]; + anzahlStellungenCD [a][b] = 0; + }} + + // mark all indexCD as not indexed + memset(indexCD, NOT_INDEXED, 4 * MAX_ANZ_STELLUNGEN_C*MAX_ANZ_STELLUNGEN_D); + + for (stateCD=0; stateCD numSquaresGroupC + numSquaresGroupD) continue; + if (a > NUM_STONES_PER_PLAYER) continue; + if (b > NUM_STONES_PER_PLAYER) continue; + + // mark original state + indexCD [stateCD] = anzahlStellungenCD[a][b]; + symmetryOperationCD [stateCD] = SO_DO_NOTHING; + originalStateCD_tmp [a][b][anzahlStellungenCD[a][b]] = stateCD; + + // mark all symmetric states + for (i=0; i numSquaresGroupC + numSquaresGroupD) continue; + originalStateCD[a][b] = new unsigned int[anzahlStellungenCD[a][b]]; + for (i=0; iNUM_STONES_PER_PLAYER) continue; + if (b>NUM_STONES_PER_PLAYER) continue; + if (a+b != totalNumStones) continue; + + layerIndex[LAYER_INDEX_MOVING_PHASE][a][b] = layerNum; + layer[layerNum].numWhiteStones = a; + layer[layerNum].numBlackStones = b; + layer[layerNum].numSubLayers = 0; + + for (wCD=0; wCD<=layer[layerNum].numWhiteStones; wCD++) { for (bCD=0; bCD<=layer[layerNum].numBlackStones; bCD++) { + + // calc number of white and black stones for group A&B + wAB = layer[layerNum].numWhiteStones - wCD; + bAB = layer[layerNum].numBlackStones - bCD; + + // conditions + if (wCD + wAB != layer[layerNum].numWhiteStones) continue; + if (bCD + bAB != layer[layerNum].numBlackStones) continue; + if (wAB + bAB > numSquaresGroupA + numSquaresGroupB) continue; + if (wCD + bCD > numSquaresGroupC + numSquaresGroupD) continue; + + if (layer[layerNum].numSubLayers > 0) { + layer[layerNum].subLayer[layer[layerNum].numSubLayers].maxIndex = layer[layerNum].subLayer[layer[layerNum].numSubLayers - 1].maxIndex + anzahlStellungenAB[wAB][bAB] * anzahlStellungenCD[wCD][bCD]; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].minIndex = layer[layerNum].subLayer[layer[layerNum].numSubLayers - 1].maxIndex + 1; + } else { + layer[layerNum].subLayer[layer[layerNum].numSubLayers].maxIndex = anzahlStellungenAB[wAB][bAB] * anzahlStellungenCD[wCD][bCD] - 1; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].minIndex = 0; + } + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numBlackStonesGroupAB = bAB; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numBlackStonesGroupCD = bCD; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numWhiteStonesGroupAB = wAB; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numWhiteStonesGroupCD = wCD; + layer[layerNum].subLayerIndexAB[wAB][bAB] = layer[layerNum].numSubLayers; + layer[layerNum].subLayerIndexCD[wCD][bCD] = layer[layerNum].numSubLayers; + layer[layerNum].numSubLayers++; + }} + layerNum++; + }} + } + + // setting phase + for (totalNumStones=0, layerNum=NUM_LAYERS-1; totalNumStones<=2*NUM_STONES_PER_PLAYER; totalNumStones++) { + for (a=0; a<=totalNumStones; a++) { for (b=0; b<=totalNumStones-a; b++) { + if (a > NUM_STONES_PER_PLAYER) continue; + if (b > NUM_STONES_PER_PLAYER) continue; + if (a+b != totalNumStones) continue; + layer[layerNum].numWhiteStones = a; + layer[layerNum].numBlackStones = b; + layerIndex[LAYER_INDEX_SETTING_PHASE][a][b] = layerNum; + layer[layerNum].numSubLayers = 0; + + for (wCD=0; wCD<=layer[layerNum].numWhiteStones; wCD++) { for (bCD=0; bCD<=layer[layerNum].numBlackStones; bCD++) { + + // calc number of white and black stones for group A&B + wAB = layer[layerNum].numWhiteStones - wCD; + bAB = layer[layerNum].numBlackStones - bCD; + + // conditions + if (wCD + wAB != layer[layerNum].numWhiteStones) continue; + if (bCD + bAB != layer[layerNum].numBlackStones) continue; + if (wAB + bAB > numSquaresGroupA + numSquaresGroupB) continue; + if (wCD + bCD > numSquaresGroupC + numSquaresGroupD) continue; + + if (layer[layerNum].numSubLayers > 0) { + layer[layerNum].subLayer[layer[layerNum].numSubLayers].maxIndex = layer[layerNum].subLayer[layer[layerNum].numSubLayers - 1].maxIndex + anzahlStellungenAB[wAB][bAB] * anzahlStellungenCD[wCD][bCD]; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].minIndex = layer[layerNum].subLayer[layer[layerNum].numSubLayers - 1].maxIndex + 1; + } else { + layer[layerNum].subLayer[layer[layerNum].numSubLayers].maxIndex = anzahlStellungenAB[wAB][bAB] * anzahlStellungenCD[wCD][bCD] - 1; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].minIndex = 0; + } + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numBlackStonesGroupAB = bAB; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numBlackStonesGroupCD = bCD; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numWhiteStonesGroupAB = wAB; + layer[layerNum].subLayer[layer[layerNum].numSubLayers].numWhiteStonesGroupCD = wCD; + layer[layerNum].subLayerIndexAB[wAB][bAB] = layer[layerNum].numSubLayers; + layer[layerNum].subLayerIndexCD[wCD][bCD] = layer[layerNum].numSubLayers; + layer[layerNum].numSubLayers++; + }} + layerNum--; + }} + } + + // write vars into file + preCalcVarsHeader.sizeInBytes = sizeof(preCalcedVarsFileHeaderStruct); + + WriteFile(hFilePreCalcVars, &preCalcVarsHeader, preCalcVarsHeader.sizeInBytes, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, layer, sizeof(layerStruct) *NUM_LAYERS, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, layerIndex, sizeof(unsigned int) *2*NUM_STONES_PER_PLAYER_PLUS_ONE*NUM_STONES_PER_PLAYER_PLUS_ONE, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, anzahlStellungenAB, sizeof(unsigned int) *NUM_STONES_PER_PLAYER_PLUS_ONE*NUM_STONES_PER_PLAYER_PLUS_ONE, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, anzahlStellungenCD, sizeof(unsigned int) *NUM_STONES_PER_PLAYER_PLUS_ONE*NUM_STONES_PER_PLAYER_PLUS_ONE, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, indexAB, sizeof(unsigned int) *MAX_ANZ_STELLUNGEN_A*MAX_ANZ_STELLUNGEN_B, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, indexCD, sizeof(unsigned int) *MAX_ANZ_STELLUNGEN_C*MAX_ANZ_STELLUNGEN_D, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, symmetryOperationCD, sizeof(unsigned char) *MAX_ANZ_STELLUNGEN_C*MAX_ANZ_STELLUNGEN_D, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, powerOfThree, sizeof(unsigned int) *(numSquaresGroupC+numSquaresGroupD), &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, symmetryOperationTable, sizeof(unsigned int) *fieldStruct::size*NUM_SYM_OPERATIONS, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, reverseSymOperation, sizeof(unsigned int) *NUM_SYM_OPERATIONS, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, concSymOperation, sizeof(unsigned int) *NUM_SYM_OPERATIONS*NUM_SYM_OPERATIONS, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, mOverN, sizeof(unsigned int) *(fieldStruct::size+1)*(fieldStruct::size+1), &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, valueOfMove, sizeof(unsigned char) *fieldStruct::size*fieldStruct::size, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, plyInfoForOutput, sizeof(plyInfoVarType) *fieldStruct::size*fieldStruct::size, &dwBytesWritten, NULL); + WriteFile(hFilePreCalcVars, incidencesValuesSubMoves,sizeof(unsigned int) *4*fieldStruct::size*fieldStruct::size, &dwBytesWritten, NULL); + + // process originalStateAB[][] + for (a=0; a<=NUM_STONES_PER_PLAYER; a++) { for (b=0; b<=NUM_STONES_PER_PLAYER; b++) { + if (a + b > numSquaresGroupA + numSquaresGroupB) continue; + WriteFile(hFilePreCalcVars, originalStateAB[a][b], sizeof(unsigned int) *anzahlStellungenAB[a][b], &dwBytesWritten, NULL); + }} + + // process originalStateCD[][] + for (a=0; a<=NUM_STONES_PER_PLAYER; a++) { for (b=0; b<=NUM_STONES_PER_PLAYER; b++) { + if (a + b > numSquaresGroupC + numSquaresGroupD) continue; + WriteFile(hFilePreCalcVars, originalStateCD[a][b], sizeof(unsigned int) *anzahlStellungenCD[a][b], &dwBytesWritten, NULL); + }} + } + + // Close File + CloseHandle(hFilePreCalcVars); +} + +//----------------------------------------------------------------------------- +// Name: ~perfectKI() +// Desc: perfectKI class destructor +//----------------------------------------------------------------------------- +perfectKI::~perfectKI() +{ + // locals + unsigned int curThread; + + // release memory + for (curThread=0; curThreaddeleteField(); + } + SAFE_DELETE_ARRAY(threadVars); +} + +//----------------------------------------------------------------------------- +// Name: play() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::play(fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo) +{ + // ... trick 17 + theField->copyField(&dummyField); + + // locals + threadVars[0].field = theField; + threadVars[0].ownId = threadVars[0].field->curPlayer->id; + unsigned int bestChoice, i; + + // reset + for (i=0; isettingPhase) threadVars[0].depthOfFullTree = 2; + else threadVars[0].depthOfFullTree = 2; + + // current state already calculated? + if (isCurrentStateInDatabase(0)) { + cout << "perfectKI is using database!\n\n\n"; + threadVars[0].depthOfFullTree = 3; + } else { + cout << "perfectKI is thinking thinking with a depth of " << threadVars[0].depthOfFullTree << " steps!\n\n\n"; + } + + // start the miniMax-algorithmn + possibilityStruct *rootPossibilities = (possibilityStruct*) getBestChoice(threadVars[0].depthOfFullTree, &bestChoice, MAX_NUM_POS_MOVES); + + // decode the best choice + if (threadVars[0].field->stoneMustBeRemoved) { *pushFrom = bestChoice; *pushTo = 0; } + else if (threadVars[0].field->settingPhase) { *pushFrom = 0; *pushTo = bestChoice; } + else { *pushFrom = rootPossibilities->from[bestChoice]; + *pushTo = rootPossibilities->to [bestChoice]; } + + // release memory + threadVars[0].field = &dummyField; +} + +//----------------------------------------------------------------------------- +// Name: prepareDatabaseCalculation() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::prepareDatabaseCalculation() +{ + // only prepare layers? + unsigned int curThread; + + // create a temporary field + for (curThread=0; curThreadcreateField(); + setOpponentLevel(curThread, false); + } + + // open database file + openDatabase(databaseDirectory.c_str(), MAX_NUM_POS_MOVES); +} + +//----------------------------------------------------------------------------- +// Name: wrapUpDatabaseCalculation() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::wrapUpDatabaseCalculation(bool calculationAborted) +{ + // locals + unsigned int curThread; + + // release memory + for (curThread=0; curThreaddeleteField(); + SAFE_DELETE(threadVars[curThread].field); + threadVars[curThread].field = &dummyField; + } +} + +//----------------------------------------------------------------------------- +// Name: testLayers() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::testLayers(unsigned int startTestFromLayer, unsigned int endTestAtLayer) +{ + // locals + unsigned int curLayer; + bool result = true; + + for (curLayer=startTestFromLayer; curLayer<=endTestAtLayer; curLayer++) { + closeDatabase(); + if (!openDatabase(databaseDirectory.c_str(), MAX_NUM_POS_MOVES)) result = false; + if (!testIfSymStatesHaveSameValue(curLayer)) result = false; + if (!testLayer(curLayer)) result = false; + unloadAllLayers(); + unloadAllPlyInfos(); + closeDatabase(); + } + return result; +} + +//----------------------------------------------------------------------------- +// Name: setDatabasePath() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::setDatabasePath(const char *directory) +{ + if (directory == NULL) { + return false; + } else { + cout << "Path to database set to: " << directory << endl; + databaseDirectory.assign(directory); + return true; + } +} + +//----------------------------------------------------------------------------- +// Name: prepareBestChoiceCalculation() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::prepareBestChoiceCalculation() +{ + for (unsigned int curThread=0; curThreadsize; i++) { + if (field->field[i] == field->oppPlayer->id && field->stonePartOfMill[i] == 0) { + stoneCanBeRemoved = true; + break; + } + } + + // possibilities with cut off + for ((*numPossibilities) = 0, i=0; isize; i++) { + + // move possible ? + if (field->field[i] == field->squareIsFree) { + + // check if a mill is beeing closed + numberOfMillsBeeingClosed = 0; + if (field->curPlayer->id == field->field[field->neighbour[i][0][0]] && field->curPlayer->id == field->field[field->neighbour[i][0][1]]) numberOfMillsBeeingClosed++; + if (field->curPlayer->id == field->field[field->neighbour[i][1][0]] && field->curPlayer->id == field->field[field->neighbour[i][1][1]]) numberOfMillsBeeingClosed++; + + // Version 15: don't allow to close two mills at once + // Version 25: don't allow to close a mill, although no stone can be removed from the opponent + if ((numberOfMillsBeeingClosed < 2) && (numberOfMillsBeeingClosed==0 || stoneCanBeRemoved)) { + idPossibility[*numPossibilities] = i; + (*numPossibilities)++; + } + + } + } + + // possibility code is simple + if (pPossibilities != NULL) *pPossibilities = NULL; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossNormalMove() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * perfectKI::threadVarsStruct::getPossNormalMove(unsigned int *numPossibilities, void **pPossibilities) +{ + // locals + unsigned int from, to, dir; + unsigned int *idPossibility = &idPossibilities[curSearchDepth * MAX_NUM_POS_MOVES]; + possibilityStruct *possibility = &possibilities [curSearchDepth]; + + // if he is not allowed to spring + if (field->curPlayer->numStones > 3) { + + for ((*numPossibilities) = 0, from=0; from < field->size; from++) { for (dir=0; dir<4; dir++) { + + // destination + to = field->connectedSquare[from][dir]; + + // move possible ? + if (to < field->size && field->field[from] == field->curPlayer->id && field->field[to] == field->squareIsFree) { + + // stone is moveable + idPossibility[*numPossibilities] = *numPossibilities; + possibility->from[*numPossibilities] = from; + possibility->to[*numPossibilities] = to; + (*numPossibilities)++; + + // current player is allowed to spring + }}}} else if (field->curPlayer->numStones == 3) { + + for ((*numPossibilities) = 0, from=0; from < field->size; from++) { for (to=0; to < field->size; to++) { + + // move possible ? + if (field->field[from] == field->curPlayer->id && field->field[to] == field->squareIsFree && *numPossibilities < MAX_NUM_POS_MOVES) { + + // stone is moveable + idPossibility[*numPossibilities] = *numPossibilities; + possibility->from[*numPossibilities] = from; + possibility->to[*numPossibilities] = to; + (*numPossibilities)++; + }}}} else { + *numPossibilities = 0; + } + + // pass possibilities + if (pPossibilities != NULL) *pPossibilities = (void*)possibility; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossStoneRemove() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * perfectKI::threadVarsStruct::getPossStoneRemove(unsigned int *numPossibilities, void **pPossibilities) +{ + // locals + unsigned int i; + unsigned int *idPossibility = &idPossibilities[curSearchDepth * MAX_NUM_POS_MOVES]; + + // possibilities with cut off + for ((*numPossibilities) = 0, i=0; isize; i++) { + + // move possible ? + if (field->field[i] == field->oppPlayer->id && !field->stonePartOfMill[i]) { + + idPossibility[*numPossibilities] = i; + (*numPossibilities)++; + } + } + + // possibility code is simple + if (pPossibilities != NULL) *pPossibilities = NULL; + + return idPossibility; +} + +//----------------------------------------------------------------------------- +// Name: getPossibilities() +// Desc: +//----------------------------------------------------------------------------- +unsigned int * perfectKI::getPossibilities(unsigned int threadNo, unsigned int *numPossibilities, bool *opponentsMove, void **pPossibilities) +{ + // locals + bool aStoneCanBeRemovedFromCurPlayer = 0; + unsigned int numberOfMillsCurrentPlayer = 0; + unsigned int numberOfMillsOpponentPlayer = 0; + unsigned int i; + + // set opponentsMove + threadVarsStruct * tv = &threadVars[threadNo]; + *opponentsMove = (tv->field->curPlayer->id == tv->ownId) ? false : true; + + // count completed mills + for (i=0; ifield->field[i] == tv->field->curPlayer->id) numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[i]; + else numberOfMillsOpponentPlayer += tv->field->stonePartOfMill[i]; + if (tv->field->stonePartOfMill[i] == 0 && tv->field->field[i] == tv->field->curPlayer->id) aStoneCanBeRemovedFromCurPlayer = true; + } + numberOfMillsCurrentPlayer /= 3; + numberOfMillsOpponentPlayer /= 3; + + // When game has ended of course nothing happens any more + if (tv->gameHasFinished || !tv->fieldIntegrityOK(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, aStoneCanBeRemovedFromCurPlayer)) { + *numPossibilities = 0; + return 0; + // look what is to do + } else { + if (tv->field->stoneMustBeRemoved) return tv->getPossStoneRemove (numPossibilities, pPossibilities); + else if (tv->field->settingPhase) return tv->getPossSettingPhase (numPossibilities, pPossibilities); + else return tv->getPossNormalMove (numPossibilities, pPossibilities); + } +} + +//----------------------------------------------------------------------------- +// Name: getValueOfSituation() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getValueOfSituation(unsigned int threadNo, float &floatValue, twoBit &shortValue) +{ + threadVarsStruct * tv = &threadVars[threadNo]; + floatValue = tv->floatValue; + shortValue = tv->shortValue; +} + +//----------------------------------------------------------------------------- +// Name: deletePossibilities() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::deletePossibilities(unsigned int threadNo, void *pPossibilities) +{ +} + +//----------------------------------------------------------------------------- +// Name: undo() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::undo(unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void *pBackup, void *pPossibilities) +{ + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + backupStruct *oldState = (backupStruct*)pBackup; + + // reset old value + tv->floatValue = oldState->floatValue; + tv->shortValue = oldState->shortValue; + tv->gameHasFinished = oldState->gameHasFinished; + tv->curSearchDepth--; + + tv->field->curPlayer = oldState->curPlayer; + tv->field->oppPlayer = oldState->oppPlayer; + tv->field->curPlayer->numStones = oldState->curNumStones; + tv->field->oppPlayer->numStones = oldState->oppNumStones; + tv->field->curPlayer->numStonesMissing = oldState->curMissStones; + tv->field->oppPlayer->numStonesMissing = oldState->oppMissStones; + tv->field->curPlayer->numPossibleMoves = oldState->curPosMoves; + tv->field->oppPlayer->numPossibleMoves = oldState->oppPosMoves; + tv->field->settingPhase = oldState->settingPhase; + tv->field->stonesSet = oldState->stonesSet; + tv->field->stoneMustBeRemoved = oldState->stoneMustBeRemoved; + tv->field->field[oldState->from] = oldState->fieldFrom; + tv->field->field[oldState->to ] = oldState->fieldTo; + + // very expensive + for (int i=0; ifield->size; i++) { + tv->field->stonePartOfMill[i] = oldState->stonePartOfMill[i]; + } +} + +//----------------------------------------------------------------------------- +// Name: setWarning() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::setWarning(unsigned int stoneOne, unsigned int stoneTwo, unsigned int stoneThree) +{ + // if all 3 fields are occupied by current player than he closed a mill + if (field->field[stoneOne] == field->curPlayer->id && field->field[stoneTwo] == field->curPlayer->id && field->field[stoneThree] == field->curPlayer->id) { + field->stonePartOfMill[stoneOne ]++; + field->stonePartOfMill[stoneTwo ]++; + field->stonePartOfMill[stoneThree]++; + field->stoneMustBeRemoved = 1; + } + + // is a mill destroyed ? + if (field->field[stoneOne] == field->squareIsFree && field->stonePartOfMill[stoneOne] && field->stonePartOfMill[stoneTwo] && field->stonePartOfMill[stoneThree]) { + field->stonePartOfMill[stoneOne ]--; + field->stonePartOfMill[stoneTwo ]--; + field->stonePartOfMill[stoneThree]--; + } +} + +//----------------------------------------------------------------------------- +// Name: updateWarning() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::updateWarning(unsigned int firstStone, unsigned int secondStone) +{ + // set warnings + if (firstStone < field->size) this->setWarning(firstStone, field->neighbour[firstStone][0][0], field->neighbour[firstStone][0][1]); + if (firstStone < field->size) this->setWarning(firstStone, field->neighbour[firstStone][1][0], field->neighbour[firstStone][1][1]); + + if (secondStone < field->size) this->setWarning(secondStone, field->neighbour[secondStone][0][0], field->neighbour[secondStone][0][1]); + if (secondStone < field->size) this->setWarning(secondStone, field->neighbour[secondStone][1][0], field->neighbour[secondStone][1][1]); + + // no stone must be removed if each belongs to a mill + unsigned int i; + bool atLeastOneStoneRemoveAble = false; + if (field->stoneMustBeRemoved) for (i=0; isize; i++) if (field->stonePartOfMill[i] == 0 && field->field[i] == field->oppPlayer->id) { atLeastOneStoneRemoveAble = true; break; } + if (!atLeastOneStoneRemoveAble) field->stoneMustBeRemoved = 0; +} + +//----------------------------------------------------------------------------- +// Name: updatePossibleMoves() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::updatePossibleMoves(unsigned int stone, playerStruct *stoneOwner, bool stoneRemoved, unsigned int ignoreStone) +{ + // locals + unsigned int neighbor, direction; + + // look into every direction + for (direction=0; direction<4; direction++) { + + neighbor = field->connectedSquare[stone][direction]; + + // neighbor must exist + if (neighbor < field->size) { + + // relevant when moving from one square to another connected square + if (ignoreStone == neighbor) continue; + + // if there is no neighbour stone than it only affects the actual stone + if (field->field[neighbor] == field->squareIsFree) { + + if (stoneRemoved) stoneOwner->numPossibleMoves--; + else stoneOwner->numPossibleMoves++; + + // if there is a neighbour stone than it effects only this one + } else if (field->field[neighbor] == field->curPlayer->id) { + + if (stoneRemoved) field->curPlayer->numPossibleMoves++; + else field->curPlayer->numPossibleMoves--; + + } else { + + if (stoneRemoved) field->oppPlayer->numPossibleMoves++; + else field->oppPlayer->numPossibleMoves--; + } + }} + + // only 3 stones resting + if (field->curPlayer->numStones <= 3 && !field->settingPhase) field->curPlayer->numPossibleMoves = field->curPlayer->numStones * (field->size - field->curPlayer->numStones - field->oppPlayer->numStones); + if (field->oppPlayer->numStones <= 3 && !field->settingPhase) field->oppPlayer->numPossibleMoves = field->oppPlayer->numStones * (field->size - field->curPlayer->numStones - field->oppPlayer->numStones); +} + +//----------------------------------------------------------------------------- +// Name: setStone() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::setStone(unsigned int to, backupStruct *backup) +{ + // backup + backup->from = field->size; + backup->to = to; + backup->fieldFrom = field->size; + backup->fieldTo = field->field[to]; + + // set stone into field + field->field[to] = field->curPlayer->id; + field->curPlayer->numStones++; + field->stonesSet++; + + // setting phase finished ? + if (field->stonesSet == 18) field->settingPhase = false; + + // update possible moves + updatePossibleMoves(to, field->curPlayer, false, field->size); + + // update warnings + updateWarning(to, field->size); +} + +//----------------------------------------------------------------------------- +// Name: normalMove() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::normalMove(unsigned int from, unsigned int to, backupStruct *backup) +{ + // backup + backup->from = from; + backup->to = to; + backup->fieldFrom = field->field[from]; + backup->fieldTo = field->field[to ]; + + // set stone into field + field->field[from] = field->squareIsFree; + field->field[to] = field->curPlayer->id; + + // update possible moves + updatePossibleMoves(from, field->curPlayer, true, to); + updatePossibleMoves(to, field->curPlayer, false, from); + + // update warnings + updateWarning(from, to); +} + +//----------------------------------------------------------------------------- +// Name: removeStone() +// Desc: +//----------------------------------------------------------------------------- +inline void perfectKI::threadVarsStruct::removeStone(unsigned int from, backupStruct *backup) +{ +// backup + backup->from = from; + backup->to = field->size; + backup->fieldFrom = field->field[from]; + backup->fieldTo = field->size; + + // remove stone + field->field[from] = field->squareIsFree; + field->oppPlayer->numStones--; + field->oppPlayer->numStonesMissing++; + field->stoneMustBeRemoved--; + + // update possible moves + updatePossibleMoves(from, field->oppPlayer, true, field->size); + + // update warnings + updateWarning(from, field->size); + + // end of game ? + if ((field->oppPlayer->numStones < 3) && (!field->settingPhase)) gameHasFinished = true; +} + +//----------------------------------------------------------------------------- +// Name: move() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::move(unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void **pBackup, void *pPossibilities) +{ + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + backupStruct *oldState = &tv->oldStates[tv->curSearchDepth]; + possibilityStruct *tmpPossibility = (possibilityStruct*) pPossibilities; + playerStruct *tmpPlayer; + unsigned int i; + + // calculate place of stone + *pBackup = (void*) oldState; + oldState->floatValue = tv->floatValue; + oldState->shortValue = tv->shortValue; + oldState->gameHasFinished = tv->gameHasFinished; + oldState->curPlayer = tv->field->curPlayer; + oldState->oppPlayer = tv->field->oppPlayer; + oldState->curNumStones = tv->field->curPlayer->numStones; + oldState->oppNumStones = tv->field->oppPlayer->numStones; + oldState->curPosMoves = tv->field->curPlayer->numPossibleMoves; + oldState->oppPosMoves = tv->field->oppPlayer->numPossibleMoves; + oldState->curMissStones = tv->field->curPlayer->numStonesMissing; + oldState->oppMissStones = tv->field->oppPlayer->numStonesMissing; + oldState->settingPhase = tv->field->settingPhase; + oldState->stonesSet = tv->field->stonesSet; + oldState->stoneMustBeRemoved= tv->field->stoneMustBeRemoved; + tv->curSearchDepth++; + + // very expensive + for (i=0; ifield->size; i++) { + oldState->stonePartOfMill[i] = tv->field->stonePartOfMill[i]; + } + + // move + if (tv->field->stoneMustBeRemoved) { tv->removeStone(idPossibility, oldState); } + else if (tv->field->settingPhase) { tv->setStone(idPossibility, oldState); } + else { tv->normalMove(tmpPossibility->from[idPossibility], tmpPossibility->to[idPossibility], oldState); } + + // when opponent is unable to move than current player has won + if ((!tv->field->oppPlayer->numPossibleMoves) && (!tv->field->settingPhase) && (!tv->field->stoneMustBeRemoved) && (tv->field->oppPlayer->numStones > 3)) tv->gameHasFinished = true; + + // when game has finished - perfect for the current player + if (tv->gameHasFinished && !opponentsMove) tv->shortValue = SKV_VALUE_GAME_WON; + if (tv->gameHasFinished && opponentsMove) tv->shortValue = SKV_VALUE_GAME_LOST; + + tv->floatValue = tv->shortValue; + + // calc value + if (!opponentsMove) tv->floatValue = (float) tv->field->oppPlayer->numStonesMissing - tv->field->curPlayer->numStonesMissing + tv->field->stoneMustBeRemoved + tv->field->curPlayer->numPossibleMoves * 0.1f - tv->field->oppPlayer->numPossibleMoves * 0.1f; + else tv->floatValue = (float) tv->field->curPlayer->numStonesMissing - tv->field->oppPlayer->numStonesMissing - tv->field->stoneMustBeRemoved + tv->field->oppPlayer->numPossibleMoves * 0.1f - tv->field->curPlayer->numPossibleMoves * 0.1f; + + // when game has finished - perfect for the current player + if (tv->gameHasFinished && !opponentsMove) tv->floatValue = VALUE_GAME_WON - tv->curSearchDepth; + if (tv->gameHasFinished && opponentsMove) tv->floatValue = VALUE_GAME_LOST + tv->curSearchDepth; + + // set next player + if (!tv->field->stoneMustBeRemoved) { + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + } +} + +//----------------------------------------------------------------------------- +// Name: storeValueOfMove() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::storeValueOfMove(unsigned int threadNo, unsigned int idPossibility, void *pPossibilities, unsigned char value, unsigned int *freqValuesSubMoves, plyInfoVarType plyInfo) +{ + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + unsigned int index; + possibilityStruct *tmpPossibility = (possibilityStruct*) pPossibilities; + + if (tv->field->stoneMustBeRemoved) index = idPossibility; + else if (tv->field->settingPhase) index = idPossibility; + else index = tmpPossibility->from[idPossibility] * fieldStruct::size + tmpPossibility->to[idPossibility]; + + plyInfoForOutput[index] = plyInfo; + valueOfMove[index] = value; + incidencesValuesSubMoves[index][SKV_VALUE_INVALID ] = freqValuesSubMoves[SKV_VALUE_INVALID ]; + incidencesValuesSubMoves[index][SKV_VALUE_GAME_LOST ] = freqValuesSubMoves[SKV_VALUE_GAME_LOST ]; + incidencesValuesSubMoves[index][SKV_VALUE_GAME_DRAWN] = freqValuesSubMoves[SKV_VALUE_GAME_DRAWN ]; + incidencesValuesSubMoves[index][SKV_VALUE_GAME_WON ] = freqValuesSubMoves[SKV_VALUE_GAME_WON ]; +} + +//----------------------------------------------------------------------------- +// Name: getValueOfMoves() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getValueOfMoves(unsigned char *moveValue, unsigned int *freqValuesSubMoves, plyInfoVarType *plyInfo, unsigned int *moveQuality, unsigned char &knotValue, plyInfoVarType &bestAmountOfPlies) +{ + // locals + unsigned int moveQualities[fieldStruct::size * fieldStruct::size]; // 0 is bad, 1 is good + unsigned int i, j; + + // set an invalid default value + knotValue = SKV_NUM_VALUES; + + // calc knotValue + for (i=0; i= plyInfoForOutput[i*fieldStruct::size+j]) { + bestAmountOfPlies = plyInfoForOutput[i*fieldStruct::size+j]; + } + } + } + } + + } else if (knotValue == SKV_VALUE_GAME_LOST) { + bestAmountOfPlies = 0; + + for (i=0; ifield->stoneMustBeRemoved) cout << "remove stone from " << (char) (idPossibility + 97); + else if (tv->field->settingPhase) cout << "set stone to " << (char) (idPossibility + 97); + else cout << "move from " << (char) (tmpPossibility->from[idPossibility] + 97) << " to " << (char) (tmpPossibility->to[idPossibility] + 97); +} + +//----------------------------------------------------------------------------- +// Name: getNumberOfLayers() +// Desc: called one time +//----------------------------------------------------------------------------- +unsigned int perfectKI::getNumberOfLayers() +{ + return NUM_LAYERS; +} + +//----------------------------------------------------------------------------- +// Name: shallRetroAnalysisBeUsed() +// Desc: called one time for each layer time +//----------------------------------------------------------------------------- +bool perfectKI::shallRetroAnalysisBeUsed(unsigned int layerNum) +{ + if (layerNum < 100) + return true; + else + return false; +} + +//----------------------------------------------------------------------------- +// Name: getNumberOfKnotsInLayer() +// Desc: called one time +//----------------------------------------------------------------------------- +unsigned int perfectKI::getNumberOfKnotsInLayer(unsigned int layerNum) +{ + // locals + unsigned int numberOfKnots = layer[layerNum].subLayer[layer[layerNum].numSubLayers - 1].maxIndex + 1; + + // times two since either an own stone must be moved or an opponent stone must be removed + numberOfKnots *= MAX_NUM_STONES_REMOVED_MINUS_1; + + // return zero if layer is not reachable + if (((layer[layerNum].numBlackStones < 2 || layer[layerNum].numWhiteStones < 2) && layerNum < 100) // moving phase + || (layer[layerNum].numBlackStones == 2 && layer[layerNum].numWhiteStones == 2 && layerNum < 100) + || (layerNum == 100)) + return 0; + + // another way + return (unsigned int) numberOfKnots; +} + +//----------------------------------------------------------------------------- +// Name: nOverN() +// Desc: called seldom +//----------------------------------------------------------------------------- +long long perfectKI::mOverN_Function(unsigned int m, unsigned int n) +{ + // locals + long long result = 1; + long long fakN = 1; + unsigned int i; + + // invalid parameters ? + if (n > m) return 0; + + // flip, since then the result value won't get so high + if (n > m/2) n = m-n; + + // calc number of possibilities one can put n different stones in m holes + for (i=m-n+1; i<=m; i++) result *= i; + + // calc number of possibilities one can sort n different stones + for (i= 1; i<=n; i++) fakN *= i; + + // divide + result /= fakN; + + return result; +} + +//----------------------------------------------------------------------------- +// Name: applySymmetrieOperationOnField() +// Desc: called very often +//----------------------------------------------------------------------------- +void perfectKI::applySymmetrieOperationOnField(unsigned char symmetryOperationNumber, unsigned int *sourceField, unsigned int *destField) +{ + for (unsigned int i=0; ifield->oppPlayer->numStones; + unsigned int numWhiteStones = tv->field->curPlayer->numStones; + unsigned int phaseIndex = (tv->field->settingPhase == true) ? LAYER_INDEX_SETTING_PHASE : LAYER_INDEX_MOVING_PHASE; + return layerIndex[phaseIndex][numWhiteStones][numBlackStones]; +} + +//----------------------------------------------------------------------------- +// Name: getLayerAndStateNumber() +// Desc: +//----------------------------------------------------------------------------- +unsigned int perfectKI::getLayerAndStateNumber(unsigned int threadNo, unsigned int &layerNum, unsigned int &stateNumber) +{ + threadVarsStruct * tv = &threadVars[threadNo]; + return tv->getLayerAndStateNumber(layerNum, stateNumber); +} + +//----------------------------------------------------------------------------- +// Name: getLayerAndStateNumber() +// Desc: Current player has white stones, the opponent the black ones. +//----------------------------------------------------------------------------- +unsigned int perfectKI::threadVarsStruct::getLayerAndStateNumber(unsigned int &layerNum, unsigned int &stateNumber) +{ + // locals + unsigned int myField [fieldStruct::size]; + unsigned int symField[fieldStruct::size]; + unsigned int numBlackStones = field->oppPlayer->numStones; + unsigned int numWhiteStones = field->curPlayer->numStones; + unsigned int phaseIndex = (field->settingPhase == true) ? LAYER_INDEX_SETTING_PHASE : LAYER_INDEX_MOVING_PHASE; + unsigned int wCD = 0, bCD = 0; + unsigned int stateAB, stateCD; + unsigned int i; + + // layer number + layerNum = parent->layerIndex[phaseIndex][numWhiteStones][numBlackStones]; + + // make white and black fields + for(i=0; ifield[i] == fieldStruct::squareIsFree) { + myField[i] = FREE_SQUARE; + } else if (field->field[i] == field->curPlayer->id) { + myField[i] = WHITE_STONE; + if (fieldPosIsOfGroup[i] == GROUP_C) wCD++; + if (fieldPosIsOfGroup[i] == GROUP_D) wCD++; + } else { + myField[i] = BLACK_STONE; + if (fieldPosIsOfGroup[i] == GROUP_C) bCD++; + if (fieldPosIsOfGroup[i] == GROUP_D) bCD++; + } + } + + // calc stateCD + stateCD = myField[squareIndexGroupC[0]] * parent->powerOfThree[15] + + myField[squareIndexGroupC[1]] * parent->powerOfThree[14] + + myField[squareIndexGroupC[2]] * parent->powerOfThree[13] + + myField[squareIndexGroupC[3]] * parent->powerOfThree[12] + + myField[squareIndexGroupC[4]] * parent->powerOfThree[11] + + myField[squareIndexGroupC[5]] * parent->powerOfThree[10] + + myField[squareIndexGroupC[6]] * parent->powerOfThree[ 9] + + myField[squareIndexGroupC[7]] * parent->powerOfThree[ 8] + + myField[squareIndexGroupD[0]] * parent->powerOfThree[ 7] + + myField[squareIndexGroupD[1]] * parent->powerOfThree[ 6] + + myField[squareIndexGroupD[2]] * parent->powerOfThree[ 5] + + myField[squareIndexGroupD[3]] * parent->powerOfThree[ 4] + + myField[squareIndexGroupD[4]] * parent->powerOfThree[ 3] + + myField[squareIndexGroupD[5]] * parent->powerOfThree[ 2] + + myField[squareIndexGroupD[6]] * parent->powerOfThree[ 1] + + myField[squareIndexGroupD[7]] * parent->powerOfThree[ 0]; + + // apply symmetry operation on group A&B + parent->applySymmetrieOperationOnField(parent->symmetryOperationCD[stateCD], myField, symField); + + // calc stateAB + stateAB = symField[squareIndexGroupA[0]] * parent->powerOfThree[7] + + symField[squareIndexGroupA[1]] * parent->powerOfThree[6] + + symField[squareIndexGroupA[2]] * parent->powerOfThree[5] + + symField[squareIndexGroupA[3]] * parent->powerOfThree[4] + + symField[squareIndexGroupB[0]] * parent->powerOfThree[3] + + symField[squareIndexGroupB[1]] * parent->powerOfThree[2] + + symField[squareIndexGroupB[2]] * parent->powerOfThree[1] + + symField[squareIndexGroupB[3]] * parent->powerOfThree[0]; + + // calc index + stateNumber = parent->layer[layerNum].subLayer[parent->layer[layerNum].subLayerIndexCD[wCD][bCD]].minIndex * MAX_NUM_STONES_REMOVED_MINUS_1 + + parent->indexAB[stateAB] * parent->anzahlStellungenCD[wCD][bCD] * MAX_NUM_STONES_REMOVED_MINUS_1 + + parent->indexCD[stateCD] * MAX_NUM_STONES_REMOVED_MINUS_1 + + field->stoneMustBeRemoved; + + return parent->symmetryOperationCD[stateCD]; +} + +//----------------------------------------------------------------------------- +// Name: setSituation() +// Desc: Current player has white stones, the opponent the black ones. +// Sets up the game situation corresponding to the passed layer number and state. +//----------------------------------------------------------------------------- +bool perfectKI::setSituation(unsigned int threadNo, unsigned int layerNum, unsigned int stateNumber) +{ + // parameters ok ? + if (getNumberOfLayers() <= layerNum ) return false; + if (getNumberOfKnotsInLayer(layerNum) <= stateNumber) return false; + + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + unsigned int stateNumberWithInSubLayer; + unsigned int stateNumberWithInAB; + unsigned int stateNumberWithInCD; + unsigned int stateAB, stateCD; + unsigned int myField [fieldStruct::size]; + unsigned int symField[fieldStruct::size]; + unsigned int numWhiteStones = layer[layerNum].numWhiteStones; + unsigned int numBlackStones = layer[layerNum].numBlackStones; + unsigned int numberOfMillsCurrentPlayer = 0; + unsigned int numberOfMillsOpponentPlayer = 0; + unsigned int wCD, bCD, wAB, bAB; + unsigned int i; + bool aStoneCanBeRemovedFromCurPlayer; + + // get wCD, bCD, wAB, bAB + for (i=0; i<=layer[layerNum].numSubLayers; i++) { + if (layer[layerNum].subLayer[i].minIndex <= stateNumber / MAX_NUM_STONES_REMOVED_MINUS_1 + && layer[layerNum].subLayer[i].maxIndex >= stateNumber / MAX_NUM_STONES_REMOVED_MINUS_1) { + wCD = layer[layerNum].subLayer[i].numWhiteStonesGroupCD; + bCD = layer[layerNum].subLayer[i].numBlackStonesGroupCD; + wAB = layer[layerNum].subLayer[i].numWhiteStonesGroupAB; + bAB = layer[layerNum].subLayer[i].numBlackStonesGroupAB; + break; + } + } + + // reset values + tv->curSearchDepth = 0; + tv->floatValue = 0.0f; + tv->shortValue = SKV_VALUE_GAME_DRAWN; + tv->gameHasFinished = false; + + tv->field->settingPhase = (layerNum >= NUM_LAYERS / 2) ? LAYER_INDEX_SETTING_PHASE : LAYER_INDEX_MOVING_PHASE; + tv->field->stoneMustBeRemoved = stateNumber % MAX_NUM_STONES_REMOVED_MINUS_1; + tv->field->curPlayer->numStones = numWhiteStones; + tv->field->oppPlayer->numStones = numBlackStones; + + // reconstruct field->field[] + stateNumberWithInSubLayer = (stateNumber / MAX_NUM_STONES_REMOVED_MINUS_1) - layer[layerNum].subLayer[layer[layerNum].subLayerIndexCD[wCD][bCD]].minIndex; + stateNumberWithInAB = stateNumberWithInSubLayer / anzahlStellungenCD[wCD][bCD]; + stateNumberWithInCD = stateNumberWithInSubLayer % anzahlStellungenCD[wCD][bCD]; + + // get stateCD + stateCD = originalStateCD[wCD][bCD][stateNumberWithInCD]; + stateAB = originalStateAB[wAB][bAB][stateNumberWithInAB]; + + // set myField from stateCD and stateAB + myField[squareIndexGroupA[0]] = (stateAB / powerOfThree[7]) % 3; + myField[squareIndexGroupA[1]] = (stateAB / powerOfThree[6]) % 3; + myField[squareIndexGroupA[2]] = (stateAB / powerOfThree[5]) % 3; + myField[squareIndexGroupA[3]] = (stateAB / powerOfThree[4]) % 3; + myField[squareIndexGroupB[0]] = (stateAB / powerOfThree[3]) % 3; + myField[squareIndexGroupB[1]] = (stateAB / powerOfThree[2]) % 3; + myField[squareIndexGroupB[2]] = (stateAB / powerOfThree[1]) % 3; + myField[squareIndexGroupB[3]] = (stateAB / powerOfThree[0]) % 3; + + myField[squareIndexGroupC[0]] = (stateCD / powerOfThree[15]) % 3; + myField[squareIndexGroupC[1]] = (stateCD / powerOfThree[14]) % 3; + myField[squareIndexGroupC[2]] = (stateCD / powerOfThree[13]) % 3; + myField[squareIndexGroupC[3]] = (stateCD / powerOfThree[12]) % 3; + myField[squareIndexGroupC[4]] = (stateCD / powerOfThree[11]) % 3; + myField[squareIndexGroupC[5]] = (stateCD / powerOfThree[10]) % 3; + myField[squareIndexGroupC[6]] = (stateCD / powerOfThree[ 9]) % 3; + myField[squareIndexGroupC[7]] = (stateCD / powerOfThree[ 8]) % 3; + myField[squareIndexGroupD[0]] = (stateCD / powerOfThree[ 7]) % 3; + myField[squareIndexGroupD[1]] = (stateCD / powerOfThree[ 6]) % 3; + myField[squareIndexGroupD[2]] = (stateCD / powerOfThree[ 5]) % 3; + myField[squareIndexGroupD[3]] = (stateCD / powerOfThree[ 4]) % 3; + myField[squareIndexGroupD[4]] = (stateCD / powerOfThree[ 3]) % 3; + myField[squareIndexGroupD[5]] = (stateCD / powerOfThree[ 2]) % 3; + myField[squareIndexGroupD[6]] = (stateCD / powerOfThree[ 1]) % 3; + myField[squareIndexGroupD[7]] = (stateCD / powerOfThree[ 0]) % 3; + + // apply symmetry operation on group A&B + applySymmetrieOperationOnField(reverseSymOperation[symmetryOperationCD[stateCD]], myField, symField); + + // translate symField[] to field->field[] + for (i=0; ifield->field[i] = fieldStruct::squareIsFree; + else if (symField[i] == WHITE_STONE) tv->field->field[i] = tv->field->curPlayer->id; + else tv->field->field[i] = tv->field->oppPlayer->id; + } + + // calc possible moves + tv->calcPossibleMoves(tv->field->curPlayer); + tv->calcPossibleMoves(tv->field->oppPlayer); + + // zero + for (i=0; ifield->stonePartOfMill[i] = 0; + } + + // go in every direction + for (i=0; isetWarningAndMill(i, tv->field->neighbour[i][0][0], tv->field->neighbour[i][0][1]); + tv->setWarningAndMill(i, tv->field->neighbour[i][1][0], tv->field->neighbour[i][1][1]); + } + + // since every mill was detected 3 times + for (i=0; ifield->stonePartOfMill[i] /= 3; + + // count completed mills + for (i=0; ifield->field[i] == tv->field->curPlayer->id) numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[i]; + else numberOfMillsOpponentPlayer += tv->field->stonePartOfMill[i]; + } + + numberOfMillsCurrentPlayer /= 3; + numberOfMillsOpponentPlayer /= 3; + + // stonesSet & numStonesMissing + if (tv->field->settingPhase) { + // BUG: ... This calculation is not correct! It is possible that some mills did not cause a stone removal. + tv->field->curPlayer->numStonesMissing = numberOfMillsOpponentPlayer; + tv->field->oppPlayer->numStonesMissing = numberOfMillsCurrentPlayer - tv->field->stoneMustBeRemoved; + tv->field->stonesSet = tv->field->curPlayer->numStones + tv->field->oppPlayer->numStones + tv->field->curPlayer->numStonesMissing + tv->field->oppPlayer->numStonesMissing; + } else { + tv->field->stonesSet = 18; + tv->field->curPlayer->numStonesMissing = 9 - tv->field->curPlayer->numStones; + tv->field->oppPlayer->numStonesMissing = 9 - tv->field->oppPlayer->numStones; + } + + // when opponent is unable to move than current player has won + if ((!tv->field->curPlayer->numPossibleMoves) && (!tv->field->settingPhase) && (!tv->field->stoneMustBeRemoved) && (tv->field->curPlayer->numStones > 3)) { tv->gameHasFinished = true; tv->shortValue = SKV_VALUE_GAME_LOST; } + if ((tv->field->curPlayer->numStones < 3) && (!tv->field->settingPhase)) { tv->gameHasFinished = true; tv->shortValue = SKV_VALUE_GAME_LOST; } + if ((tv->field->oppPlayer->numStones < 3) && (!tv->field->settingPhase)) { tv->gameHasFinished = true; tv->shortValue = SKV_VALUE_GAME_WON; } + + tv-> floatValue = tv->shortValue; + + // precalc aStoneCanBeRemovedFromCurPlayer + for (aStoneCanBeRemovedFromCurPlayer=false, i=0; ifield->size; i++) { + if (tv->field->stonePartOfMill[i] == 0 && tv->field->field[i] == tv->field->curPlayer->id) { + aStoneCanBeRemovedFromCurPlayer = true; + break; + }} + + // test if field is ok + return tv->fieldIntegrityOK(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, aStoneCanBeRemovedFromCurPlayer); +} + +//----------------------------------------------------------------------------- +// Name: calcPossibleMoves() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::threadVarsStruct::calcPossibleMoves(playerStruct *player) +{ + // locals + unsigned int i, j , k, movingDirection; + + for (player->numPossibleMoves=0, i=0; ifield[i] != player->id) continue; + + // is destination free ? + if (field->field[j] != field->squareIsFree) continue; + + // when current player has only 3 stones he is allowed to spring his stone + if (player->numStones > 3 || field->settingPhase) { + + // determine moving direction + for (k=0, movingDirection=4; k<4; k++) if (field->connectedSquare[i][k] == j) movingDirection = k; + + // are both squares connected ? + if (movingDirection == 4) continue; + } + + // everything is ok + player->numPossibleMoves++; + }} +} + +//----------------------------------------------------------------------------- +// Name: setWarningAndMill() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::threadVarsStruct::setWarningAndMill(unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour) +{ + // locals + int rowOwner = field->field[stone]; + + // mill closed ? + if (rowOwner != field->squareIsFree && field->field[firstNeighbour] == rowOwner && field->field[secondNeighbour] == rowOwner) { + + field->stonePartOfMill[stone]++; + field->stonePartOfMill[firstNeighbour]++; + field->stonePartOfMill[secondNeighbour]++; + } +} + +//----------------------------------------------------------------------------- +// Name: getOutputInformation() +// Desc: +//----------------------------------------------------------------------------- +string perfectKI::getOutputInformation(unsigned int layerNum) +{ + stringstream ss; + ss << " white stones : " << layer[layerNum].numWhiteStones << " \tblack stones : " << layer[layerNum].numBlackStones; + return ss.str(); +} + +//----------------------------------------------------------------------------- +// Name: printField() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::printField(unsigned int threadNo, unsigned char value) +{ + threadVarsStruct * tv = &threadVars[threadNo]; + char wonStr[] = "WON"; + char lostStr[] = "LOST"; + char drawStr[] = "DRAW"; + char invStr[] = "INVALID"; + char* table[4] = {invStr, lostStr, drawStr, wonStr}; + + cout << "\nstate value : " << table[value]; + cout << "\nstones set : " << tv->field->stonesSet << "\n"; + tv->field->printField(); +} + +//----------------------------------------------------------------------------- +// Name: getField() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getField(unsigned int layerNum, unsigned int stateNumber, fieldStruct *field, bool *gameHasFinished) +{ + // set current desired state on thread zero + setSituation(0, layerNum, stateNumber); + + // copy content of fieldStruct + threadVars[0].field->copyField(field); + if (gameHasFinished != NULL) *gameHasFinished = threadVars[0].gameHasFinished; +} + + +//----------------------------------------------------------------------------- +// Name: getLayerAndStateNumber() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getLayerAndStateNumber(unsigned int& layerNum, unsigned int& stateNumber/*, unsigned int& symmetryOperation*/) +{ + /*symmetryOperation = */threadVars[0].getLayerAndStateNumber(layerNum, stateNumber); +} + +//----------------------------------------------------------------------------- +// Name: setOpponentLevel() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::setOpponentLevel(unsigned int threadNo, bool isOpponentLevel) +{ + threadVarsStruct * tv = &threadVars[threadNo]; + tv->ownId = isOpponentLevel ? tv->field->oppPlayer->id : tv->field->curPlayer->id; +} + +//----------------------------------------------------------------------------- +// Name: getOpponentLevel() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::getOpponentLevel(unsigned int threadNo) +{ + threadVarsStruct * tv = &threadVars[threadNo]; + return (tv->ownId == tv->field->oppPlayer->id); +} + +//----------------------------------------------------------------------------- +// Name: getPartnerLayer() +// Desc: +//----------------------------------------------------------------------------- +unsigned int perfectKI::getPartnerLayer(unsigned int layerNum) +{ + if (layerNum < 100) + for (int i=0; i<100; i++) { + if (layer[layerNum].numBlackStones == layer[i].numWhiteStones + && layer[layerNum].numWhiteStones == layer[i].numBlackStones) { + return i; + } + } + return layerNum; +} + +//----------------------------------------------------------------------------- +// Name: getSuccLayers() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getSuccLayers(unsigned int layerNum, unsigned int *amountOfSuccLayers, unsigned int *succLayers) +{ + // locals + unsigned int i; + unsigned int shift = (layerNum >= 100) ? 100 : 0; + int diff = (layerNum >= 100) ? 1 : -1; + + // search layer with one white stone less + for (*amountOfSuccLayers=0, i=0+shift; i<100+shift; i++) { + if (layer[i].numWhiteStones == layer[layerNum].numBlackStones + diff + && layer[i].numBlackStones == layer[layerNum].numWhiteStones ) { + succLayers[*amountOfSuccLayers] = i; + *amountOfSuccLayers = *amountOfSuccLayers + 1; + break; + } + } + + // search layer with one black stone less + for (i=0+shift; i<100+shift; i++) { + if (layer[i].numWhiteStones == layer[layerNum].numBlackStones + && layer[i].numBlackStones == layer[layerNum].numWhiteStones + diff) { + succLayers[*amountOfSuccLayers] = i; + *amountOfSuccLayers = *amountOfSuccLayers + 1; + break; + } + } +} + +//----------------------------------------------------------------------------- +// Name: getSymStateNumWithDoubles() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::getSymStateNumWithDoubles(unsigned int threadNo, unsigned int *numSymmetricStates, unsigned int **symStateNumbers) +{ + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + int originalField [fieldStruct::size]; + unsigned int originalPartOfMill[fieldStruct::size]; + unsigned int i, symmetryOperation; + unsigned int layerNum, stateNum; + + *numSymmetricStates = 0; + *symStateNumbers = symmetricStateNumberArray; + + // save current field + for (i=0; ifield->field[i]; + originalPartOfMill[i] = tv->field->stonePartOfMill[i]; + } + + // add all symmetric states + for (symmetryOperation=0; symmetryOperationfield->field); + applySymmetrieOperationOnField(symmetryOperation, (unsigned int*) originalPartOfMill, (unsigned int*) tv->field->stonePartOfMill); + + getLayerAndStateNumber(threadNo, layerNum, stateNum); + symmetricStateNumberArray[*numSymmetricStates] = stateNum; + (*numSymmetricStates)++; + } + + // restore original field + for (i=0; ifield->field[i] = originalField[i]; + tv->field->stonePartOfMill[i] = originalPartOfMill[i]; + } +} + +//----------------------------------------------------------------------------- +// Name: fieldIntegrityOK() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::threadVarsStruct::fieldIntegrityOK(unsigned int numberOfMillsCurrentPlayer, unsigned int numberOfMillsOpponentPlayer, bool aStoneCanBeRemovedFromCurPlayer) +{ + // locals + int i, j; + bool noneFullFilled; + + // when stone is going to be removed than at least one opponent stone mustn't be part of a mill + if (numberOfMillsOpponentPlayer > 0 && field->stoneMustBeRemoved) { + for (i=0; isize; i++) if (field->stonePartOfMill[i] == 0 && field->oppPlayer->id == field->field[i]) break; + if (i == field->size) return false; + } + + // when no mill is closed than no stone can be removed + if (field->stoneMustBeRemoved && numberOfMillsCurrentPlayer == 0) { + return false; + + // when in setting phase and difference in number of stones between the two players is not + } else if (field->settingPhase) { + + // Version 8: added for-loop + noneFullFilled = true; + + for (i=0; noneFullFilled && i<=(int)numberOfMillsOpponentPlayer && i<=(int)numberOfMillsCurrentPlayer; i++) { + for (j=0; noneFullFilled && j<=(int)numberOfMillsOpponentPlayer && j<=(int)numberOfMillsCurrentPlayer-(int)field->stoneMustBeRemoved; j++) { + if (field->curPlayer->numStones + numberOfMillsOpponentPlayer + 0 - field->stoneMustBeRemoved - j == field->oppPlayer->numStones + numberOfMillsCurrentPlayer - field->stoneMustBeRemoved - i) noneFullFilled = false; + if (field->curPlayer->numStones + numberOfMillsOpponentPlayer + 1 - field->stoneMustBeRemoved - j == field->oppPlayer->numStones + numberOfMillsCurrentPlayer - field->stoneMustBeRemoved - i) noneFullFilled = false; + } + } + + if (noneFullFilled || field->stonesSet >= 18) { + return false; + } + + // moving phase + } else if (!field->settingPhase && (field->curPlayer->numStones < 2 || field->oppPlayer->numStones < 2)) { + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Name: isSymOperationInvariantOnGroupCD() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::isSymOperationInvariantOnGroupCD(unsigned int symmetryOperation, int *theField) +{ + // locals + unsigned int i; + + i = squareIndexGroupC[0]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[1]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[2]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[3]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[4]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[5]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[6]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupC[7]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[0]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[1]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[2]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[3]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[4]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[5]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[6]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + i = squareIndexGroupD[7]; if (theField[i] != theField[symmetryOperationTable[symmetryOperation][i]]) return false; + + return true; +} + +//----------------------------------------------------------------------------- +// Name: storePredecessor() +// Desc: +//----------------------------------------------------------------------------- +void perfectKI::threadVarsStruct::storePredecessor(unsigned int numberOfMillsCurrentPlayer, unsigned int numberOfMillsOpponentPlayer, unsigned int *amountOfPred, retroAnalysisPredVars *predVars) +{ + // locals + int originalField[fieldStruct::size]; + unsigned int i, symmetryOperation, symOpApplied; + unsigned int predLayerNum, predStateNum; + unsigned int originalAmountOfPred = *amountOfPred; + + // store only if state is valid + if (fieldIntegrityOK(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, false)) { + + // save current field + for (i=0; ifield[i]; + + // add all symmetric states + for (symmetryOperation=0; symmetryOperationisSymOperationInvariantOnGroupCD(symmetryOperation, originalField)) { + + // appy symmetry operation + parent->applySymmetrieOperationOnField(symmetryOperation, (unsigned int*) originalField, (unsigned int*) field->field); + + symOpApplied = getLayerAndStateNumber(predLayerNum, predStateNum); + predVars[*amountOfPred].predSymOperation = parent->concSymOperation[symmetryOperation][symOpApplied]; + predVars[*amountOfPred].predLayerNumbers = predLayerNum; + predVars[*amountOfPred].predStateNumbers = predStateNum; + predVars[*amountOfPred].playerToMoveChanged = predVars[originalAmountOfPred].playerToMoveChanged; + + // add only if not already in list + for (i=0; i<(*amountOfPred); i++) if (predVars[i].predLayerNumbers == predLayerNum && predVars[i].predStateNumbers == predStateNum) break; + if (i == *amountOfPred) (*amountOfPred)++; + } + } + + // restore original field + for (i=0; ifield[i] = originalField[i]; + } +} + +//----------------------------------------------------------------------------- +// Name: getPredecessors() +// Desc: CAUTION: States musn't be returned twice. +//----------------------------------------------------------------------------- +void perfectKI::getPredecessors(unsigned int threadNo, unsigned int *amountOfPred, retroAnalysisPredVars *predVars) +{ + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // the important variables, which much be updated for the getLayerAndStateNumber function are the following ones: + // - field->curPlayer->numStones + // - field->oppPlayer->numStones + // - field->curPlayer->id + // - field->field + // - field->stoneMustBeRemoved + // - field->settingPhase + //////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + // locals + threadVarsStruct * tv = &threadVars[threadNo]; + bool aStoneCanBeRemovedFromCurPlayer; + bool millWasClosed; + unsigned int from, to, dir, i; + playerStruct *tmpPlayer; + unsigned int numberOfMillsCurrentPlayer = 0; + unsigned int numberOfMillsOpponentPlayer = 0; + + // zero + *amountOfPred = 0; + + // count completed mills + for (i=0; ifield->field[i] == tv->field->curPlayer->id) numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[i]; + else numberOfMillsOpponentPlayer += tv->field->stonePartOfMill[i]; + } + + numberOfMillsCurrentPlayer /= 3; + numberOfMillsOpponentPlayer /= 3; + + // precalc aStoneCanBeRemovedFromCurPlayer + for (aStoneCanBeRemovedFromCurPlayer=false, i=0; ifield->size; i++) { + if (tv->field->stonePartOfMill[i] == 0 && tv->field->field[i] == tv->field->curPlayer->id) { + aStoneCanBeRemovedFromCurPlayer = true; + break; + }} + + // was a mill closed? + if (tv->field->stoneMustBeRemoved) millWasClosed = true; + else millWasClosed = false; + + // in moving phase + if (!tv->field->settingPhase && tv->field->curPlayer->numStones >= 3 && tv->field->oppPlayer->numStones >= 3) { + + // normal move + if (( tv->field->stoneMustBeRemoved && tv->field->curPlayer->numStones > 3) + || (!tv->field->stoneMustBeRemoved && tv->field->oppPlayer->numStones > 3)) { + + // when game has finished then because current player can't move anymore or has less then 3 stones + if (!tv->gameHasFinished || (tv->gameHasFinished && tv->field->curPlayer->numPossibleMoves == 0)) { + + // test each destination + for (to=0; to < tv->field->size; to++) { + + // was opponent player stone owner? + if (tv->field->field[to] != (tv->field->stoneMustBeRemoved ? tv->field->curPlayer->id : tv->field->oppPlayer->id)) continue; + + // when stone is going to be removed than a mill must be closed + if (tv->field->stoneMustBeRemoved && tv->field->stonePartOfMill[to] == 0) continue; + + // when stone is part of a mill then a stone must be removed + if (aStoneCanBeRemovedFromCurPlayer && tv->field->stoneMustBeRemoved == 0 && tv->field->stonePartOfMill[to]) continue; + + // test each direction + for (dir=0; dir<4; dir++) { + + // origin + from = tv->field->connectedSquare[to][dir]; + + // move possible ? + if (from < tv->field->size && tv->field->field[from] == tv->field->squareIsFree) { + + if (millWasClosed) { + numberOfMillsCurrentPlayer -= tv->field->stonePartOfMill[to]; + tv->field->stoneMustBeRemoved = 0; + predVars[*amountOfPred].playerToMoveChanged = false; + } else { + predVars[*amountOfPred].playerToMoveChanged = true; + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + i = numberOfMillsCurrentPlayer; + numberOfMillsCurrentPlayer = numberOfMillsOpponentPlayer; + numberOfMillsOpponentPlayer = i; + numberOfMillsCurrentPlayer -= tv->field->stonePartOfMill[to]; + } + + // make move + tv->field->field[from] = tv->field->field[to]; + tv->field->field[to] = tv->field->squareIsFree; + + // store predecessor + tv->storePredecessor(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, amountOfPred, predVars); + + // undo move + tv->field->field[to] = tv->field->field[from]; + tv->field->field[from] = tv->field->squareIsFree; + + if (millWasClosed) { + numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[to]; + tv->field->stoneMustBeRemoved = 1; + } else { + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[to]; + i = numberOfMillsCurrentPlayer; + numberOfMillsCurrentPlayer = numberOfMillsOpponentPlayer; + numberOfMillsOpponentPlayer = i; + } + + // current or opponent player were allowed to spring + }}}}} else if (!tv->gameHasFinished) { + + // test each destination + for (to=0; to < tv->field->size; to++) { + + // when stone must be removed than current player closed a mill, otherwise the opponent did a common spring move + if (tv->field->field[to] != (tv->field->stoneMustBeRemoved ? tv->field->curPlayer->id : tv->field->oppPlayer->id)) continue; + + // when stone is going to be removed than a mill must be closed + if (tv->field->stoneMustBeRemoved && tv->field->stonePartOfMill[to] == 0) continue; + + // when stone is part of a mill then a stone must be removed + if (aStoneCanBeRemovedFromCurPlayer && tv->field->stoneMustBeRemoved == 0 && tv->field->stonePartOfMill[to]) continue; + + // test each direction + for (from=0; fromfield->size; from++) { + + // move possible ? + if (tv->field->field[from] == tv->field->squareIsFree) { + + // was a mill closed? + if (millWasClosed) { + numberOfMillsCurrentPlayer -= tv->field->stonePartOfMill[to]; + tv->field->stoneMustBeRemoved = 0; + predVars[*amountOfPred].playerToMoveChanged = false; + } else { + predVars[*amountOfPred].playerToMoveChanged = true; + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + i = numberOfMillsCurrentPlayer; + numberOfMillsCurrentPlayer = numberOfMillsOpponentPlayer; + numberOfMillsOpponentPlayer = i; + numberOfMillsCurrentPlayer -= tv->field->stonePartOfMill[to]; + } + + // make move + tv->field->field[from] = tv->field->field[to]; + tv->field->field[to] = tv->field->squareIsFree; + + // store predecessor + tv->storePredecessor(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, amountOfPred, predVars); + + // undo move + tv->field->field[to] = tv->field->field[from]; + tv->field->field[from] = tv->field->squareIsFree; + + if (millWasClosed) { + numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[to]; + tv->field->stoneMustBeRemoved = 1; + } else { + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[to]; + i = numberOfMillsCurrentPlayer; + numberOfMillsCurrentPlayer = numberOfMillsOpponentPlayer; + numberOfMillsOpponentPlayer = i; + } + }}}} + } + + // was a stone removed ? + if (tv->field->curPlayer->numStones < 9 && tv->field->curPlayer->numStonesMissing > 0 && tv->field->stoneMustBeRemoved == 0) { + + // has opponent player a closed mill ? + if (numberOfMillsOpponentPlayer) { + + // from each free position the opponent could have removed a stone from the current player + for (from=0; fromfield->size; from++) { + + // square free? + if (tv->field->field[from] == tv->field->squareIsFree) { + + // stone mustn't be part of mill + if ((!(tv->field->field[tv->field->neighbour[from][0][0]] == tv->field->curPlayer->id && tv->field->field[tv->field->neighbour[from][0][1]] == tv->field->curPlayer->id)) + && (!(tv->field->field[tv->field->neighbour[from][1][0]] == tv->field->curPlayer->id && tv->field->field[tv->field->neighbour[from][1][1]] == tv->field->curPlayer->id))) { + + // put back stone + tv->field->stoneMustBeRemoved = 1; + tv->field->field[from] = tv->field->curPlayer->id; + tv->field->curPlayer->numStones++; + tv->field->curPlayer->numStonesMissing--; + + // it was an opponent move + predVars[*amountOfPred].playerToMoveChanged = true; + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + + // store predecessor + tv->storePredecessor(numberOfMillsOpponentPlayer, numberOfMillsCurrentPlayer, amountOfPred, predVars); + + tmpPlayer = tv->field->curPlayer; + tv->field->curPlayer = tv->field->oppPlayer; + tv->field->oppPlayer = tmpPlayer; + + // remove stone again + tv->field->stoneMustBeRemoved = 0; + tv->field->field[from] = tv->field->squareIsFree; + tv->field->curPlayer->numStones--; + tv->field->curPlayer->numStonesMissing++; + }}}}} +} + +//----------------------------------------------------------------------------- +// Name: checkMoveAndSetSituation() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::checkMoveAndSetSituation() +{ + // locals + bool aStoneCanBeRemovedFromCurPlayer; + unsigned int numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer; + unsigned int stateNum, layerNum, curMove, i; + unsigned int * idPossibility; + unsigned int numPossibilities; + bool isOpponentLevel; + void * pPossibilities; + void * pBackup; + unsigned int threadNo = 0; + threadVarsStruct * tv = &threadVars[threadNo]; + + // output + cout << endl << "checkMoveAndSetSituation()" << endl; + + // test if each successor from getPossibilities() leads to the original state using getPredecessors() + for (layerNum=0; layerNumfield->field[i] == tv->field->curPlayer->id) numberOfMillsCurrentPlayer += tv->field->stonePartOfMill[i]; + else numberOfMillsOpponentPlayer += tv->field->stonePartOfMill[i]; + } + numberOfMillsCurrentPlayer /= 3; + numberOfMillsOpponentPlayer /= 3; + + // precalc aStoneCanBeRemovedFromCurPlayer + for (aStoneCanBeRemovedFromCurPlayer=false, i=0; ifield->size; i++) { + if (tv->field->stonePartOfMill[i] == 0 && tv->field->field[i] == tv->field->curPlayer->id) { + aStoneCanBeRemovedFromCurPlayer = true; + break; + }} + + // + if (tv->fieldIntegrityOK(numberOfMillsCurrentPlayer, numberOfMillsOpponentPlayer, aStoneCanBeRemovedFromCurPlayer) == false) { + cout << endl << "ERROR: STATE " << stateNum << " REACHED WITH move(), BUT IS INVALID!"; + //return false; + } + + // undo move + undo(threadNo, idPossibility[curMove], isOpponentLevel, pBackup, pPossibilities); + } + } + cout << endl << "LAYER OK: " << layerNum << endl; + } + + // free mem + return true; +} + +//----------------------------------------------------------------------------- +// Name: checkGetPossThanGetPred() +// Desc: +//----------------------------------------------------------------------------- +bool perfectKI::checkGetPossThanGetPred() +{ + // locals + unsigned int stateNum, layerNum, i, j; + unsigned int * idPossibility; + unsigned int numPossibilities; + unsigned int amountOfPred; + bool isOpponentLevel; + void * pPossibilities; + void * pBackup; + retroAnalysisPredVars predVars[MAX_NUM_PREDECESSORS]; + unsigned int threadNo = 0; + threadVarsStruct * tv = &threadVars[threadNo]; + + // test if each successor from getPossibilities() leads to the original state using getPredecessors() + for (layerNum=0; layerNumfield->size; k++) symField[k] = tv->field->field[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->field); + for (k=0; kfield->size; k++) symField[k] = tv->field->stonePartOfMill[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->stonePartOfMill); + cout << "predecessor" << endl; + cout << " layerNum: " << predVars[j].predLayerNumbers << "\tstateNum: " << predVars[j].predStateNumbers << endl; + printField(threadNo, 0); + if (predVars[j].playerToMoveChanged) { + k = tv->field->curPlayer->id; + tv->field->curPlayer->id = tv->field->oppPlayer->id; + tv->field->oppPlayer->id = k; + for (k=0; kfield->size; k++) tv->field->field[k] = -1 * tv->field->field[k]; + } + idPossibility = getPossibilities(threadNo, &numPossibilities, &isOpponentLevel, &pPossibilities); + setSituation(threadNo, layerNum, stateNum); + cout << "current state" << endl; + cout << " layerNum: " << layerNum <<"\tstateNum: " << stateNum << endl; + printField(threadNo, 0); + getPredecessors(threadNo, &amountOfPred, predVars); + } + + // regard used symmetry operation + for (k=0; kfield->size; k++) symField[k] = tv->field->field[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->field); + for (k=0; kfield->size; k++) symField[k] = tv->field->stonePartOfMill[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->stonePartOfMill); + if (predVars[j].playerToMoveChanged) { + k = tv->field->curPlayer->id; + tv->field->curPlayer->id = tv->field->oppPlayer->id; + tv->field->oppPlayer->id = k; + for (k=0; kfield->size; k++) tv->field->field[k] = -1 * tv->field->field[k]; + } + + // get all possible moves + idPossibility = getPossibilities(threadNo, &numPossibilities, &isOpponentLevel, &pPossibilities); + + // go to each successor state + for (i=0; ifield->size; k++) symField[k] = tv->field->field[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->field); + for (k=0; kfield->size; k++) symField[k] = tv->field->stonePartOfMill[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->stonePartOfMill); + cout << "predecessor" << endl; + cout << " layerNum: " << predVars[j].predLayerNumbers <<"\tstateNum: " << predVars[j].predStateNumbers << endl; + printField(threadNo, 0); + if (predVars[j].playerToMoveChanged) { + k = tv->field->curPlayer->id; + tv->field->curPlayer->id = tv->field->oppPlayer->id; + tv->field->oppPlayer->id = k; + for (k=0; kfield->size; k++) tv->field->field[k] = -1 * tv->field->field[k]; + } + idPossibility = getPossibilities(threadNo, &numPossibilities, &isOpponentLevel, &pPossibilities); + setSituation(threadNo, layerNum, stateNum); + cout << "current state" << endl; + cout << " layerNum: " << layerNum <<"\tstateNum: " << stateNum << endl; + printField(threadNo, 0); + getPredecessors(threadNo, &amountOfPred, predVars); + + k = tv->field->curPlayer->id; + tv->field->curPlayer->id = tv->field->oppPlayer->id; + tv->field->oppPlayer->id = k; + for (k=0; kfield->size; k++) tv->field->field[k] = -1 * tv->field->field[k]; + setSituation(threadNo, predVars[j].predLayerNumbers, predVars[j].predStateNumbers); + for (k=0; kfield->size; k++) symField[k] = tv->field->field[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->field); + for (k=0; kfield->size; k++) symField[k] = tv->field->stonePartOfMill[k]; applySymmetrieOperationOnField(reverseSymOperation[predVars[j].predSymOperation], (unsigned int*) symField, (unsigned int*)tv->field->stonePartOfMill); + printField(threadNo, 0); + idPossibility = getPossibilities(threadNo, &numPossibilities, &isOpponentLevel, &pPossibilities); + move(threadNo, idPossibility[1], isOpponentLevel, &pBackup, pPossibilities); + printField(threadNo, 0); + getLayerAndStateNumber(threadNo, layerNumB, stateNumB); + } + } + } + cout << endl << "LAYER OK: " << layerNum << endl; + } + + // free mem + return true; +} + +/*** To Do's *************************************** +- Womöglich alle cyclicArrays in einer Datei speichern. Besser sogar noch kompromieren (auf Windows oder Programm-Ebene?), was gut gehen sollte da ja eh blockweise gearbeitet wird. + Da Größe vorher unbekannt muss eine table her. Möglicher Klassenname "compressedCyclicArray(blockSize, numBlocks, numArrays, filePath)". +- initFileReader implementieren +***************************************************/ diff --git a/src/perfect/perfectKI.h b/src/perfect/perfectKI.h new file mode 100644 index 00000000..51c5d3cc --- /dev/null +++ b/src/perfect/perfectKI.h @@ -0,0 +1,242 @@ +/*********************************************************************\ + perfectKI.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef PERFEKT_KI_H +#define PERFEKT_KI_H + +#include +#include +#include +#include "muehleKI.h" +#include "miniMax.h" + +//using namespace std; + +// values of states/situations +#define VALUE_GAME_LOST -1000.0f +#define VALUE_GAME_WON 1000.0f + +// since a state must be saved two times, +// one time where no stone must be removed, +// one time where a stone must be removed +#define MAX_NUM_STONES_REMOVED_MINUS_1 2 + +// 10 x 10 since each color can range from 0 to 9 stones +// x2 since there is the setting phase and the moving phase +#define NUM_LAYERS 200 +#define MAX_NUM_SUB_LAYERS 100 +#define LAYER_INDEX_SETTING_PHASE 1 +#define LAYER_INDEX_MOVING_PHASE 0 +#define NOT_INDEXED 4294967295 +#define MAX_DEPTH_OF_TREE 100 +#define NUM_STONES_PER_PLAYER 9 +#define NUM_STONES_PER_PLAYER_PLUS_ONE 10 + +// The Four Groups (the field position is divided in four groups A,B,C,D) +#define numSquaresGroupA 4 +#define numSquaresGroupB 4 +#define numSquaresGroupC 8 +#define numSquaresGroupD 8 +#define GROUP_A 0 +#define GROUP_B 1 +#define GROUP_C 2 +#define GROUP_D 3 +#define MAX_ANZ_STELLUNGEN_A 81 +#define MAX_ANZ_STELLUNGEN_B 81 +#define MAX_ANZ_STELLUNGEN_C (81*81) +#define MAX_ANZ_STELLUNGEN_D (81*81) + +#define FREE_SQUARE 0 +#define WHITE_STONE 1 +#define BLACK_STONE 2 + +// Symmetry Operations +#define SO_TURN_LEFT 0 +#define SO_TURN_180 1 +#define SO_TURN_RIGHT 2 +#define SO_DO_NOTHING 3 +#define SO_INVERT 4 +#define SO_MIRROR_VERT 5 +#define SO_MIRROR_HORI 6 +#define SO_MIRROR_DIAG_1 7 +#define SO_MIRROR_DIAG_2 8 +#define SO_INV_LEFT 9 +#define SO_INV_RIGHT 10 +#define SO_INV_180 11 +#define SO_INV_MIR_VERT 12 +#define SO_INV_MIR_HORI 13 +#define SO_INV_MIR_DIAG_1 14 +#define SO_INV_MIR_DIAG_2 15 +#define NUM_SYM_OPERATIONS 16 + +/*** Klassen *********************************************************/ +class perfectKI : public muehleKI, public miniMax +{ +protected: + + // structs + struct subLayerStruct + { + unsigned int minIndex; + unsigned int maxIndex; + unsigned int numWhiteStonesGroupCD, numBlackStonesGroupCD; + unsigned int numWhiteStonesGroupAB, numBlackStonesGroupAB; + }; + + struct layerStruct + { + unsigned int numWhiteStones; + unsigned int numBlackStones; + unsigned int numSubLayers; + unsigned int subLayerIndexAB[NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + unsigned int subLayerIndexCD[NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + subLayerStruct subLayer[MAX_NUM_SUB_LAYERS]; + }; + + struct possibilityStruct + { + unsigned int from[MAX_NUM_POS_MOVES]; + unsigned int to [MAX_NUM_POS_MOVES]; + }; + + struct backupStruct + { + float floatValue; + twoBit shortValue; + bool gameHasFinished; + bool settingPhase; + int fieldFrom, fieldTo; // value of field + unsigned int from, to; // index of field + unsigned int curNumStones, oppNumStones; + unsigned int curPosMoves, oppPosMoves; + unsigned int curMissStones, oppMissStones; + unsigned int stonesSet; + unsigned int stoneMustBeRemoved; + unsigned int stonePartOfMill[fieldStruct::size]; + playerStruct *curPlayer, *oppPlayer; + }; + + // preCalcedVars.dat + struct preCalcedVarsFileHeaderStruct + { + unsigned int sizeInBytes; + }; + + // constant variables for state addressing in the database + layerStruct layer [NUM_LAYERS]; // the layers + unsigned int layerIndex [2][NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; // indices of layer [moving/setting phase][number of white stones][number of black stones] + unsigned int anzahlStellungenCD [NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + unsigned int anzahlStellungenAB [NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + unsigned int indexAB [MAX_ANZ_STELLUNGEN_A*MAX_ANZ_STELLUNGEN_B]; + unsigned int indexCD [MAX_ANZ_STELLUNGEN_C*MAX_ANZ_STELLUNGEN_D]; + unsigned char symmetryOperationCD [MAX_ANZ_STELLUNGEN_C*MAX_ANZ_STELLUNGEN_D]; // index of symmetry operation used to get from the original state to the current one + unsigned int powerOfThree [numSquaresGroupC+numSquaresGroupD]; // 3^0, 3^1, 3^2, ... + unsigned int symmetryOperationTable [NUM_SYM_OPERATIONS][fieldStruct::size]; // Matrix used for application of the symmetry operations + unsigned int * originalStateCD [NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + unsigned int * originalStateAB [NUM_STONES_PER_PLAYER_PLUS_ONE][NUM_STONES_PER_PLAYER_PLUS_ONE]; + unsigned int reverseSymOperation [NUM_SYM_OPERATIONS]; // index of the reverse symmetry operation + unsigned int concSymOperation [NUM_SYM_OPERATIONS][NUM_SYM_OPERATIONS]; // symmetry operation, which is identical to applying those two in the index + unsigned int mOverN [fieldStruct::size+1][fieldStruct::size+1]; // m over n + unsigned char valueOfMove [fieldStruct::size * fieldStruct::size]; // contains the value of the situation, which will be achieved by that move + unsigned short plyInfoForOutput [fieldStruct::size * fieldStruct::size]; // contains the value of the situation, which will be achieved by that move + unsigned int incidencesValuesSubMoves [fieldStruct::size * fieldStruct::size][4]; // contains the number of ... + unsigned int symmetricStateNumberArray [NUM_SYM_OPERATIONS]; // array for state numbers + string databaseDirectory; // directory containing the database files + + // Variables used individually by each single thread + class threadVarsStruct + { + public: + fieldStruct * field; // pointer of the current field [changed by move()] + float floatValue; // value of current situation for field->currentPlayer + twoBit shortValue; // '' + bool gameHasFinished; // someone has won or current field is full + int ownId; // id of the player who called the play()-function + unsigned int curSearchDepth; // current level + unsigned int depthOfFullTree; // search depth where the whole tree is explored + unsigned int * idPossibilities; // returned pointer of getPossibilities()-function + backupStruct * oldStates; // for undo()-function + possibilityStruct * possibilities; // for getPossNormalMove()-function + perfectKI * parent; // + + // constructor + threadVarsStruct (); + + // Functions + unsigned int * getPossSettingPhase (unsigned int *numPossibilities, void **pPossibilities); + unsigned int * getPossNormalMove (unsigned int *numPossibilities, void **pPossibilities); + unsigned int * getPossStoneRemove (unsigned int *numPossibilities, void **pPossibilities); + + // move functions + inline void updatePossibleMoves (unsigned int stone, playerStruct *stoneOwner, bool stoneRemoved, unsigned int ignoreStone); + inline void updateWarning (unsigned int firstStone, unsigned int secondStone); + inline void setWarning (unsigned int stoneOne, unsigned int stoneTwo, unsigned int stoneThree); + inline void removeStone (unsigned int from, backupStruct *backup); + inline void setStone (unsigned int to, backupStruct *backup); + inline void normalMove (unsigned int from, unsigned int to, backupStruct *backup); + + // database functions + unsigned int getLayerAndStateNumber (unsigned int &layerNum, unsigned int &stateNumber); + void setWarningAndMill (unsigned int stone, unsigned int firstNeighbour, unsigned int secondNeighbour); + bool fieldIntegrityOK (unsigned int numberOfMillsCurrentPlayer, unsigned int numberOfMillsOpponentPlayer, bool aStoneCanBeRemovedFromCurPlayer); + void calcPossibleMoves (playerStruct *player); + void storePredecessor (unsigned int numberOfMillsCurrentPlayer, unsigned int numberOfMillsOpponentPlayer, unsigned int *amountOfPred, retroAnalysisPredVars *predVars); + }; + threadVarsStruct * threadVars; + + // database functions + unsigned int getNumberOfLayers (); + unsigned int getNumberOfKnotsInLayer (unsigned int layerNum); + long long mOverN_Function (unsigned int m, unsigned int n); + void applySymmetrieOperationOnField (unsigned char symmetryOperationNumber, unsigned int *sourceField, unsigned int *destField); + bool isSymOperationInvariantOnGroupCD(unsigned int symmetryOperation, int *theField); + bool shallRetroAnalysisBeUsed (unsigned int layerNum); + void getSuccLayers (unsigned int layerNum, unsigned int *amountOfSuccLayers, unsigned int *succLayers); + void getPredecessors (unsigned int threadNo, unsigned int *amountOfPred, retroAnalysisPredVars *predVars); + bool setSituation (unsigned int threadNo, unsigned int layerNum, unsigned int stateNumber); + unsigned int getLayerNumber (unsigned int threadNo); + unsigned int getLayerAndStateNumber (unsigned int threadNo, unsigned int &layerNum, unsigned int &stateNumber); + + // integrity test functions + bool checkMoveAndSetSituation (); + bool checkGetPossThanGetPred (); + bool checkGetPredThanGetPoss (); + + // Virtual Functions + void prepareBestChoiceCalculation (); + void getValueOfSituation (unsigned int threadNo, float &floatValue, twoBit &shortValue); + void setOpponentLevel (unsigned int threadNo, bool isOpponentLevel); + bool getOpponentLevel (unsigned int threadNo); + void deletePossibilities (unsigned int threadNo, void *pPossibilities); + unsigned int * getPossibilities (unsigned int threadNo, unsigned int *numPossibilities, bool *opponentsMove, void **pPossibilities); + void undo (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void *pBackup, void *pPossibilities); + void move (unsigned int threadNo, unsigned int idPossibility, bool opponentsMove, void **pBackup, void *pPossibilities); + void printMoveInformation (unsigned int threadNo, unsigned int idPossibility, void *pPossibilities); + void storeValueOfMove (unsigned int threadNo, unsigned int idPossibility, void *pPossibilities, unsigned char value, unsigned int *freqValuesSubMoves, plyInfoVarType plyInfo); + void getSymStateNumWithDoubles (unsigned int threadNo, unsigned int *numSymmetricStates, unsigned int **symStateNumbers); + void printField (unsigned int threadNo, unsigned char value); + string getOutputInformation (unsigned int layerNum); + unsigned int getPartnerLayer (unsigned int layerNum); + void prepareDatabaseCalculation (); + void wrapUpDatabaseCalculation (bool calculationAborted); + +public: + // Constructor / destructor + perfectKI (const char *directory); + ~perfectKI (); + + // Functions + bool setDatabasePath (const char *directory); + void play (fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo); + void getValueOfMoves (unsigned char *moveValue, unsigned int *freqValuesSubMoves, plyInfoVarType *plyInfo, unsigned int *moveQuality, unsigned char &knotValue, plyInfoVarType &bestAmountOfPlies); + void getField (unsigned int layerNum, unsigned int stateNumber, fieldStruct *field, bool *gameHasFinished); + void getLayerAndStateNumber (unsigned int& layerNum, unsigned int& stateNumber); + + // Testing functions + bool testLayers (unsigned int startTestFromLayer, unsigned int endTestAtLayer); +}; + +#endif \ No newline at end of file diff --git a/src/perfect/randomKI.cpp b/src/perfect/randomKI.cpp new file mode 100644 index 00000000..c597457f --- /dev/null +++ b/src/perfect/randomKI.cpp @@ -0,0 +1,86 @@ +/********************************************************************* + randomKI.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "randomKI.h" + +//----------------------------------------------------------------------------- +// Name: randomKI() +// Desc: randomKI class constructor +//----------------------------------------------------------------------------- +randomKI::randomKI() +{ + // Init + srand( (unsigned)time( NULL ) ); +} + +//----------------------------------------------------------------------------- +// Name: ~randomKI() +// Desc: randomKI class destructor +//----------------------------------------------------------------------------- +randomKI::~randomKI() +{ + // Locals + +} + +//----------------------------------------------------------------------------- +// Name: play() +// Desc: +//----------------------------------------------------------------------------- +void randomKI::play(fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo) +{ + // locals + unsigned int from, to, direction; + bool allowedToSpring = (theField->curPlayer->numStones == 3) ? true : false; + + // must stone be removed ? + if (theField->stoneMustBeRemoved) { + + // search a stone from the enemy + do { + from = rand() % theField->size; + to = theField->size; + } while (theField->field[from] != theField->oppPlayer->id || theField->stonePartOfMill[from]); + + // still in setting phase ? + } else if (theField->settingPhase) { + + // search a free square + do { + from = theField->size; + to = rand() % theField->size; + } while (theField->field[to] != theField->squareIsFree); + + // try to push randomly + } else { + + do { + // search an own stone + do { + from = rand() % theField->size; + } while (theField->field[from] != theField->curPlayer->id); + + // select a free square + if (allowedToSpring) { + do { + to = rand() % theField->size; + } while (theField->field[to] != theField->squareIsFree); + + // select a connected square + } else { + do { + direction = rand() % 4; + to = theField->connectedSquare[from][direction]; + } while (to == theField->size); + } + + } while (theField->field[to] != theField->squareIsFree); + } + + *pushFrom = from; + *pushTo = to; +} diff --git a/src/perfect/randomKI.h b/src/perfect/randomKI.h new file mode 100644 index 00000000..88dd5661 --- /dev/null +++ b/src/perfect/randomKI.h @@ -0,0 +1,28 @@ +/*********************************************************************\ + randomKI.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef RANDOM_KI_H +#define RANDOM_KI_H + +#include +#include +#include "muehleKI.h" + +/*** Klassen *********************************************************/ + +class randomKI : public muehleKI +{ +public: + // Constructor / destructor + randomKI(); + ~randomKI(); + + // Functions + void play(fieldStruct *theField, unsigned int *pushFrom, unsigned int *pushTo); +}; + +#endif + diff --git a/src/perfect/strLib.cpp b/src/perfect/strLib.cpp new file mode 100644 index 00000000..3a7dfceb --- /dev/null +++ b/src/perfect/strLib.cpp @@ -0,0 +1,250 @@ +/********************************************************************* + strLib.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "strLib.h" + +//----------------------------------------------------------------------------- +// Name: hibit() +// Desc: +//----------------------------------------------------------------------------- +int mystring::hibit(unsigned int n) +{ + n |= (n >> 1); + n |= (n >> 2); + n |= (n >> 4); + n |= (n >> 8); + n |= (n >> 16); + return n - (n >> 1); +} + +//----------------------------------------------------------------------------- +// Name: mystring() +// Desc: +//----------------------------------------------------------------------------- +mystring::mystring() +{ +} + +//----------------------------------------------------------------------------- +// Name: mystring() +// Desc: +//----------------------------------------------------------------------------- +mystring::mystring(const char *cStr) +{ + assign(cStr); +} + +//----------------------------------------------------------------------------- +// Name: mystring() +// Desc: +//----------------------------------------------------------------------------- +mystring::mystring(const WCHAR *cStr) +{ + assign(cStr); +} + +//----------------------------------------------------------------------------- +// Name: mystring() +// Desc: +//----------------------------------------------------------------------------- +mystring::~mystring() +{ + if (strA != nullptr) { delete [] strA; strA = nullptr; } + if (strW != nullptr) { delete [] strW; strW = nullptr; } + strW = nullptr; + strA = nullptr; + length = 0; + reserved = 0; +} + +//----------------------------------------------------------------------------- +// Name: c_strA() +// Desc: +//----------------------------------------------------------------------------- +const char * mystring::c_strA() +{ + return strA; +} + +//----------------------------------------------------------------------------- +// Name: c_strW() +// Desc: +//----------------------------------------------------------------------------- +const WCHAR * mystring::c_strW() +{ + return strW; +} + +//----------------------------------------------------------------------------- +// Name: assign() +// Desc: +//----------------------------------------------------------------------------- +mystring & mystring::assign(const char *cStr) +{ + // locals + size_t convertedChars = 0; + size_t newLength = strlen(cStr); + size_t newReserved = (size_t) hibit((unsigned int) newLength) * 2; + + if (reserved < newReserved) this->~mystring(); + if (strA == nullptr) strA = new char [newReserved]; + if (strW == nullptr) strW = new WCHAR[newReserved]; + + reserved = newReserved; + length = newLength; + + strcpy_s(strA, newReserved, cStr); + mbstowcs_s(&convertedChars, strW, newLength+1, cStr, _TRUNCATE); + + return *this; +} + +//----------------------------------------------------------------------------- +// Name: assign() +// Desc: +//----------------------------------------------------------------------------- +mystring & mystring::assign(const WCHAR *cStr) +{ + // locals + size_t returnValue; + size_t newLength = wcslen(cStr); + size_t newReserved = (size_t) hibit((unsigned int) newLength) * 2; + + if (reserved < newReserved) this->~mystring(); + if (strA == nullptr) strA = new char [newReserved]; + if (strW == nullptr) strW = new WCHAR[newReserved]; + + reserved = newReserved; + length = newLength; + + wcscpy_s(strW, newReserved, cStr); + wcstombs_s(&returnValue, strA, newLength+1, cStr, newLength+1); + + return *this; +} + +//----------------------------------------------------------------------------- +// Name: readAsciiData() +// Desc: This functions reads in a table of floating point values faster than "cin". +//----------------------------------------------------------------------------- +bool readAsciiData(HANDLE hFile, double* pData, unsigned int numValues, unsigned char decimalSeperator, unsigned char columnSeparator) +{ + // constants + const unsigned int maxValueLengthInBytes = 32; + const unsigned int bufferSize = 1000; + + // locals + DWORD dwBytesRead; + unsigned char buffer[bufferSize]; + unsigned char * curByte = &buffer[0]; + unsigned int curReadValue = 0; + unsigned int actualBufferSize = 0; + unsigned int curBufferPos = bufferSize; + unsigned int decimalPos = 0; + int integralValue = 0; // ACHTUNG: Erlaubt nur 8 Vorkommastellen + int fractionalValue = 0; // ACHTUNG: Erlaubt nur 8 Nachkommastellen + int exponentialValue = 1; + bool valIsNegativ = false; + bool expIsNegativ = false; + bool decimalPlace = false; + bool exponent = false; + double fractionalFactor[] = { 0, + 0.1, + 0.01, + 0.001, + 0.0001, + 0.00001, + 0.000001, + 0.0000001, + 0.00000001, + 0.000000001, + 0.0000000001 }; + + // read each value + do { + + // read from buffer if necessary + if (curBufferPos >= bufferSize - maxValueLengthInBytes) { + memcpy(&buffer[0], &buffer[curBufferPos], bufferSize - curBufferPos); + ReadFile(hFile, &buffer[bufferSize - curBufferPos], curBufferPos, &dwBytesRead, nullptr); + actualBufferSize= bufferSize - curBufferPos + dwBytesRead; + curBufferPos = 0; + curByte = &buffer[curBufferPos]; + } + + // process current byte + switch (*curByte) + { + case '-': if (exponent) { expIsNegativ = true; } else { valIsNegativ = true; } break; + case '+': /* ignore */ break; + case 'e': case 'E': exponent = true; decimalPlace = false; break; + case '0': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 0; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 0; } else { integralValue *= 10; integralValue += 0; } break; + case '1': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 1; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 1; } else { integralValue *= 10; integralValue += 1; } break; + case '2': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 2; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 2; } else { integralValue *= 10; integralValue += 2; } break; + case '3': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 3; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 3; } else { integralValue *= 10; integralValue += 3; } break; + case '4': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 4; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 4; } else { integralValue *= 10; integralValue += 4; } break; + case '5': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 5; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 5; } else { integralValue *= 10; integralValue += 5; } break; + case '6': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 6; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 6; } else { integralValue *= 10; integralValue += 6; } break; + case '7': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 7; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 7; } else { integralValue *= 10; integralValue += 7; } break; + case '8': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 8; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 8; } else { integralValue *= 10; integralValue += 8; } break; + case '9': if (decimalPlace) { fractionalValue *= 10; fractionalValue += 9; decimalPos++; } else if (exponent) { exponentialValue *= 10; exponentialValue += 9; } else { integralValue *= 10; integralValue += 9; } break; + default: + if (*curByte == decimalSeperator) { + decimalPlace = true; + exponent = false; + } else if (*curByte == columnSeparator) { + + // everything ok? + if (decimalPos > 8) { + cout << "ERROR in function readAsciiData(): Too many digits on decimal place. Maximum is 8 !" << endl; + return false; + } + + // calc final value + (*pData) = integralValue; + if (decimalPos) { + (*pData) += fractionalValue * fractionalFactor[decimalPos]; + } + if (valIsNegativ ) { + (*pData) *= -1; + } + if (exponent) { + (*pData) *= pow(10, expIsNegativ ? -1*exponentialValue : 1); + } + + // init + valIsNegativ = false; + expIsNegativ = false; + decimalPlace = false; + exponent = false; + integralValue = 0; + fractionalValue = 0; + exponentialValue = 1; + decimalPos = 0; + + // save value + pData++; + curReadValue++; + + } else { + // do nothing + } + break; + } + + // consider next byte + curBufferPos++; + curByte++; + + // buffer overrun? + if (curBufferPos >= actualBufferSize) return false; + + } while (curReadValue < numValues); + + // quit + return true; +} diff --git a/src/perfect/strLib.h b/src/perfect/strLib.h new file mode 100644 index 00000000..bf8b8870 --- /dev/null +++ b/src/perfect/strLib.h @@ -0,0 +1,50 @@ +/*********************************************************************\ + strLib.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef STRLIB_H +#define STRLIB_H + +#include +#include +#include +#include +#include +#include + +using namespace std; + +// general functions +bool readAsciiData(HANDLE hFile, double* pData, unsigned int numValues, unsigned char decimalSeperator, unsigned char columnSeparator); + +class mystring +{ +private: + + // variables + WCHAR* strW = nullptr; + char * strA = nullptr; + size_t length = 0; + size_t reserved = 0; + + // functions + +public: + + // functions + mystring (); + mystring (const char *cStr); + mystring (const WCHAR *cStr); + ~mystring (); + + const char * c_strA (); + const WCHAR * c_strW (); + mystring & assign (const char *cStr); + mystring & assign (const WCHAR *cStr); + + static int hibit (unsigned int n); +}; + +#endif diff --git a/src/perfect/threadManager.cpp b/src/perfect/threadManager.cpp new file mode 100644 index 00000000..77087ae2 --- /dev/null +++ b/src/perfect/threadManager.cpp @@ -0,0 +1,404 @@ +/********************************************************************* + threadManager.cpp + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ + +#include "threadManager.h" + +//----------------------------------------------------------------------------- +// Name: threadManagerClass() +// Desc: threadManagerClass class constructor +//----------------------------------------------------------------------------- +threadManagerClass::threadManagerClass() +{ + // locals + unsigned int curThreadNo; + SYSTEM_INFO m_si = {0}; + + GetSystemInfo(&m_si); + + // init default values + executionPaused = false; + executionCancelled = false; + numThreads = m_si.dwNumberOfProcessors; + hThread = new HANDLE[numThreads]; + threadId = new DWORD [numThreads]; + hBarrier = new HANDLE[numThreads]; + numThreadsPassedBarrier = 0; + + InitializeCriticalSection(&csBarrier); + hEventBarrierPassedByEveryBody = CreateEvent(NULL, true, false, NULL); + + for (curThreadNo=0; curThreadNo0)"; + if (numThreadsPassedBarrier>0) { + WaitForSingleObject(hEventBarrierPassedByEveryBody, INFINITE); + } + + // a simple while (numThreadsPassedBarrier>0) {}; does not work, since the variable 'numThreadsPassedBarrier' is not updated, due to compiler optimizations + + // set signal that barrier is reached +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "SetEvent()"; + SetEvent(hBarrier[threadNo]); + + // enter the barrier one by one +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "EnterCriticalSection()"; + EnterCriticalSection(&csBarrier); + + // if the first one which entered, then wait until other threads + if (numThreadsPassedBarrier==0) { +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "WaitForMultipleObjects()"; + WaitForMultipleObjects(numThreads, hBarrier, TRUE, INFINITE); + ResetEvent(hEventBarrierPassedByEveryBody); + } + + // count threads which passed the barrier +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "numThreadsPassedBarrier++"; + numThreadsPassedBarrier++; + + // the last one closes the door +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "if (numThreadsPassedBarrier == numThreads) numThreadsPassedBarrier = 0"; + if (numThreadsPassedBarrier == numThreads) { + numThreadsPassedBarrier = 0; + SetEvent(hEventBarrierPassedByEveryBody); + } + +//cout << endl << "thread=" << threadNo << ", numThreadsPassedBarrier= " << numThreadsPassedBarrier << ": " << "LeaveCriticalSection()"; + LeaveCriticalSection(&csBarrier); +} + +//----------------------------------------------------------------------------- +// Name: getNumThreads() +// Desc: +//----------------------------------------------------------------------------- +unsigned int threadManagerClass::getNumThreads() +{ + return numThreads; +} + +//----------------------------------------------------------------------------- +// Name: setNumThreads() +// Desc: +//----------------------------------------------------------------------------- +bool threadManagerClass::setNumThreads(unsigned int newNumThreads) +{ + // cancel if any thread running + EnterCriticalSection(&csBarrier); + for (unsigned int curThreadNo=0; curThreadNo0; curThreadNo--) { + CloseHandle(hThread[curThreadNo-1]); + hThread[curThreadNo-1] = NULL; + } + return TM_RETURN_VALUE_UNEXPECTED_ERROR; + } + } + + // start threads + for (curThreadNo=0; curThreadNo= TM_SCHEDULE_NUM_TYPES) return TM_RETURN_VALUE_INVALID_PARAM; + if (inkrement == 0) return TM_RETURN_VALUE_INVALID_PARAM; + if (abs(finalValue-initialValue)==abs(inkrement)) return TM_RETURN_VALUE_INVALID_PARAM; + + // locals + unsigned int curThreadNo; // the threads are enumerated from 0 to numThreads-1 + int numIterations = (finalValue - initialValue) / inkrement + 1; // total number of iterations + int chunkSize = 0; // number of iterations per chunk + SIZE_T dwStackSize = 0; // initital stack size of each thread. 0 means default size ~1MB + forLoopStruct * forLoopParameters = new forLoopStruct[numThreads]; // + + // globals + termineAllThreads = false; + + // create threads + for (curThreadNo=0; curThreadNo0; curThreadNo--) { + CloseHandle(hThread[curThreadNo-1]); + hThread[curThreadNo-1] = NULL; + } + return TM_RETURN_VALUE_UNEXPECTED_ERROR; + } + //DWORD dwThreadAffinityMask = 1 << curThreadNo; + //SetThreadAffinityMask(hThread[curThreadNo], &dwThreadAffinityMask); + } + + // start threads, but don't resume if in pause mode + for (curThreadNo=0; curThreadNoscheduleType) + { + case TM_SCHEDULE_STATIC: + for (index=forLoopParameters->initialValue; (forLoopParameters->inkrement<0) ? index >= forLoopParameters->finalValue : index <= forLoopParameters->finalValue; index += forLoopParameters->inkrement) { + switch (forLoopParameters->threadProc(forLoopParameters->pParameter, index)) + { + case TM_RETURN_VALUE_OK: + break; + case TM_RETURN_VALUE_TERMINATE_ALL_THREADS: + forLoopParameters->threadManager->termineAllThreads = true; + break; + default: + break; + } + if (forLoopParameters->threadManager->termineAllThreads) break; + } + break; + case TM_SCHEDULE_DYNAMIC: + return TM_RETURN_VALUE_INVALID_PARAM; + break; + case TM_SCHEDULE_GUIDED: + return TM_RETURN_VALUE_INVALID_PARAM; + break; + case TM_SCHEDULE_RUNTIME: + return TM_RETURN_VALUE_INVALID_PARAM; + break; + } + + return TM_RETURN_VALUE_OK; +} + +/*** To Do's ******************************************************************************** +- Beschränkung auf 'int' kann zu Überlauf führen, wenn mehr states in einer layer vorliegen. + ==> Vielleicht mit class templates arbeiten +*********************************************************************************************/ \ No newline at end of file diff --git a/src/perfect/threadManager.h b/src/perfect/threadManager.h new file mode 100644 index 00000000..c4ac9984 --- /dev/null +++ b/src/perfect/threadManager.h @@ -0,0 +1,147 @@ +/*********************************************************************\ + threadManager.h + Copyright (c) Thomas Weber. All rights reserved. + Licensed under the MIT License. + https://github.com/madweasel/madweasels-cpp +\*********************************************************************/ +#ifndef THREADMANAGER_H +#define THREADMANAGER_H + +// standard library & win32 api +#include +#include +#include + +using namespace std; // use standard library namespace + +/*** Konstanten ******************************************************/ +#define TM_SCHEDULE_USER_DEFINED 0 +#define TM_SCHEDULE_STATIC 1 +#define TM_SCHEDULE_DYNAMIC 2 +#define TM_SCHEDULE_GUIDED 3 +#define TM_SCHEDULE_RUNTIME 4 +#define TM_SCHEDULE_NUM_TYPES 5 + +#define TM_RETURN_VALUE_OK 0 +#define TM_RETURN_VALUE_TERMINATE_ALL_THREADS 1 +#define TM_RETURN_VALUE_EXECUTION_CANCELLED 2 +#define TM_RETURN_VALUE_INVALID_PARAM 3 +#define TM_RETURN_VALUE_UNEXPECTED_ERROR 4 + +/*** Makros ******************************************************/ + +/*** Strukturen ******************************************************/ + +/*** Klassen *********************************************************/ + +class threadManagerClass +{ +private: + + // structures + struct forLoopStruct + { + unsigned int scheduleType; + int inkrement; + int initialValue; + int finalValue; + void * pParameter; + DWORD (*threadProc)(void* pParameter, int index); // pointer to the user function to be executed by the threads + threadManagerClass *threadManager; + }; + + // Variables + unsigned int numThreads; // number of threads + HANDLE * hThread; // array of size 'numThreads' containing the thread handles + DWORD * threadId; // array of size 'numThreads' containing the thread ids + bool termineAllThreads; + bool executionPaused; // switch for the + bool executionCancelled; // true when cancelExecution() was called + + // barier stuff + HANDLE hEventBarrierPassedByEveryBody; + HANDLE * hBarrier; // array of size 'numThreads' containing the event handles for the barrier + unsigned int numThreadsPassedBarrier; + CRITICAL_SECTION csBarrier; + + // functions + static DWORD WINAPI threadForLoop (LPVOID lpParameter); + +public: + + class threadVarsArrayItem + { + public: + unsigned int curThreadNo; + + virtual void initializeElement () {}; + virtual void destroyElement () {}; + virtual void reduce () {}; + }; + + template class threadVarsArray + { + public: + unsigned int numberOfThreads; + varType * item; + + threadVarsArray(unsigned int numberOfThreads, varType& master) + { + this->numberOfThreads = numberOfThreads; + this->item = new varType[numberOfThreads]; + + for (unsigned int threadCounter=0; threadCounter