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