File indexing completed on 2024-05-19 07:52:01

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"