/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "bagel/hodjnpodj/hnplibs/stdafx.h" #include "bagel/hodjnpodj/mankala/mnk.h" #include "bagel/hodjnpodj/hnplibs/gamedll.h" #include "bagel/boflib/misc.h" namespace Bagel { namespace HodjNPodj { namespace Mankala { extern LPGAMESTRUCT pGameParams; // declared in mnk.cpp. bool gbTurnSoundsOff; // used by mnkui.cpp too. ///DEFS mnk.h static inline void DebugBreak() { } //* CMnk::InitMankala -- initialize a new game of Mankala bool CMnk::InitMankala() // returns: true if error, false otherwise { JXENTER(CMnk::InitMankala) ; int iError = 0 ; // error code int iPlayer, iPit ; // loop variables CMove * xpcMove = &m_cCurrentMove ; // pointer to current // position move object CPit * xpcPit ; // pointer to pit object xpcMove->Zero() ; // clear all data fields in current move/pos object xpcMove->m_bRealMove = true ; // but make this one the real one // m_iPlayer = m_iNumberMoves = 0 ; for (iPlayer = 0 ; iPlayer < NUMPLAYERS ; ++iPlayer) for (iPit = -2 ; iPit < NUMPITS ; ++iPit) { xpcPit = m_xpcPits[iPlayer][iPit + 2] ; // point to pit object if (xpcPit->m_iPlayer != iPlayer || xpcPit->m_iPit != iPit) // validity checking { //iError = 100 ; //goto cleanup ; } // xpcPit->m_iPlayer = iPlayer ; // xpcPit->m_iPit = iPit ; xpcPit->m_iNumStones = xpcMove->m_iNumStones[iPlayer][iPit + 2] = (iPit < 0) ? 0 // no stones in home/hand : m_iStartStones ; } /*cleanup */ JXELEAVE(CMnk::InitMankala) ; RETURN(iError != 0) ; } //* CMnk::Move -- make a move bool CMnk::Move(CPit * xpcSowPit, CMove * xpcMove) // xpcSowPit -- ptr to pit object whose stones are being sowed // xpcMove -- move object for position to start from; if nullptr, // then use the move object for current position on // the board, and make the actual moves on the board // returns: true if error, false otherwise { JXENTER(CMnk::Move) ; int iError = 0 ; // error code int iNumStones ; // number of stones to be sowed int iStone ; // loop variable bool bStonesFound, bOtherStonesFound ; // flag: stones found, game not over int iTemp ; // temp variable for swapping players int iPlayer = xpcSowPit->m_iPlayer, iPit = xpcSowPit->m_iPit ; int iOtherPlayer = OTHERPLAYER(iPlayer) ; // other player (0 or 1) int iTempPlayer; CPit * xpcHandPit = m_xpcPits[iPlayer][HANDINDEX + 2] ; // hand pit CPit * xpcHomePit = m_xpcPits[iPlayer][HOMEINDEX + 2] ; // home bin CPit * xpcPit ; // target pit // bool B; MSG msg; //_asm int 3; //to track GPf if (!xpcMove) // if no starting position specified xpcMove = &m_cCurrentMove ; // then it's a board move iNumStones = xpcMove->m_iNumStones [xpcMove->m_iPlayer][xpcSowPit->m_iPit + 2] ; // get # stones being moved if (iNumStones <= 0) { // if no stones iError = 100 ; // attempt to sow an empty pit goto cleanup ; } xpcMove->m_xpcPit = xpcSowPit ; // save ptr to pit being sowed xpcMove->m_bFreeTurn = xpcMove->m_bCapture = false ; //reset FreeTurn, Capture indicators. if (xpcMove->m_bRealMove && m_bDumpMoves) DumpPosition(xpcMove) ; /* move the stones from the bin to the hand */ if (pGameParams->bSoundEffectsEnabled) sndPlaySound(PICKUP, SND_ASYNC); for (iStone = iNumStones ; iStone-- > 0 ;) if (MoveStone(xpcMove, xpcSowPit, xpcHandPit)) { iError = 101 ; // internal error moving stone goto cleanup ; } /* move stones from the hands to the individual pits */ for (iStone = iNumStones ; iStone-- > 0 ;) { if (iPit < 0 || (iPit == 0 && iPlayer != xpcMove->m_xpcPit->m_iPlayer)) // if last iteration placed a stone in my home bin // or in my opponent's last pit iPit = NUMPITS - 1, iTemp = iPlayer, iPlayer = iOtherPlayer, iOtherPlayer = iTemp ; // new pit is last pit on other side -- swap // player and other player else --iPit ; // otherwise, just decrement to get pit # xpcPit = m_xpcPits[iPlayer][iPit + 2] ; // pt to target pit if (pGameParams->bSoundEffectsEnabled) sndPlaySound(ONESHELL, SND_ASYNC); if (MoveStone(xpcMove, xpcHandPit, xpcPit)) // move stone from hand to target pit, test for error { iError = 102 ; goto cleanup ; } } if (iPit < 0) // if my last move was into my home bin xpcMove->m_bFreeTurn = true ; // I get a free turn else if (iPlayer == xpcMove->m_xpcPit->m_iPlayer && xpcMove->m_iNumStones[iPlayer][iPit + 2] == 1) { // if the last stone went into a previously empty pit if (pGameParams->bSoundEffectsEnabled) sndPlaySound(PICKUP, SND_ASYNC); xpcMove->m_bCapture = true ; // we have a capture xpcMove->m_iCapturePit = NUMPITS - 1 - iPit ; // number of opponent's pit being captured xpcPit = m_xpcPits[iOtherPlayer][xpcMove->m_iCapturePit + 2] ; // point to pit object for captured pit iNumStones = xpcMove->m_iNumStones[iOtherPlayer][xpcMove->m_iCapturePit + 2] ; while (iNumStones-- > 0) // count stones MoveStone(xpcMove, xpcPit, xpcHomePit) ; // move stone from captured pit to my home bin pit } if (!xpcMove->m_bFreeTurn) // if there's no free turn xpcMove->m_iPlayer = OTHERPLAYER(xpcMove->m_iPlayer) ; // then other player's turn iPlayer = xpcMove->m_iPlayer ; iOtherPlayer = OTHERPLAYER(iPlayer) ; // check whether there are any moves to be played now -- if not, // the game is over bStonesFound = false ; // no stones found yet bOtherStonesFound = false; for (iPit = 0 ; iPit < NUMPITS ; iPit++) { bStonesFound = bStonesFound || (xpcMove->m_iNumStones[iPlayer][iPit + 2] > 0) ; bOtherStonesFound = bOtherStonesFound || xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][iPit + 2]; } while (MFC::PeekMessage(&msg, nullptr, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)); //flush out pending mouse clicks // test for stones in this pit if (pGameParams->bSoundEffectsEnabled && !gbTurnSoundsOff && bStonesFound && bOtherStonesFound) { if (MFC::PeekMessage(&msg, nullptr, MM_MCINOTIFY, MM_MCINOTIFY, PM_REMOVE)) { MFC::TranslateMessage(&msg); MFC::DispatchMessage(&msg); } if (!xpcMove->m_bFreeTurn) sndPlaySound(iPlayer ? IGO3 : YOUGO3, SND_SYNC); else sndPlaySound(iPlayer ? IGOAGAIN : GOAGAIN, SND_SYNC); } //#endif if ((m_bGameOver = (!bStonesFound || !bOtherStonesFound))) { // game is over if // no stones found -- tally the results /* depending upon who has no stones left, for each of the other player's pits, transfer the stones to the corresponding home bin */ iTempPlayer = !bStonesFound ? iOtherPlayer : iPlayer; xpcHomePit = m_xpcPits[ iTempPlayer][-1 + 2] ; // home bin for (iPit = 0 ; iPit < NUMPITS ; iPit++) { xpcPit = m_xpcPits[iTempPlayer][iPit + 2] ; iNumStones = xpcMove->m_iNumStones[iTempPlayer][iPit + 2] ; // get number of stones while (iNumStones-- > 0) // count stones MoveStone(xpcMove, (CPitWnd *)xpcPit, (CPitWnd *)xpcHomePit) ; // move stone from pit to my home bin } } // ... winner is the one with more stones in home bin if (xpcMove->m_bRealMove && m_bDumpMoves) DumpPosition(xpcMove) ; #define DECENT_CODE 0 #if DECENT_CODE //"THE FOLLOWING SECTION OF CODE IS RUINED"...(nish) CMove* xpcStoreMove; int iMoveIndex; if (xpcMove->m_bRealMove) { // if it's real move iMoveIndex = m_iNumberMoves++ ; // get move index, incr count xpcStoreMove = m_cMoveList + iMoveIndex ; // point to correct slot xpcStoreMove->Copy(xpcMove) ; // copy } #endif cleanup: JXELEAVE(CMnk::Move) ; RETURN(iError != 0) ; } //* CMnk::MoveStone -- move one stone for move bool CMnk::MoveStone(CMove * xpcMove, CPit * xpcFromPit, CPit * xpcToPit) // xpcMove -- position/move object in which move takes place // xpcFromPit -- source pit (where stone comes from) // xpcToPit -- target pit (where stone goes) // returns: true if error, false otherwise { JXENTER(CMnk::MoveStone) ; int iError = 0 ; // error code --xpcMove->m_iNumStones[xpcFromPit->m_iPlayer] [xpcFromPit->m_iPit + 2] ; if (xpcMove->m_bRealMove) // if this is a real move ((CMnkWindow *)this)->MoveStoneDisplay((CPitWnd *)xpcFromPit, (CPitWnd *)xpcToPit) ; // move display of stone from one pit to another ++xpcMove->m_iNumStones[xpcToPit->m_iPlayer][xpcToPit->m_iPit + 2] ; /* MSG msg; if(MFC::PeekMessage(&msg,nullptr,MM_MCINOTIFY, MM_MCINOTIFY,PM_REMOVE)){ MFC::TranslateMessage(&msg); MFC::DispatchMessage(&msg); } */ // cleanup: JXELEAVE(CMnk::MoveStone) ; RETURN(iError != 0) ; } //* CMnk::InitData -- initialize data class object bool CMnk::InitData(bool bInit) // bInit -- if false, release data // returns: true if error, false otherwise { JXENTER(CMnk::InitData) ; int iError = 0 ; // error code // long lTableSize ; // size of lookup table long lK ; // loop variable int iJ ; // loop variable byte *hpcTab; // table pointer for initialization byte *xpcFive; // ptr to Five structure struct FIVE stFive = {TABLEUNDEF, // 0 TABLEUNDEF >> 2, TABLEUNDEF & 3, TABLEUNDEF, // 1-2 TABLEUNDEF >> 4, TABLEUNDEF & 15, // 3 TABLEUNDEF >> 1, TABLEUNDEF & 1, TABLEUNDEF, // 4-5 TABLEUNDEF >> 3, TABLEUNDEF & 7, TABLEUNDEF } ; // 6-7 if (bInit) { // if we're initializing if (m_iTableStones > MAXTABLESTONES) // parm set too high m_iTableStones = MAXTABLESTONES ; if (!(m_lpCMnkData = new FAR CMnkData)) { iError = 100 ; // can't allocate CMnkData goto cleanup ; } if ((iError = CountConfigurations())) goto cleanup ; m_lNumConfigs = m_lpCMnkData->m_NA[m_iTableStones][TOTALPITS] ; // m_lTableSize = (MAXCONFIGS/8 + 1)*5 ; // table length in bytes m_lTableSize = (m_lNumConfigs / 8 + 2) * 5 ; // table length in bytes if (!(m_lpCMnkData->m_hBestWin = GlobalAlloc(0, m_lTableSize))) // allocate global handle and test { iError = 110 ; // Windows GlobalAlloc failure goto cleanup ; } if (!(m_lpCMnkData->m_hpcBestWin = (byte *)GlobalLock(m_lpCMnkData->m_hBestWin))) { iError = 111 ; // Windows GlobalLock failure goto cleanup ; } #if 0 if (!(m_lpCMnkData->m_hpcBestWin = new byte[m_lTableSize + 8])) { iError = 101 ; // can't allocate best win array goto cleanup ; } #endif if (m_bInitData) { // if we're initializing data tables // init best win table to all undefined values for (lK = m_lTableSize / sizeof(struct FIVE), hpcTab = m_lpCMnkData->m_hpcBestWin ; lK-- > 0 ;) for (iJ = sizeof(struct FIVE), xpcFive = (byte *)&stFive ; iJ-- > 0 ;) *hpcTab++ = *xpcFive++ ; PopulateTable() ; // populate the best win table WriteTableFile() ; // write best win table to disk } else { // initialized data is already available //ReadTableFile() ; // read best win table from disk } } else { // we're releasing table if (m_lpCMnkData) { // if data table locked GlobalUnlock(m_lpCMnkData->m_hBestWin) ; // unlock it m_lpCMnkData = nullptr ; // clear pointer } if (m_lpCMnkData->m_hBestWin) // if allocated GlobalFree(m_lpCMnkData->m_hBestWin), m_lpCMnkData->m_hBestWin = nullptr ; // free it #if 0 if (m_lpCMnkData) { // if data table allocated if (m_lpCMnkData->m_hpcBestWin) // if there's a best win table delete m_lpCMnkData->m_hpcBestWin, m_lpCMnkData->m_hpcBestWin = nullptr ; delete m_lpCMnkData ; m_lpCMnkData = nullptr ; } #endif } //delete m_lpCMnkData ; //m_lpCMnkData = nullptr ; cleanup: JXELEAVE(CMnk::InitData) ; RETURN(iError != 0) ; } //* CMnk::CountConfigurations -- set up Configurations table bool CMnk::CountConfigurations() // returns: true if error, false otherwise { JXENTER(CMnk::CountConfigurations) ; int iError = 0 ; // error code int iStone, iPit, iK ; // loop variables for (iStone = 0 ; iStone <= m_iTableStones ; ++iStone) m_lpCMnkData->m_NX[iStone][1] = 1 ; // if there's only one // pit, then there's only one configuration for (iPit = 2 ; iPit <= TOTALPITS ; ++iPit) for (iStone = 0 ; iStone <= m_iTableStones ; ++iStone) { m_lpCMnkData->m_NX[iStone][iPit] = 0 ; for (iK = 0 ; iK <= iStone ; ++iK) m_lpCMnkData->m_NX[iStone][iPit] += m_lpCMnkData->m_NX[iK][iPit - 1] ; } for (iPit = 1 ; iPit <= TOTALPITS ; ++iPit) for (iStone = 0 ; iStone <= m_iTableStones ; ++iStone) { m_lpCMnkData->m_NA[iStone][iPit] = 0 ; for (iK = 0 ; iK <= iStone ; ++iK) m_lpCMnkData->m_NA[iStone][iPit] += m_lpCMnkData->m_NX[iK][iPit] ; } // cleanup: JXELEAVE(CMnk::CountConfigurations) ; RETURN(iError != 0) ; } //* CMnk::PopulateTable -- compute values for best win table bool CMnk::PopulateTable() { JXENTER(CMnk::PopulateTable) ; int iError = 0 ; // error code uint lConfigIndex ; // loop variable CMove cMove ; // move structure m_iCurrentMaxDepth = 3 ; // minimax depth is 3 for table populate m_iCurrentCapDepth = 0 ; // capture depth is 0 for (lConfigIndex = 1 ; lConfigIndex < MAXCONFIGS - 1 ; ++lConfigIndex) { cMove.Zero() ; // clear move/position object cMove.m_lConfigIndex = lConfigIndex ; // store into move object if (UnmapConfiguration(&cMove)) // translate configuration index // into a configuration, test for error { iError = 100 ; // UnmapConfiguration error goto cleanup ; } if (Minimax(&cMove)) { // find move, and test for error iError = 101 ; // Minimax error goto cleanup ; } if (SetBestWinCount(&cMove)) { iError = 102 ; // SetBestWinCount error goto cleanup ; } } cleanup: if (iError) iError += 100 ; JXELEAVE(CMnk::PopulateTable) ; RETURN(iError != 0) ; } bool CMnk::WriteTableFile() { int iError = 0; // error code JXENTER(CMnk::WriteTableFile); Common::strcpy_s(m_lpCMnkData->m_cFileHeader.m_szText, "Mankala data file, version 1.0, " "May, 1994, by John J. Xenakis"); m_lpCMnkData->m_cFileHeader.m_iHeaderSize = sizeof(CFileHeader) ; m_lpCMnkData->m_cFileHeader.m_iVersion = 100 ; m_lpCMnkData->m_cFileHeader.m_iTableStones = m_iTableStones ; m_lpCMnkData->m_cFileHeader.m_lTableSize = m_lTableSize ; Common::WriteStream *ws = g_system->getSavefileManager()->openForSaving("mankala.dat"); if (ws) { Common::Serializer s(nullptr, ws); m_lpCMnkData->m_cFileHeader.sync(s); ws->finalize(); } else { // Can't create file iError = 100; } delete ws; JXELEAVE(CMnk::WriteTableFile) ; RETURN iError != 0; } bool CMnk::ReadTableFile() { JXENTER(CMnk::ReadTableFile) ; int iError = 0 ; // error code Common::SeekableReadStream *rs = g_system->getSavefileManager()->openForLoading("mankala.dat"); if (rs) { Common::Serializer s(rs, nullptr); m_lpCMnkData->m_cFileHeader.sync(s); if (m_lpCMnkData->m_cFileHeader.m_iVersion != 100) { iError = 102; // invalid file header; } else { if (m_iTableStones > m_lpCMnkData->m_cFileHeader.m_iTableStones) m_iTableStones = m_lpCMnkData->m_cFileHeader.m_iTableStones; if (m_lTableSize > m_lpCMnkData->m_cFileHeader.m_lTableSize) m_lTableSize = m_lpCMnkData->m_cFileHeader.m_lTableSize; //lTotalCount = m_lTableSize; // total number of bytes to be read //hpData = m_lpCMnkData->m_hpcBestWin; // point to beginning of table } } else { iError = 100; // can't open file } delete rs; JXELEAVE(CMnk::ReadTableFile); RETURN(iError != 0); } //* CMnk::MapConfiguration -- map a configuration to its integer index, // store configuration index into Move object bool CMnk::MapConfiguration(CMove * xpcMove) // xpcMove -- CMove object containing the configuration // returns: true if error, false otherwise { JXENTER(CMnk::MapConfiguration) ; int iError = 0 ; // error code long lConfigIndex = 0 ; // return value, configuration index int iStones ; // total number of stones int iPlayer = xpcMove->m_iPlayer ; int iOtherPlayer = OTHERPLAYER(iPlayer) ; int iPit, iPitCount; // #pit, #stones in pit ; int iK ; // loop variable CountStones(xpcMove) ; // set m_iTotalStones iStones = xpcMove->m_iTotalStones ; if (iStones > m_iTableStones) // if number of stones // exceeds the number we're supporting lConfigIndex = -1 ; else { iPit = TOTALPITS ; // start from pit 12 iPitCount = -1 ; // force fetch of # stones in pit lConfigIndex = (iStones == 0) ? 0 : m_lpCMnkData->m_NA[iStones - 1][TOTALPITS] ; // start from end of table as of previous # stones for (iK = 1 ; iK <= iStones ; ++iK) { while (iPitCount <= 0) { // find a stone if (iPitCount == 0 && --iPit < 1) { iError = 100 ; // logic error in algorithm goto cleanup ; } // set iPitCount to the number of stones in iPit if (iPit <= NUMPITS) // pit for current player iPitCount = xpcMove->m_iNumStones[iPlayer] [iPit - 1 + 2] ; else // pit for other player iPitCount = xpcMove->m_iNumStones [iOtherPlayer] [iPit - (NUMPITS + 1) + 2] ; } lConfigIndex += m_lpCMnkData->m_NX[iStones - iK + 1] [iPit - 1] ; --iPitCount ; // one less stone to count in this pit } ++lConfigIndex ; // index begins at 1 } cleanup: xpcMove->m_lConfigIndex = lConfigIndex ; // store result JXELEAVE(CMnk::MapConfiguration) ; RETURN(iError != 0) ; } //* CMnk::UnmapConfiguration -- map configuration index back // to configuration bool CMnk::UnmapConfiguration(CMove * xpcMove) // xpcMove -- CMove object containing the configuration / index // returns: true if error, false otherwise { JXENTER(CMnk::UnmapConfiguration) ; int iError = 0 ; // error code long lConfigIndex = xpcMove->m_lConfigIndex - 1 ; // config index long lConfigSave = lConfigIndex + 1 ; // save for debugging int iPlayer = xpcMove->m_iPlayer ; int iOtherPlayer = OTHERPLAYER(iPlayer) ; int iStones ; // total number of stones int iPit, iPitCur ; // loop variable int iK ; // loop variable long lValue = 0L, lValueNew ; // table values char szDebugStr[200] ; // debugging string if (lConfigIndex < 0 || (uint)lConfigIndex >= MAXCONFIGS) { iError = 100 ; // config index is out of range goto cleanup ; } // find number of stones in configuration // xpcMove->m_iTotalStones = 0 ; for (iStones = 0 ; iStones <= m_iTableStones && (lValueNew = m_lpCMnkData->m_NA[iStones][TOTALPITS]) <= lConfigIndex ; ++iStones, lValue = lValueNew) ; // null loop body xpcMove->m_iTotalStones = iStones ; if (iStones > m_iTableStones) { iError = 101 ; // can't compute number of stones goto cleanup ; } lConfigIndex -= lValue ; for (iK = xpcMove->m_iTotalStones ; iK >= 1 ; --iK) { // find which pit stone # iK is in iPit = 1 ; for (iPitCur = 1, lValue = 0L ; iPitCur <= TOTALPITS && (lValueNew = m_lpCMnkData->m_NX[iK][iPitCur]) <= lConfigIndex ; lValue = lValueNew, ++iPitCur) ; iPit = iPitCur ; // if (iPit > TOTALPITS) // { // iError = 120 + iK ; // can't compute pit # // goto cleanup ; // } lConfigIndex -= lValue ; // // ***** debugging // Common::sprintf_s(szDebugStr, "lConfig=%ld, #stones=%d, pit %d = %d\n", // lConfigSave, iStones, iK, iPit) ; // debug(szDebugStr) ; // increment stone count for appropriate pit if (iPit <= NUMPITS) // pit for current player ++xpcMove->m_iNumStones[iPlayer][iPit - 1 + 2] ; else // pit for other player ++xpcMove->m_iNumStones[iOtherPlayer] [iPit - (NUMPITS + 1) + 2] ; } // *** debugging MapConfiguration(xpcMove) ; if (lConfigSave != xpcMove->m_lConfigIndex) { // if unmap/map failed Common::sprintf_s(szDebugStr, "Config %ld changed to %ld.\n", lConfigSave, xpcMove->m_lConfigIndex) ; debug("%s", szDebugStr) ; DebugBreak(); } cleanup: JXELEAVE(CMnk::UnmapConfiguration) ; RETURN(iError != 0) ; } //* CMnk::SearchMove -- search for best move bool CMnk::SearchMove(CMove * xpcMove, int &iMove) // xpcMove -- pointer to move/position to find move for // iMove (output) -- recommended pit number // returns: true if error, false otherwise { JXENTER(CMnk::SearchMove) ; int iError = 0 ; // error code //bool bDone = false ; // flag: evaluations done //int iNumMoves = 0 ; // number of legal moves int iNumStones ; // number of stones in pit // bool bFree[NUMPITS], bCapture[NUMPITS] ; // int iValue[NUMPITS] ; // value of each pit int iPit ; // loop variable //int iLastPit = -1 ; // for finding lowest/highest # pit int iMaxValue = BESTWINUNDEF, iNumberAtMax = 0, tmpVal, maxtmpVal ; // computing maximum value // CPit * xpcPit ; // pit being processed int iPlayer = xpcMove->m_iPlayer ; // person on the move // CMove * xpcMove = &m_cCurrentMove ; // current move/position object bool bStonesFound; m_iCurrentMaxDepth = m_iMaxDepth[iPlayer] ; // minimax depth m_iCurrentCapDepth = m_iCapDepth[iPlayer] ; // capture depth for (iPit = 0 ; iPit < NUMPITS ; ++iPit) xpcMove->m_iValues[iPit] = BESTWINUNDEF ; // values undefined switch (m_eLevel[iPlayer]) // switch based on type of // move generation algorithm { case LEV_RANDOM: // handle these individually below break; case LEV_LOWEST: StaticEvaluation(xpcMove); // fall through case LEV_HIGHEST: AggressiveStaticEvaluation(xpcMove); break; case LEV_EVAL: // evaluate statically DefensiveStaticEvaluation(xpcMove) ; break ; case LEV_MINIMAX: TreeAlgo(xpcMove); //Minimax(xpcMove) ; // use minimax algorithm break ; default: iError = 100 ; // invalid m_eLevel value goto cleanup ; } for (iPit = 0, bStonesFound = false ; iPit < NUMPITS ; ++iPit) { iNumStones = xpcMove->m_iNumStones[iPlayer][iPit + 2]; bStonesFound = bStonesFound || iNumStones; if ((iNumStones > 0)) // get # stones, test // if this is a possible move switch (m_eLevel[iPlayer]) // switch based on type of // move generation algorithm { case LEV_RANDOM: // Random choice xpcMove->m_iValues[iPit] = 1 ; break ; case LEV_LOWEST: case LEV_HIGHEST: case LEV_EVAL: // these were handled above case LEV_MINIMAX: break ; default: iError = 101 ; // invalid m_eLevel value goto cleanup ; } } /* if(!bStonesFound){ iPit=-3; //-3 is a good choice because iPit=-1, iPit=-2 represent the hand and home bins. return(false); //let the while loop in mnkui.cpp that calls SearchMove continue, so that moving stones is automatic. } */ // loop thru pits, find those with maximum value for (iPit = 0 ; iPit < NUMPITS ; ++iPit) if (xpcMove->m_iValues[iPit] > iMaxValue) // new max iMaxValue = xpcMove->m_iValues[iPit], iNumberAtMax = 1 ; else if (xpcMove->m_iValues[iPit] == iMaxValue) ++iNumberAtMax ; if (iMaxValue <= BESTWINUNDEF) // if no move found (or there // are no legal moves) { iError = 110 ; // no move found goto cleanup ; } for (maxtmpVal = BESTWINUNDEF + 1, iPit = 0; iPit < NUMPITS ; ++iPit) { if (xpcMove->m_iValues[iPit] == iMaxValue) /* Among those with equal iValues, Select the least range pit of 'em all */ if ((tmpVal = iPit - xpcMove->m_iNumStones[xpcMove->m_iPlayer][iPit + 2]) > maxtmpVal) { iMove = iPit; maxtmpVal = tmpVal; } } cleanup: JXELEAVE(CMnk::SearchMove) ; RETURN(iError != 0) ; } //* CMnk::Minimax -- find best move from supplied configuration bool CMnk::Minimax(CMove * xpcMove, int iDepth) // xpcMove -- CMove object containing the configuration // iDepth -- minimax tree depth so far // returns: true if error, false otherwise { JXENTER(CMnk::Minimax) ; int iError = 0 ; // error code int iPlayer = xpcMove->m_iPlayer ; // player moving CMove cMoveBase, cMove ; // six possible moves int iLegalMoves = 0 ; // number of legal moves CPit * xpcSowPit ; // ptr to CPit object being sowed int iValue, iMaxValue = BESTWINUNDEF ; // move value for this move int iBestMove = -1 ; // best move bool bDone ; // flag: processing this move is done int iPit, iP ; // loop variable for (iPit = 0 ; iPit < NUMPITS ; ++iPit) xpcMove->m_iValues[iPit] = BESTWINUNDEF ; // values undefined memcpy(&cMoveBase, xpcMove, sizeof(CMove)) ; // form a base CMove object cMoveBase.m_iNumStones[0][HOMEINDEX + 2] = cMoveBase.m_iNumStones[1][HOMEINDEX + 2] = 0 ; // clear both players' home bins cMoveBase.m_bRealMove = false ; for (iPit = 0 ; iPit < NUMPITS ; ++iPit) { bDone = false ; // not done yet iValue = BESTWINUNDEF ; // no value yet if (xpcMove->m_iNumStones[iPlayer][iPit + 2] == 0) // if this pit has no stones in it bDone = true ; // nothing to do if (!bDone) { ++iLegalMoves ; // increment legal move count memcpy(&cMove, &cMoveBase, sizeof(CMove)) ; // copy the new CMove object from base object xpcSowPit = m_xpcPits[iPlayer][iPit + 2] ; Move(xpcSowPit, &cMove) ; // make move // sow pit # iPit, and change iPlayer CountStones(&cMove) ; // set m_iTotalStones if (cMove.m_iTotalStones <= m_iTableStones) // if we have the value of this in the // best win table { MapConfiguration(&cMove) ; // map stone configuration // to index in best win table GetBestWinCount(&cMove) ; iValue = cMove.m_iBestWinValue ; // get value of this move if (iValue > BESTWINUNDEF) // value was in table bDone = true ; } if (!bDone && (iDepth < m_iCurrentMaxDepth || (iDepth < m_iCurrentMaxDepth + m_iCurrentCapDepth && (cMove.m_bCapture || cMove.m_bFreeTurn)))) { Minimax(&cMove, iDepth + 1) ; iValue = cMove.m_iBestWinValue ; bDone = true ; } if (!bDone) { iValue = 0 ; for (iP = 0 ; iP < NUMPITS ; ++iP) iValue += (cMove.m_iNumStones[cMove.m_iPlayer][iP + 2] - cMove.m_iNumStones [OTHERPLAYER(cMove.m_iPlayer)][iP + 2]) / 2 ; } iValue += cMove.m_iNumStones[cMove.m_iPlayer][HOMEINDEX + 2] - cMove.m_iNumStones[OTHERPLAYER(cMove.m_iPlayer)] [HOMEINDEX + 2] ; if (cMove.m_iPlayer != iPlayer && iValue > BESTWINUNDEF) iValue = -iValue ; // reverse value if other player if (iMaxValue < iValue) // new max? iMaxValue = iValue, iBestMove = iPit ; } xpcMove->m_iValues[iPit] = iValue ; // save val for this move } if ((xpcMove->m_iNumMoves = iLegalMoves) == 0) // if there were no legal moves iMaxValue = - xpcMove->m_iTotalStones ; // if there is no legal move, then the value of this // position is minus the number of stones on the // other side // ... by inserting test for iLegalMoves > 1 here, can provide other // patterns -- eg, occasionally choosing less good move xpcMove->m_iBestWinValue = iMaxValue ; // store best value xpcMove->m_iBestMove = iBestMove ; // and best move // cleanup: JXELEAVE(CMnk::Minimax) ; RETURN(iError != 0) ; } //* CMnk::StaticEvaluation -- determine static value of a position bool CMnk::StaticEvaluation(CMove * xpcMove) // xpcMove -- position to be evaluated // returns: true if error, false otherwise { JXENTER(CMnk::StaticEvaluation) ; int iError = 0 ; // error code int iNumMoves = 0 ; // number of legal moves int iNumStones ; // number of stones in pit bool bFree[NUMPITS], bCapture[NUMPITS] ; int iPit ; // loop variable //int iMaxValue = BESTWINUNDEF ; // computing maximum value //CPit * xpcPit ; // pit being processed int iPlayer = xpcMove->m_iPlayer ; // current player xpcMove->m_bHasCapture = xpcMove->m_bHasFree = false ; for (iPit = 0 ; iPit < NUMPITS ; ++iPit) { xpcMove->m_iValues[iPit] = BESTWINUNDEF ; // no value yet // xpcPit = m_xpcPits[iPlayer][iPit + 2] ; // pt to pit object if ((iNumStones = xpcMove->m_iNumStones[iPlayer][iPit + 2])) // get number of stones and test ++iNumMoves ; // it's a legal move bFree[iPit] = (iNumStones && (iNumStones % (2 * NUMPITS + 1) == iPit + 1)) ; // test whether this pit yields a free turn if (iNumStones == 2 * NUMPITS + 1) bCapture[iPit] = true ; else if (iNumStones && iNumStones <= iPit) bCapture[iPit] = (xpcMove->m_iNumStones[iPlayer] [iPit - iNumStones + 2] == 0) ; else if (iNumStones > iPit + NUMPITS && iNumStones <= 2 * NUMPITS) bCapture[iPit] = (xpcMove->m_iNumStones[iPlayer] [iPit - iNumStones + NUMPITS + 2] == 0) ; else bCapture[iPit] = false ; if (bFree[iPit]) xpcMove->m_bHasFree = true ; if (bCapture[iPit]) xpcMove->m_bHasCapture = true ; // the following formula is really just a guess xpcMove->m_iValues[iPit] = iNumStones + (bCapture[iPit] ? 5 : 0) + (bFree[iPit] ? 5 : 0) ; } xpcMove->m_iNumMoves = iNumMoves ; // store # legal moves // cleanup: JXELEAVE(CMnk::StaticEvaluation) ; RETURN(iError != 0) ; } //* CMnk::AggressiveStaticEvaluation -- determine static value of a position bool CMnk::AggressiveStaticEvaluation(CMove * xpcMove) // xpcMove -- position to be evaluated // returns: true if error, false otherwise /* For Simplicity: defensive moves are discussed from pt of view of crab */ { JXENTER(CMnk::StaticEvaluation) ; int iError = 0 ; // error code int iNumMoves = 0 ; // number of legal moves int iNumStones ; // number of stones in pit int iFree, iCapture, maxCapture = 0; //int maxFree=0; int iPit, iCapturePit = 0, iFreePit = 0; //int iMaxValue = BESTWINUNDEF ; // computing maximum value int iPlayer = xpcMove->m_iPlayer ; // current player bool bAFreePitExists, bACaptureExists, bDoFreePitFirst; //CPit * xpcPit ; // pit being processed /*INITIALIZE*/ xpcMove->m_bHasCapture = xpcMove->m_bHasFree = false ; bDoFreePitFirst = false; bAFreePitExists = false; bACaptureExists = false; maxCapture = 0; for (iPit = 0, bAFreePitExists = false, bACaptureExists = false ; iPit < NUMPITS ; ++iPit) { xpcMove->m_iValues[iPit] = BESTWINUNDEF ; // no value yet //xpcPit = m_xpcPits[iPlayer][iPit + 2] ; // pt to pit object if ((iNumStones = xpcMove->m_iNumStones[iPlayer][iPit + 2])) // get number of stones and test ++iNumMoves ; // it's a legal move iFree = (iNumStones && (iNumStones % (2 * NUMPITS + 1) == iPit + 1)) ? 1 : 0; // test whether this pit yields a free turn if (iNumStones == 2 * NUMPITS + 1) iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][NUMPITS + 1 - iPit]; else if (iNumStones && (iNumStones <= iPit) && !xpcMove->m_iNumStones[iPlayer][iPit - iNumStones + 2]) iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][NUMPITS + 1 - iPit + iNumStones] ; else if (iNumStones > iPit + NUMPITS && (iNumStones <= 2 * NUMPITS) && !xpcMove->m_iNumStones[iPlayer][2 * NUMPITS + 3 + iPit - iNumStones]) iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][iNumStones - iPit - NUMPITS]; else iCapture = 0; if (iFree) { xpcMove->m_bHasFree = true ; if (!bAFreePitExists) iFreePit = iPit; //so that you start w/ the closest free Pit (ie. iPit with the lowest index), if (iNumStones <= NUMPITS) bAFreePitExists = true; //unless there's a "WRAP AROUND" in which case the farthest pit will be selected. } /* determine which pit yields the max capture */ if (iCapture) { xpcMove->m_bHasCapture = true ; if (iCapture > maxCapture) { maxCapture = iCapture; iCapturePit = iPit; } bACaptureExists = true; } }//end- for /* Determine whether the FreePit "interferes" with the CapturePit */ if (bACaptureExists && bAFreePitExists) { if ((xpcMove->m_iNumStones[iPlayer][iFreePit + 2] < NUMPITS - 1) && //i.e. if iFreePit <=3 (xpcMove->m_iNumStones[iPlayer][iCapturePit + 2] < NUMPITS + 1) && //i.e. No Wrap Around Capture ((iCapturePit - xpcMove->m_iNumStones[iPlayer][iCapturePit + 2]) > iFreePit)) { //i.e. the landing pit in the capture case, ... // is farther than the freePit. bDoFreePitFirst = true; } } if (((maxCapture < 4) && bAFreePitExists) || bDoFreePitFirst) xpcMove->m_iValues[iFreePit] = 5; else if (bACaptureExists) xpcMove->m_iValues[iCapturePit] = 5; else for (signed char i = 0; i < NUMPITS; xpcMove->m_iValues[i] = (xpcMove->m_iNumStones[iPlayer][i + 2]) ? 5 : 0, i++); xpcMove->m_iNumMoves = iNumMoves ; // store # legal moves // cleanup: JXELEAVE(CMnk::AggressiveStaticEvaluation) ; RETURN(iError != 0) ; } //* CMnk::StaticEvaluation -- determine static value of a position bool CMnk::DefensiveStaticEvaluation(CMove * xpcMove) // xpcMove -- position to be evaluated // returns: true if error, false otherwise /* For Simplicity: defensive moves are discussed from pt of view of crab */ { JXENTER(CMnk::DefensiveStaticEvaluation) ; int iError = 0 ; // error code int iNumMoves = 0 ; // number of legal moves int iNumStones, // number of stones in pit iOtherNumStones, //number of stones in opponent's pits. iMaxStones, StonesGained, StonesSaved, TotalBenefit, maxTotalBenefit; int OtherPlayer; int iFree, iCapture, maxCapture, // maxFree, maxThreat, minThreat; int iPit = 0, iCapturePit = 0, iFreePit = 0, iMaxThreatPit = 0, iCurrentPitUnderThreat = 0, iSaved = 0, iStartWithThisCapture = 0; //int iMaxValue = BESTWINUNDEF ; // computing maximum value int iPlayer = xpcMove->m_iPlayer ; // current player int ThreatCount, //how many pits of HUMAN are aggressive. iEmptyPitCount, iOtherEmptyPitCount; int iPitUnderThreat[NUMPITS]; //List of pits of CRAB under threat. int iEvasionNecessary[NUMPITS]; //Amount of threat to a pit on this side. int iOtherOffensivePit[NUMPITS]; //which pit a given pit on the opposite side is offensive to. int jYieldsACapture[NUMPITS]; //the pit on the opposite side that can be captured by a given pit. int ii, u, j, k, kk, tmpValues, tmpCount; bool bAFreePitExists, bACaptureExists, bAThreatExists, bDoFreePitFirst, bEndGamePlay, bNearClustered, bConclusive, bSaveable, bUnSaveable; // CPit * xpcPit ; // pit being processed /*INITIALIZE*/ xpcMove->m_bHasCapture = xpcMove->m_bHasFree = false ; bAThreatExists = false; bDoFreePitFirst = false; bAFreePitExists = false; bACaptureExists = false; bEndGamePlay = false; bConclusive = false; bSaveable = false; bUnSaveable = false; tmpCount = 0; maxCapture = 0; maxThreat = 0; minThreat = 0; ThreatCount = 0; iMaxStones = 0; // maxFree = 0; maxTotalBenefit = 0; bNearClustered = false; memset((void *)iEvasionNecessary, 0x0, NUMPITS * sizeof(int)); memset((void *)iPitUnderThreat, -1, NUMPITS * sizeof(int)); memset((void *)iOtherOffensivePit, -1, NUMPITS * sizeof(int)); /* note that in SOME OF THE following sections of code the prefix "Other" may refer to the computer_crab. Codewise though the terms are perfectly interchangeable. */ /*determine if this is an EndOfGame Play move */ for (ii = 0, iEmptyPitCount = 0, iOtherEmptyPitCount = 0; ii < NUMPITS; ii++) { if (!xpcMove->m_iNumStones[0][ii + 2]) iEmptyPitCount++; if (!xpcMove->m_iNumStones[1][ii + 2]) iOtherEmptyPitCount++; } if (((iEmptyPitCount > 2) && (iOtherEmptyPitCount > 2)) || (bNearClustered && ((iEmptyPitCount > 1) || (iOtherEmptyPitCount > 1)))) bEndGamePlay = true; /* SET UP DEFENSIVE MOVES, DETERMINE OFFENDING CRAB_PITS, and MY PITS UNDER ATTACK */ for (OtherPlayer = OTHERPLAYER(iPlayer), j = 0; j < NUMPITS; j++) { iOtherNumStones = xpcMove->m_iNumStones[OtherPlayer][j + 2]; /*Stones in the j-th HUMAN pit*/ /* if there are 13 stones in the HUMAN'S pit then the (5-j)th CRAB'S pit is threatened */ if (iOtherNumStones == 2 * NUMPITS + 1) iCurrentPitUnderThreat = NUMPITS - j - 1; else if (iOtherNumStones && (iOtherNumStones <= j) && !xpcMove->m_iNumStones[OtherPlayer][j - iOtherNumStones + 2]) { iCurrentPitUnderThreat = NUMPITS - 1 - j + iOtherNumStones; } else if ((iOtherNumStones > j + NUMPITS) && (iOtherNumStones <= 2 * NUMPITS) && (!xpcMove->m_iNumStones[OtherPlayer][2 * NUMPITS + 3 + j - iOtherNumStones])) iCurrentPitUnderThreat = iOtherNumStones - j - NUMPITS - 2; else iCurrentPitUnderThreat = -1; /* then check to see if there are stones in iCurrentPitUnderThreat at all */ if ((iCurrentPitUnderThreat != -1) && !xpcMove->m_iNumStones[iPlayer][iCurrentPitUnderThreat + 2]) iCurrentPitUnderThreat = -1; /* if there are stones in the pit under threat then set flags */ if (iCurrentPitUnderThreat != -1) { bAThreatExists = true; iPitUnderThreat[ThreatCount] = iCurrentPitUnderThreat; iEvasionNecessary[iCurrentPitUnderThreat] = xpcMove->m_iNumStones[iPlayer][iCurrentPitUnderThreat + 2]; //the extent of threat to a given CRAB pit iOtherOffensivePit[j] = iCurrentPitUnderThreat; // the j th HUMAN pit is offensive to iCurrentPitUnderThreat CRAB pit. ThreatCount++; //the total # of CRAB pits under threat. } }//end for -j /* Find the pit least threatened and the one most threatened */ for (ii = 0; ii < NUMPITS; ii++) { if (maxThreat < iEvasionNecessary[ii]) { maxThreat = iEvasionNecessary[ii]; iMaxThreatPit = ii; } minThreat = min(minThreat, iEvasionNecessary[ii]); } for (iPit = 0, bAFreePitExists = false, bACaptureExists = false ; iPit < NUMPITS ; ++iPit) { xpcMove->m_iValues[iPit] = BESTWINUNDEF ; // no value yet //xpcPit = m_xpcPits[iPlayer][iPit + 2] ; // pt to pit object if ((iNumStones = xpcMove->m_iNumStones[iPlayer][iPit + 2])) // get number of stones and test ++iNumMoves ; // it's a legal move /* determine the max # of stones across all pits on one side */ if (iNumStones > iMaxStones) iMaxStones = iNumStones; /* test whether this pit yields a free turn */ iFree = (iNumStones && (iNumStones % (2 * NUMPITS + 1) == iPit + 1)) ? 1 : 0; /* Test for a Capture and the amount of Capture. There are 3 ways you can execute a capture: if you have 13 stones in a pit, then you definitely capture all stones in the opposite pit +1 (which is your own stone). if you have <13 stones in a pit and you don't wrap around the other side, then you might yield a capture. if you have <13 stones in a pit then you never cross the point you started from and you might yield a capture. If You happen to cross your starting point as in a loop (if you have >13 stones) you will never land up w/ a capture. */ if (iNumStones == 2 * NUMPITS + 1) { iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][NUMPITS + 1 - iPit]; jYieldsACapture[iPit] = NUMPITS - 1 - iPit; } else if (iNumStones && (iNumStones <= iPit) && !xpcMove->m_iNumStones[iPlayer][iPit - iNumStones + 2]) { iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][NUMPITS + 1 - iPit + iNumStones] ; jYieldsACapture[iPit] = NUMPITS - 1 - iPit + iNumStones; } else if (iNumStones > iPit + NUMPITS && (iNumStones <= 2 * NUMPITS) && !xpcMove->m_iNumStones[iPlayer][2 * NUMPITS + 3 + iPit - iNumStones]) { iCapture = xpcMove->m_iNumStones[OTHERPLAYER(iPlayer)][iNumStones - iPit - NUMPITS]; jYieldsACapture[iPit] = iNumStones - iPit - NUMPITS - 2; } else { iCapture = 0; jYieldsACapture[iPit] = -1; } /* (OTHER THINGS BEING THE SAME), if there're more than one FreePit then start from the one closest to the home bin */ if (iFree) { xpcMove->m_bHasFree = true ; if (!bAFreePitExists) iFreePit = iPit; //so that you start w/ the closest free Pit (ie. iPit with the lowest index), if (iNumStones <= NUMPITS) bAFreePitExists = true; //unless there's a "WRAP AROUND" in which case the farthest pit will be selected. } /* Determine which pit yields the max capture, (OTHER THINGS BEING THE SAME), start with this pit */ if (iCapture) { xpcMove->m_bHasCapture = true ; if (iCapture > maxCapture) { maxCapture = iCapture; iCapturePit = iPit; } bACaptureExists = true; } }//end- for iPit /* Determine whether the FreePit "interferes" with the CapturePit. There's NO interference if doing the free pit first keeps the capture intact. */ if (bACaptureExists && bAFreePitExists) { if ((xpcMove->m_iNumStones[iPlayer][iFreePit + 2] < NUMPITS - 1) && //i.e. if iFreePit <=3 (xpcMove->m_iNumStones[iPlayer][iCapturePit + 2] < NUMPITS + 1) && //i.e. No Wrap Around Capture ((iCapturePit - xpcMove->m_iNumStones[iPlayer][iCapturePit + 2]) > iFreePit)) { //i.e. the landing pit in the capture case, ... // is farther than the freePit. bDoFreePitFirst = true; } } /* Use End Game Play Strategy if more than 2 stones are empty on each side. In this case ALWAYS start with the pit which has stones falling furthest away from home bin. if this pit is a free pit avoid it, unless by doing otherwise you have to squander more than 3 stones to the opposite side then go for the free pit. */ if (bEndGamePlay) { if (maxCapture > 3) { xpcMove->m_iValues[iCapturePit] = 5; } else if (bAThreatExists) { xpcMove->m_iValues[iMaxThreatPit] = 5; } else { for (u = 0; u < NUMPITS; u++) { if (!xpcMove->m_iNumStones[iPlayer][u + 2]) continue; //proceed in the current loop only if there are stones. tmpValues = u + 1 - xpcMove->m_iNumStones[iPlayer][u + 2]; //highest value to pit w/ stones falling furthest away from home bin. if (tmpValues <= 0) //u is a free pit or has stones falling on the opp. side. tmpValues = BESTWINUNDEF + 1; //the lowest possible; add 1 to distinguish with pits without stones. xpcMove->m_iValues[u] = tmpValues; }//end for u. }//end else goto cleanup; }//end if bEndGamePlay /* if there's no end of game play: (HIGHEST PRIORITY enumerated first) 1) if the DoFreePitFirst flag is high then do the Free Pit first 2) if there's a threat more than the max capture do the threat first. 3) if the smallest capture's more than the highest threat do the capture first. 4) if you can't execute 2) or 3) then do the threat if it's more than 3 first, else do the capture. 5) if your capture is less than 4 stones and you have a free pit too, do the free pit. 6) if you can't conclude from 1)-5) assign equal values to all. This is the overall condensed strategy. The actual implementation is simpler if you construct a binary conditions table (TCF for threat, capture, free) and then implement subconditions if two or more of these flags are high, according to the above rules. */ //T C F stands for....(threatExists, CaptureExists, FreePitExists) if (!bAThreatExists && !bACaptureExists && bAFreePitExists) { // 0 0 1 xpcMove->m_iValues[iFreePit] = 5; bConclusive = true; } if (!bAThreatExists && bACaptureExists && !bAFreePitExists) { // 0 1 0 xpcMove->m_iValues[iCapturePit] = 5; bConclusive = true; } if (!bAThreatExists && bACaptureExists && bAFreePitExists) { // 0 1 1 if (bDoFreePitFirst || (maxCapture < 3)) //If the capture is less than 3 or if there's a "non-interfering" xpcMove->m_iValues[iFreePit] = 5; //free pit then do free pit first. else xpcMove->m_iValues[iCapturePit] = 5; bConclusive = true; } if (bAThreatExists && !bAFreePitExists && !bACaptureExists) { //1 0 0 xpcMove->m_iValues[iMaxThreatPit] = 5; bConclusive = true; } if (bAThreatExists && !bACaptureExists && bAFreePitExists) { //1 0 1 xpcMove->m_iValues[iFreePit] = 5; bConclusive = true; // if no capture do free pit ---> no contest from Threat. } if (bAThreatExists && bACaptureExists) { if (!bAFreePitExists || (bAFreePitExists && !bDoFreePitFirst)) { //1 1 0 && 1 1 1 if (maxCapture >= maxThreat) { xpcMove->m_iValues[iCapturePit] = 5; } else { /* see if you can capture the offending pit anyway : if you have more than CapturePit Stones (ie. a wrap around capture). you take care of the threat because you fill up pits on the opp.side. If this condition is true and maxThreat>5 then go for the capture. If that condition is true and but maxThreat<=5 then go for the threat. if IT'S not a wrap around capture, and the offending pits can be captured (i.e. look at all capture pits and the pit numbers captured from opp. side), then see if you have more than one offending pit for the same threat, in which case go for the bigger offending pit capture... */ if (xpcMove->m_iNumStones[iPlayer][iCapturePit] > iCapturePit) { if (maxThreat > 4) xpcMove->m_iValues[iCapturePit] = 5; else xpcMove->m_iValues[iMaxThreatPit] = 5; } else { /* determine if an offensive pit is capturable. If atleast one pit offensive to the maxThreatPit is uncapturable then go for the threat pit instead of the capture. */ for (kk = 0, tmpCount = 0, bUnSaveable = false; kk < ThreatCount; kk++) { if (iPitUnderThreat[kk] == iMaxThreatPit) tmpCount++; if (tmpCount > 1) { bUnSaveable = true; break; } } /* go thru every pit and see if it can capture a pit. If it can't go to next pit. else see if the captured pit is offensive to one of the other pits. If not go to next pit. if yes, find out the number of stones gained (captured) and the number of stones saved (stones no longer threatened because of capture(s)) */ for (ii = 0, bSaveable = false; !bUnSaveable && ii < NUMPITS; ii++) { // ii for this side. if (jYieldsACapture[ii] == -1) continue; //skip the following lines if ii does not yield a capture. if ((iSaved = iOtherOffensivePit[jYieldsACapture[ii]]) == -1) { //iSaved lies on this side. continue; //the captured pit is not offensive. } else { bSaveable = true; StonesSaved = xpcMove->m_iNumStones[iPlayer][2 + iSaved]; // no. of stones saved via capture. StonesGained = xpcMove->m_iNumStones[OtherPlayer][2 + jYieldsACapture[ii]]; //no. of stones gained via capture. TotalBenefit = StonesGained + StonesSaved; if (maxTotalBenefit < TotalBenefit) { maxTotalBenefit = TotalBenefit; iStartWithThisCapture = ii; } } //end if iSaved }//end for ii. if (bSaveable && !bUnSaveable) xpcMove->m_iValues[iStartWithThisCapture] = 5; //optimum capture else xpcMove->m_iValues[iMaxThreatPit] = 5; //threat pit can't be saved. }//endif xpcMove->... }//endif maxCapture>=maxThreat } else { //if bdoFreePitFirst xpcMove->m_iValues[iFreePit] = 5; } bConclusive = true; } //end if (bAThreatExists&&baCaptureExists) if ((!bAThreatExists && !bACaptureExists && !bAFreePitExists) || !bConclusive) { for (k = 0; k < NUMPITS; k++) { xpcMove->m_iValues[k] = xpcMove->m_iNumStones[iPlayer][k + 2] ? 5 : 0; //assign equal values to all. } }//end 0 0 0 xpcMove->m_iNumMoves = iNumMoves ; // store # legal moves cleanup: JXELEAVE(CMnk::DefensiveStaticEvaluation) ; RETURN(iError != 0) ; } /* */ bool CMnk::TreeAlgo(CMove *xpcMove) { return true; } //* CMnk::CountStones -- count total stones in configuration bool CMnk::CountStones(CMove * xpcMove) // xpcMove -- CMove object containing the configuration // returns: true if error, false otherwise { JXENTER(CMnk::CountStones) ; int iError = 0 ; // error code //long lConfigIndex = 0 ; // return value, configuration index int iStones ; // total number of stones int iPlayer, iPit ; // loop variables for (iStones = 0, iPlayer = 0 ; iPlayer < NUMPLAYERS ; ++iPlayer) for (iPit = 0 ; iPit < NUMPITS ; ++iPit) iStones += xpcMove->m_iNumStones[iPlayer][iPit + 2] ; // count total number of stones xpcMove->m_iTotalStones = iStones ; // store it // cleanup: JXELEAVE(CMnk::CountStones) ; RETURN(iError != 0) ; } //* CMnk::GetBestWinCount -- get position value in best win table bool CMnk::GetBestWinCount(CMove * xpcMove) // xpcMove -- CMove object for position to be evaluated // returns: true if error, false otherwise { JXENTER(CMnk::GetBestWinCount) ; int iError = 0 ; // error code uint lIndex = xpcMove->m_lConfigIndex; int iValue = 0; // value from table struct FIVE * hpFive ; // ptr to structure of 8 5-bit values if (/*lIndex < 0 ||*/ lIndex >= MAXCONFIGS) { iError = 100 ; // index out of range goto cleanup ; } hpFive = (struct FIVE *)(m_lpCMnkData->m_hpcBestWin + (lIndex / 8) * 5) ; // point to group of eight numbers switch (lIndex % 8) { case 0: iValue = hpFive->v0 ; break ; case 1: // iValue = hpFive->v1 ; iValue = (hpFive->v1a << 2) + (hpFive->v1b) ; break ; case 2: iValue = hpFive->v2 ; break ; case 3: // iValue = hpFive->v3 ; iValue = (hpFive->v3a << 4) + (hpFive->v3b) ; break ; case 4: // iValue = hpFive->v4 ; iValue = (hpFive->v4a << 1) + (hpFive->v4b) ; break ; case 5: iValue = hpFive->v5 ; break ; case 6: // iValue = hpFive->v6 ; iValue = (hpFive->v6a << 3) + (hpFive->v6b) ; break ; case 7: iValue = hpFive->v7 ; break ; } if (iValue == TABLEUNDEF) // undefined or unspecified iValue = BESTWINUNDEF ; else if (iValue > 15) // negative number iValue -= 32 ; xpcMove->m_iBestWinValue = iValue ; // // // xpcMove->m_iBestWinValue = (iValue == TABLEUNDEF) ? // BESTWINUNDEF : iValue ; // store value cleanup: JXELEAVE(CMnk::GetBestWinCount) ; RETURN(iError != 0) ; } //* CMnk::SetBestWinCount -- set value in best win table bool CMnk::SetBestWinCount(CMove * xpcMove) // xpcMove -- pointer to CMove object where value is to be set // returns: true if error, false otherwise { JXENTER(CMnk::SetBestWinCount) ; int iError = 0 ; // error code struct FIVE * hpFive ; // ptr to structure of 8 5-bit values uint lIndex = xpcMove->m_lConfigIndex; int iValue = xpcMove->m_iBestWinValue ; // value from table bool bTest = false ; // debugging test if (/*lIndex < 0 ||*/ lIndex >= MAXCONFIGS) { iError = 100 ; // index out of range goto cleanup ; } hpFive = (struct FIVE *)(m_lpCMnkData->m_hpcBestWin + (lIndex / 8) * 5) ; // point to group of eight numbers switch (lIndex % 8) { case 0: hpFive->v0 = iValue ; break ; case 1: // hpFive->v1 = iValue ; hpFive->v1a = iValue >> 2 ; hpFive->v1b = iValue ; break ; case 2: hpFive->v2 = iValue ; break ; case 3: // hpFive->v3 = iValue ; hpFive->v3a = iValue >> 4 ; hpFive->v3b = iValue ; break ; case 4: // hpFive->v4 = iValue ; hpFive->v4a = iValue >> 1 ; hpFive->v4b = iValue ; break ; case 5: hpFive->v5 = iValue ; break ; case 6: // hpFive->v6 = iValue ; hpFive->v6a = iValue >> 3 ; hpFive->v6b = iValue ; break ; case 7: hpFive->v7 = iValue ; break ; } cleanup: if (m_bDumpPopulate && (lIndex % 10000 == 0 || (bTest = iValue > xpcMove->m_iTotalStones || -iValue > xpcMove->m_iTotalStones))) { DumpPosition(xpcMove) ; if (bTest) { DumpBestWinTable() ; DebugBreak() ; } DoPendingEvents() ; } //#define THRESHHOLD 20000L //#define BUFSIZE 10000 // // if (lIndex >= THRESHHOLD) // { // static byte FAR cBuffer[BUFSIZE] ; // // if (lIndex == THRESHHOLD) // { // _fmemcpy(cBuffer, (char *)m_lpCMnkData->m_hpcBestWin, BUFSIZE) ; // DumpBestWinTable() ; // } ///// else if (lIndex >= 104856 // error detected at 104858 // else if (_fmemcmp(cBuffer, (char *)m_lpCMnkData->m_hpcBestWin, // BUFSIZE)) // if first BUFSIZE bytes changed // { // DumpPosition(xpcMove) ; // DumpBestWinTable() ; // DebugBreak() ; // } // } JXELEAVE(CMnk::SetBestWinCount) ; RETURN(iError != 0) ; } //* CMnk::DumpPosition -- dump contents of CMove object bool CMnk::DumpPosition(CMove * xpcMove) // xpcMove -- CMove object containing the configuration // returns: true if error, false otherwise { JXENTER(CMnk::DumpPosition) ; int iError = 0 ; // error code if (xpcMove->m_bRealMove) debugN("(REAL) "); debug("P%d Cfg=%ld w/%d stones, %d moves, sow=%d, " "best %d for %d.", xpcMove->m_iPlayer, xpcMove->m_lConfigIndex, xpcMove->m_iTotalStones, xpcMove->m_iNumMoves, (xpcMove->m_xpcPit ? xpcMove->m_xpcPit->m_iPit : -1), xpcMove->m_iBestMove, xpcMove->m_iBestWinValue); debug(" [%3d] %2d %2d %2d %2d %2d %2d H=%d", xpcMove->m_iNumStones[1][HOMEINDEX + 2], xpcMove->m_iNumStones[1][2], xpcMove->m_iNumStones[1][3], xpcMove->m_iNumStones[1][4], xpcMove->m_iNumStones[1][5], xpcMove->m_iNumStones[1][6], xpcMove->m_iNumStones[1][7], xpcMove->m_iNumStones[1][HANDINDEX + 2]); debug(" %2d %2d %2d %2d %2d %2d [%3d] H=%d\n", xpcMove->m_iNumStones[0][7], xpcMove->m_iNumStones[0][6], xpcMove->m_iNumStones[0][5], xpcMove->m_iNumStones[0][4], xpcMove->m_iNumStones[0][3], xpcMove->m_iNumStones[0][2], xpcMove->m_iNumStones[0][HOMEINDEX + 2], xpcMove->m_iNumStones[0][HANDINDEX + 2]); DoPendingEvents() ; // cleanup: JXELEAVE(CMnk::DumpPosition) ; RETURN(iError != 0) ; } //* CMnk::DumpBestWinTable -- dump fields of best win table bool CMnk::DumpBestWinTable(long lLow, long lHigh) { // lLow -- low end of configuration range // lHigh -- high end of configuration range // returns: true if error, false otherwise JXENTER(CMnk::DumpBestWinTable) ; int iError = 0 ; // error code CMove cMove ; // dummy move structure long lEol ; // config index at end of line for (lEol = lLow + 23 ; lEol <= lHigh + 23 ; lEol += 24) { if (lEol > lHigh) lEol = lHigh ; debugN("Table[%ld-%ld]:", lLow, lEol) ; while (lLow <= lEol) { // loop thru values cMove.m_lConfigIndex = lLow++ ; GetBestWinCount(&cMove) ; if (cMove.m_iBestWinValue == BESTWINUNDEF) debugN(" U") ; else debugN(" %d", cMove.m_iBestWinValue) ; } debugN("\n") ; } debugN("\n") ; // cleanup: JXELEAVE(CMnk::DumpBestWinTable) ; RETURN(iError != 0) ; } } // namespace Mankala } // namespace HodjNPodj } // namespace Bagel