File indexing completed on 2024-05-19 04:04:53
0001 /* 0002 This file is part of the game 'KJumpingCube' 0003 0004 SPDX-FileCopyrightText: 2012 Ian Wadham <iandw.au@gmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "ai_main.h" 0010 #include "ai_kepler.h" 0011 #include "ai_newton.h" 0012 0013 0014 #include "kjumpingcube_debug.h" 0015 0016 #include "prefs.h" 0017 0018 // Use a thread and return the move via a signal. 0019 class ThreadedAI : public QThread 0020 { 0021 Q_OBJECT 0022 public: 0023 ThreadedAI (AI_Main * ai) 0024 : m_ai (ai) 0025 { } 0026 0027 void run() override { 0028 int index = m_ai->computeMove(); 0029 Q_EMIT done (index); 0030 } 0031 /* IDW test. TODO - This is not actually used. Is it needed? 0032 * I think AI_Main::stop() sets m_stopped atomically and even 0033 * if it does not, the thread will see it next time around. And 0034 * the thread is read-only with respect to m_stopped ... 0035 * 0036 * ATM, KCubeBoxWidget calls AI_Main::stop() not this stop() 0037 * and it works ... 0038 * 0039 * IDW test. TODO - See AI_Main::stop(). It works, but does it need a QMutex? 0040 * 0041 void stop() { 0042 qCDebug(KJUMPINGCUBE_LOG) << "STOP THREAD REQUESTED"; 0043 { QMutexLocker lock (&m_ai->endMutex); m_ai->stop(); } 0044 wait(); 0045 qCDebug(KJUMPINGCUBE_LOG) << "STOP THREAD DONE"; 0046 } 0047 */ 0048 Q_SIGNALS: 0049 void done (int index); 0050 0051 private: 0052 AI_Main * m_ai; 0053 }; 0054 0055 const char * text[] = {"TakeOrBeTaken", "EqualOpponent", "CanTake", 0056 "OccupyCorner", "OccupyEdge", "OccupyCenter", 0057 "CanConsolidate", "CanReachMaximum", "CanExpand", 0058 "IncreaseEdge", "IncreaseCenter", "PlayHereAnyway"}; 0059 0060 void test (int array[4]) { 0061 for (int n = 0; n < 4; n++) { 0062 printf ("nb %d %d: ", n, array[n]); 0063 } 0064 printf ("\n"); 0065 } 0066 0067 void makeRandomSequence (int nMax, int * seq, QRandomGenerator & random) 0068 { 0069 // Helper routine. Sets up a random sequence of integers 0 to (nMax - 1). 0070 for (int n = 0; n < nMax; n++) { 0071 seq[n] = n; 0072 } 0073 0074 int last = nMax; 0075 int z, temp; 0076 for (int n = 0; n < nMax; n++) { 0077 z = random.bounded(last); 0078 last--; 0079 temp = seq[z]; 0080 seq[z] = seq[last]; 0081 seq[last] = temp; 0082 } 0083 return; 0084 } 0085 0086 AI_Main::AI_Main (QObject * parent, int side) 0087 : AI_Box (parent, side) 0088 , m_random(QRandomGenerator::global()->generate()) 0089 { 0090 qCDebug(KJUMPINGCUBE_LOG) << "AI_Main CONSTRUCTOR"; 0091 m_thread = new ThreadedAI (this); 0092 m_randomSeq = new int [m_nCubes]; 0093 #if AILog > 0 0094 startStats(); 0095 #endif 0096 0097 m_AI_Kepler = new AI_Kepler(); 0098 m_AI_Newton = new AI_Newton(); 0099 0100 setSkill (Prefs::EnumSkill1::Beginner, true, false, 0101 Prefs::EnumSkill2::Beginner, true, false); 0102 0103 m_stopped = false; 0104 m_currentLevel = 0; 0105 0106 connect(m_thread, &ThreadedAI::done, this, &AI_Main::done); 0107 } 0108 0109 AI_Main::~AI_Main() 0110 { 0111 delete m_AI_Kepler; 0112 delete m_AI_Newton; 0113 delete m_thread; 0114 } 0115 0116 void AI_Main::setSkill (int skill1, bool kepler1, bool newton1, 0117 int skill2, bool kepler2, bool newton2) 0118 { 0119 // TODO: turn exclusive ai type flags into one selection enum 0120 Q_UNUSED(newton1); 0121 Q_UNUSED(newton2); 0122 0123 m_ai[0] = nullptr; 0124 m_ai[1] = kepler1 ? m_AI_Kepler : m_AI_Newton; 0125 m_ai[2] = kepler2 ? m_AI_Kepler : m_AI_Newton; 0126 0127 m_ai_skill[0] = 0; 0128 m_ai_skill[1] = skill1; 0129 m_ai_skill[2] = skill2; 0130 0131 m_ai_maxLevel[0] = 0; 0132 m_ai_maxLevel[1] = depths[skill1]; 0133 m_ai_maxLevel[2] = depths[skill2]; 0134 0135 for (int player = 1; player <= 2; player++) { 0136 qCDebug(KJUMPINGCUBE_LOG) << "AI_Main::setSkill: Player" << player << m_ai[player]->whoami() 0137 << "skill" << m_ai_skill[player] 0138 << "maxLevel" << m_ai_maxLevel[player]; 0139 } 0140 } 0141 0142 void AI_Main::stop() 0143 { 0144 m_stopped = true; 0145 m_thread->wait(); 0146 } 0147 0148 void AI_Main::getMove (const Player player, const AI_Box * box) 0149 { 0150 #if AILog > 1 0151 qCDebug(KJUMPINGCUBE_LOG) << "\nEntering AI_Main::getMove() for player" << player; 0152 #endif 0153 // These settings are immutable once the thread starts and getMove() returns. 0154 // If AI_Main::setSkill() is called when the thread is active, the new values 0155 // will not take effect until the next move or hint. 0156 m_currentAI = m_ai[player]; 0157 m_skill = m_ai_skill[player]; 0158 m_maxLevel = m_ai_maxLevel[player]; 0159 0160 #if AILog > 0 0161 initStats (player); // IDW test. Statistics collection. 0162 #endif 0163 #if AILog > 1 0164 qCDebug(KJUMPINGCUBE_LOG) << tag(0) << "PLAYER" << player << m_currentAI->whoami() << "skill" << m_skill << "max level" << m_maxLevel; 0165 #endif 0166 0167 m_stopped = false; 0168 m_player = player; 0169 0170 checkWorkspace (box->side()); 0171 initPosition (box, player, true); 0172 0173 #if AILog > 1 0174 qCDebug(KJUMPINGCUBE_LOG) << "INITIAL POSITION"; 0175 printBox(); 0176 #endif 0177 0178 m_thread->start (QThread::IdlePriority); // Run computeMove() on thread. 0179 return; 0180 } 0181 0182 int AI_Main::computeMove() 0183 { 0184 // IDW test. // Set up a random sequence of integers 0 to (m_nCubes - 1). 0185 // IDW test. makeRandomSequence (m_side * m_side, m_randomSeq, m_random); 0186 0187 // Start the recursive MiniMax algorithm on a copy of the current cube box. 0188 #if AILog > 2 0189 qCDebug(KJUMPINGCUBE_LOG) << tag(0) << "Calling tryMoves(), level zero, player" << m_player; 0190 #endif 0191 0192 Move move = tryMoves (m_player, 0); 0193 0194 #if AILog > 2 0195 qCDebug(KJUMPINGCUBE_LOG) << tag(0) << "Returned from tryMoves(), level zero, player" 0196 << m_player << "value" << move.val 0197 << "simulate" << n_simulate << "assess" << n_assess; 0198 #endif 0199 0200 #if AILog > 0 0201 saveStats (move); // IDW test. Statistics collection. 0202 #endif 0203 0204 #if AILog > 1 0205 qCDebug(KJUMPINGCUBE_LOG) << "=============================================================="; 0206 qCDebug(KJUMPINGCUBE_LOG) << tag(0) << "MOVE" << m_currentMoveNo << "for PLAYER" << m_player 0207 << "X" << move.index/m_side << "Y" << move.index%m_side; 0208 #endif 0209 0210 return (move.index); // Return the best move found, via a signal. 0211 } 0212 0213 Move AI_Main::tryMoves (Player player, int level) 0214 { 0215 m_currentLevel = level; // IDW test. To limit qCDebug(KJUMPINGCUBE_LOG) in findCubesToMove(). 0216 long maxValue = -WinnerPlus1; 0217 0218 Move bestMove = {-1, -WinnerPlus1}; 0219 0220 // Find likely cubes to move. 0221 Move * cubesToMove = new Move [m_nCubes]; 0222 #if AILog > 3 0223 qCDebug(KJUMPINGCUBE_LOG) << "FIND CUBES TO MOVE for Player" << player 0224 << "from POSITION AT LEVEL" << level; 0225 if (level > 0) boxPrint (m_side, (int *) m_owners, m_values); // IDW test. 0226 #endif 0227 int moves = findCubesToMove (cubesToMove, player, 0228 m_owners, m_values, m_maxValues); 0229 0230 #if AILog > 2 0231 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "Level" << level << "Player" << player 0232 << "number of likely moves" << moves; 0233 if (level == 0) { 0234 for (int n = 0; n < moves; n++) { 0235 int v = cubesToMove[n].val; 0236 QString s = ""; 0237 if ((v > 0) && (v <= 12)) s = QString(text[v-1]); 0238 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << " " << "X" << cubesToMove[n].index/m_side 0239 << "Y" << cubesToMove[n].index%m_side 0240 << "val" << cubesToMove[n].val << s; 0241 } 0242 saveLikelyMoves (moves, cubesToMove); // IDW test. 0243 } 0244 0245 m_currentMove->searchStats->at(level)->n_moves += moves; 0246 #endif 0247 0248 // IDW TODO - Sort the moves by priority in findCubesToMove() (1 first), 0249 // shuffle moves that have the same value (to avoid repetitious openings). 0250 // IDW TODO - Apply alpha-beta pruning to the sorted moves. Maybe we can 0251 // allow low-priority moves and sacrifices ... 0252 0253 for (int n = 0; n < moves; n++) { 0254 #if AILog > 2 0255 if (level == 0) qCDebug(KJUMPINGCUBE_LOG) 0256 << "=============================================================="; 0257 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "TRY" << (n+1) << "at level" << level 0258 << "Player" << player 0259 << "X"<< cubesToMove[n].index/m_side 0260 << "Y" << cubesToMove[n].index%m_side 0261 << "val" << cubesToMove[n].val; 0262 #endif 0263 0264 MoveUndodata undodata; 0265 bool won = doMove (player, cubesToMove[n].index, &undodata); 0266 0267 #if AILog > 2 0268 n_simulate++; 0269 #endif 0270 0271 long val; 0272 if (won) { 0273 // Accept a winning move. 0274 bestMove = cubesToMove[n]; 0275 bestMove.val = WinnerPlus1 - 1; 0276 #if AILog > 2 0277 n_assess++; 0278 #endif 0279 cubesToMove[n].val = bestMove.val; // IDW test. For debug output. 0280 #if AILog > 2 0281 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "Player" << player 0282 << "wins at level" << level 0283 << "move" << cubesToMove[n].index/m_side 0284 << cubesToMove[n].index%m_side; 0285 #endif 0286 undoMove(&undodata); 0287 break; 0288 } 0289 else if (level >= m_maxLevel) { 0290 // Stop the recursion. 0291 val = m_currentAI->assessPosition (player, m_nCubes, 0292 m_owners, m_values); 0293 #if AILog > 2 0294 n_assess++; 0295 #endif 0296 cubesToMove[n].val = val; // IDW test. For debug output. 0297 #if AILog > 3 0298 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "END RECURSION: Player" << player 0299 << "X" << cubesToMove[n].index/m_side 0300 << "Y" << cubesToMove[n].index%m_side 0301 << "assessment" << val << "on POSITION"; 0302 boxPrint (m_side, (int *)(m_owners), m_values);// IDW test. 0303 #endif 0304 } 0305 else { 0306 // Switch players. 0307 Player opponent = (player == One) ? Two : One; 0308 0309 // Do the MiniMax calculation for the next recursion level. 0310 /* qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "CALL tryMoves: Player" << opponent 0311 << "level" << level+1; */ 0312 Move move = tryMoves (opponent, level + 1); 0313 val = move.val; 0314 cubesToMove[n].val = val; // IDW test. For debug output. 0315 /* qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "RETURN to level" << level 0316 << "Player" << player << "X" << move.index/m_side 0317 << "Y" << move.index%m_side << "assessment" << val; */ 0318 } 0319 0320 if (val > maxValue) { 0321 maxValue = val; 0322 bestMove = cubesToMove[n]; 0323 bestMove.val = val; 0324 cubesToMove[n].val = val; // IDW test. For debug output. 0325 #if AILog > 2 0326 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "NEW MAXIMUM at level" << level 0327 << "Player" << player << "X" << bestMove.index/m_side 0328 << "Y" << bestMove.index%m_side << "assessment" << val; 0329 #endif 0330 } 0331 Player p = player; 0332 undoMove(&undodata); 0333 if (p != player) qCDebug(KJUMPINGCUBE_LOG) << "ERROR: PLAYER CHANGED: from" << p << 0334 "to" << player; 0335 if (m_stopped) { 0336 qCDebug(KJUMPINGCUBE_LOG) << "STOPPED AT LEVEL" << level; 0337 break; 0338 } 0339 } 0340 0341 #if AILog > 2 0342 if (level == 0) { 0343 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "VALUES OF MOVES - Player" << player 0344 << "number of moves" << moves; 0345 for (int n = 0; n < moves; n++) { 0346 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << " " << "X" << cubesToMove[n].index/m_side 0347 << "Y" << cubesToMove[n].index%m_side 0348 << "val" << cubesToMove[n].val; 0349 } 0350 } 0351 #endif 0352 0353 delete [] cubesToMove; 0354 0355 #if AILog > 2 0356 qCDebug(KJUMPINGCUBE_LOG); 0357 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "BEST MOVE at level" << level << "Player" << player 0358 << "X" << bestMove.index/m_side << "Y" << bestMove.index%m_side 0359 << "assessment" << bestMove.val; 0360 #endif 0361 0362 // Apply the MiniMax rule. 0363 if (level > 0) { 0364 #if AILog > 2 0365 qCDebug(KJUMPINGCUBE_LOG) << tag(level) << "CHANGE SIGN" << bestMove.val 0366 << "to" << -bestMove.val; 0367 #endif 0368 bestMove.val = - bestMove.val; 0369 } 0370 0371 return bestMove; 0372 } 0373 0374 int AI_Main::findCubesToMove (Move * c2m, const Player player, 0375 const Player * owners, const int * values, 0376 const int * maxValues) 0377 { 0378 int index, n; 0379 int opponent = (player == One) ? Two : One; 0380 int moves = 0; 0381 int min = VeryHighValue; 0382 bool beginner = (m_skill == Prefs::EnumSkill1::Beginner); 0383 int secondMin = min; 0384 0385 // Set up a random sequence of integers 0 to (m_nCubes - 1). 0386 makeRandomSequence (m_nCubes, m_randomSeq, m_random); 0387 0388 // Put values on the cubes. 0389 int * neighbors = m_neighbors; 0390 int val; 0391 for (n = 0; n < m_nCubes; n++) { 0392 index = m_randomSeq [n]; 0393 0394 // Use only cubes that do not belong to the opponent. 0395 if (owners[index] == opponent) { 0396 continue; 0397 } 0398 0399 // The beginner selects the cubes with the most pips on them: 0400 // other players check the neighbours of each cube. 0401 val = beginner ? (5 - values [index]) : 0402 m_currentAI->assessCube (index, player, 0403 (neighbors + 4 * index), 0404 owners, values, maxValues); 0405 if (val < min) { 0406 secondMin = min; 0407 min = val; 0408 } 0409 else if ((val > min) && (val < secondMin)) { 0410 secondMin = val; 0411 } 0412 0413 // Store the move. 0414 c2m[moves].index = index; 0415 c2m[moves].val = val; 0416 moves++; 0417 } 0418 #if AILog > 2 0419 if (m_currentLevel == 0) qCDebug(KJUMPINGCUBE_LOG) << "\nMinimum is" << min 0420 << ", second minimum is" << secondMin << "available moves" << moves; 0421 #endif 0422 0423 if (moves == 0) { 0424 // Should not happen? Even bad moves are given a value > 0. 0425 qCDebug(KJUMPINGCUBE_LOG) << "NO LIKELY MOVES AVAILABLE: selecting" << moves 0426 << "min" << min; 0427 return moves; 0428 } 0429 0430 int counter = 0; 0431 // Find all moves with minimum assessment 0432 for (n = 0; n < moves; ++n) { 0433 if (c2m[n].val == min) { 0434 c2m[counter].index = c2m[n].index; 0435 c2m[counter].val = c2m[n].val; 0436 counter++; 0437 } 0438 } 0439 0440 // IDW TODO - Finalise the logic for limiting the number of moves tried. 0441 // The 1/3 gizmo is to limit searches on an empty cube box. 0442 // Should not use secondMin on Expert level? 0443 // Should not use secondMin on deeper recursion levels? 0444 // Should it all depend on how many moves are at "min"? 0445 // Should AI_Newton have overlapping values of move types? 0446 0447 // if (true || m_skill == Prefs::EnumSkill1::Average) // IDW test. 0448 // if ((counter <= 2) || (m_skill == Prefs::EnumSkill1::Average)) 0449 // if ((m_skill == Prefs::EnumSkill1::Average) && 0450 if (m_currentMoveNo > (m_nCubes / 3)) { // If board > 1/3 full. 0451 for (n = 0; n < moves; ++n) { 0452 if (c2m[n].val == secondMin) { 0453 c2m[counter].index = c2m[n].index; 0454 c2m[counter].val = c2m[n].val; 0455 counter++; 0456 } 0457 } 0458 } 0459 0460 if (counter != 0) { 0461 moves = counter; 0462 } 0463 0464 // IDW TODO - Can we find a more subtle rule? 0465 0466 // If more than maxMoves moves are favorable, take maxMoves random 0467 // moves because it will take too long to check more. 0468 return qMin (moves, maxBreadth); 0469 } 0470 0471 void AI_Main::checkWorkspace (int side) 0472 { 0473 if (m_side != side) { 0474 qCDebug(KJUMPINGCUBE_LOG) << "NEW AI_Box SIZE NEEDED: was" << m_side << "now" << side; 0475 delete[] m_randomSeq; 0476 resizeBox (side); 0477 m_randomSeq = new int [side * side]; 0478 } 0479 } 0480 0481 #if AILog > 0 0482 /* 0483 * Debugging methods for the AI. 0484 */ 0485 void AI_Main::boxPrint (int side, int * owners, int * values) 0486 { 0487 // Print out a position reached during or after recursion. 0488 // fprintf (stderr, "AI_Main::boxPrint (%d, %lu, %lu)\n", 0489 // side, (long) owners, (long) values); // Tests push and pop logic. 0490 for (int y = 0; y < side; y++) { 0491 fprintf (stderr, " "); 0492 for (int x = 0; x < side; x++) { 0493 int index = x * side + y; 0494 if (owners[index] == Nobody) fprintf (stderr, " ."); 0495 else fprintf (stderr, " %2d", (owners[index] == One) ? 0496 values[index] : -values[index]); 0497 } 0498 fprintf (stderr, "\n"); 0499 } 0500 } 0501 0502 void AI_Main::startStats() 0503 { 0504 // IDW test. For debugging. 0505 m_currentMoveNo = 0; 0506 m_moveStats.clear(); 0507 } 0508 0509 void AI_Main::postMove (Player player, int index, int side) 0510 { 0511 // IDW test. Statistics collection. 0512 // Used to record a move by a human player. 0513 checkWorkspace (side); 0514 0515 int x = index / m_side; 0516 int y = index % m_side; 0517 #if AILog > 1 0518 qCDebug(KJUMPINGCUBE_LOG) << "AI_Main::postMove(): index" << index << "at" << x << y << "m_side" << m_side; 0519 #endif 0520 m_maxLevel = m_ai_maxLevel[player]; 0521 m_currentMove = new MoveStats [1]; 0522 0523 m_currentMoveNo++; 0524 m_currentMove->player = player; 0525 m_currentMove->moveNo = m_currentMoveNo; 0526 m_currentMove->n_simulate = 0; 0527 m_currentMove->n_assess = 0; 0528 m_currentMove->nLikelyMoves = 0; 0529 m_currentMove->likelyMoves = 0; 0530 m_currentMove->searchStats = new QList<SearchStats *>(); 0531 0532 m_currentMove->x = x; 0533 m_currentMove->y = y; 0534 m_currentMove->value = 0; 0535 m_moveStats.append (m_currentMove); 0536 #if AILog > 1 0537 qCDebug(KJUMPINGCUBE_LOG) << "=============================================================="; 0538 qCDebug(KJUMPINGCUBE_LOG) << tag(0) << "MOVE" << m_currentMoveNo << "for PLAYER" << player 0539 << "X" << x << "Y" << y; 0540 qCDebug(KJUMPINGCUBE_LOG) << "=============================================================="; 0541 #endif 0542 } 0543 0544 void AI_Main::initStats (int player) 0545 { 0546 // IDW test. For debugging. 0547 m_currentMove = new MoveStats [1]; 0548 0549 m_currentMoveNo++; 0550 m_currentMove->player = (Player) player; 0551 m_currentMove->moveNo = m_currentMoveNo; 0552 m_currentMove->n_simulate = 0; 0553 m_currentMove->n_assess = 0; 0554 m_currentMove->nLikelyMoves = 0; 0555 m_currentMove->likelyMoves = 0; 0556 m_currentMove->searchStats = new QList<SearchStats *>(); 0557 0558 for (int n = 0; n <= m_maxLevel; n++) { 0559 SearchStats * s = new SearchStats [1]; 0560 s->n_moves = 0; 0561 m_currentMove->searchStats->append (s); 0562 } 0563 0564 n_simulate = 0; 0565 n_assess = 0; 0566 } 0567 0568 void AI_Main::saveLikelyMoves (int nMoves, Move * moves) 0569 { 0570 Move * m = new Move [nMoves]; 0571 m_currentMove->nLikelyMoves = nMoves; 0572 m_currentMove->likelyMoves = m; 0573 for (int n = 0; n < nMoves; n++) { 0574 m [n] = moves [n]; 0575 } 0576 } 0577 0578 void AI_Main::saveStats (Move & move) 0579 { 0580 // IDW test. For debugging. 0581 m_currentMove->x = move.index/m_side; 0582 m_currentMove->y = move.index%m_side; 0583 m_currentMove->value = move.val; 0584 m_currentMove->n_simulate = n_simulate; 0585 m_currentMove->n_assess = n_assess; 0586 m_moveStats.append (m_currentMove); 0587 } 0588 0589 void AI_Main::dumpStats() 0590 { 0591 // IDW test. For debugging. Replay all the moves, with statistics for each. 0592 qCDebug(KJUMPINGCUBE_LOG) << m_moveStats.count() << "MOVES IN THIS GAME"; 0593 AI_Box * statsBox = new AI_Box (0, m_side); 0594 statsBox->printBox(); 0595 for (MoveStats * m : std::as_const(m_moveStats)) { 0596 QList<int> l; 0597 int nMax = m->searchStats->count(); 0598 for (int n = 0; n < nMax; n++) { 0599 l.append (m->searchStats->at(n)->n_moves); 0600 } 0601 qCDebug(KJUMPINGCUBE_LOG) << ((m->player == 1) ? "p1" : "p2") << "move" << m->moveNo 0602 << "X" << m->x << "Y" << m->y 0603 << "value" << m->value << m->n_simulate << m->n_assess << l; 0604 0605 if (m->nLikelyMoves > 0) { 0606 qCDebug(KJUMPINGCUBE_LOG) << " Number of likely moves" << m->nLikelyMoves; 0607 for (int n = 0; n < m->nLikelyMoves; n++) { 0608 int v = m->likelyMoves[n].val; 0609 QString s = ""; 0610 if ((v > 0) && (v <= 12)) s = QString(text[v-1]); 0611 qCDebug(KJUMPINGCUBE_LOG) << " " << "X" << m->likelyMoves[n].index/m_side 0612 << "Y" << m->likelyMoves[n].index%m_side 0613 << "val" << m->likelyMoves[n].val << s; 0614 } 0615 delete m->likelyMoves; 0616 } 0617 bool won = statsBox->doMove (m->player, m->x * m_side + m->y); 0618 statsBox->printBox(); 0619 qDeleteAll (*(m->searchStats)); 0620 delete[] m; 0621 } 0622 m_moveStats.clear(); 0623 delete statsBox; 0624 } 0625 0626 QString AI_Main::tag (int level) 0627 { 0628 QString indent (""); 0629 indent.fill ('-', 2 * level); 0630 indent = indent.prepend (QString::number (level)); 0631 indent = indent.leftJustified (2 * m_maxLevel + 1); 0632 QString mv = QString::number(m_currentMoveNo).rightJustified(3, '0'); 0633 return (QString ("%1 %2 %3")).arg(m_currentMove->player).arg(mv).arg(indent); 0634 } 0635 #endif 0636 0637 /** 0638 * This is the default definition of virtual long AI_Base::assessPosition(). 0639 * Inheritors of AI_Base can declare and define other versions. 0640 */ 0641 long AI_Base::assessPosition (const Player player, const int nCubes, 0642 const Player owners[], const int values[]) const 0643 { 0644 int cubesOne = 0; 0645 int cubesTwo = 0; 0646 int pointsOne = 0; 0647 int pointsTwo = 0; 0648 int index, points; 0649 0650 for (index = 0; index < nCubes; index++) { 0651 points = values[index]; 0652 if (owners[index] == One) { 0653 cubesOne++; 0654 pointsOne += points * points; 0655 } 0656 else if (owners[index] == Two) { 0657 cubesTwo++; 0658 pointsTwo += points * points; 0659 } 0660 } 0661 0662 if (player == One) { 0663 return cubesOne * cubesOne + pointsOne - cubesTwo * cubesTwo - pointsTwo; 0664 } 0665 else { 0666 return cubesTwo * cubesTwo + pointsTwo - cubesOne * cubesOne - pointsOne; 0667 } 0668 } 0669 0670 #include "ai_main.moc" 0671 #include "moc_ai_main.cpp"