File indexing completed on 2024-05-19 04:04:52

0001 /*
0002     SPDX-FileCopyrightText: 2012 Ian Wadham <iandw.au@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "ai_box.h"
0008 // 
0009 #include "kjumpingcube_debug.h"
0010 #include <cstdio>
0011 
0012 AI_Box::AI_Box (QObject * parent, int side)
0013     :
0014     QObject      (parent)
0015 {
0016     m_parent = parent; // IDW test.
0017     createBox (side);
0018 }
0019 
0020 AI_Box::~AI_Box()
0021 {
0022     destroyBox();
0023 }
0024 
0025 void AI_Box::createBox (int side)
0026 {
0027     m_side         = ((side >= minSide) && (side <= maxSide)) ? side : 5;
0028     m_nCubes       = side * side;
0029     Position * pos = emptyPosition (m_nCubes);
0030     pos->player    = One;
0031     pos->isAI      = false;
0032     pos->index     = 0;
0033     m_maxValues    = new int [m_nCubes];
0034     m_neighbors    = new int [4 * m_nCubes];
0035     m_stack        = new int [m_nCubes];
0036     m_stackPtr     = -1;
0037     m_owners       = pos->owners;
0038     m_values       = pos->values;
0039     indexNeighbors();
0040     clear();
0041     m_undoList.append (pos);
0042 }
0043 
0044 void AI_Box::destroyBox()
0045 {
0046     delete[] m_maxValues;
0047     delete[] m_neighbors;
0048     delete[] m_stack;
0049     while (! m_undoList.isEmpty()) {
0050     discard (m_undoList.takeLast());
0051     }
0052 }
0053 
0054 void AI_Box::resizeBox (int side)
0055 {
0056     destroyBox();
0057     createBox (side);
0058 }
0059 
0060 
0061 // ----------------------------------------------------------------
0062 //                         Moves
0063 
0064 
0065 bool AI_Box::doMove (Player player, int index, MoveUndodata * undodata, QList<int> * steps)
0066 {
0067     // Check for move legality.
0068     Player oldOwner = m_owners[index];
0069     if ((oldOwner != player) && (oldOwner != Nobody)) {
0070     qCDebug(KJUMPINGCUBE_LOG) << "ILLEGAL MOVE: player" << player << "old" << oldOwner
0071                  << "at" << index/m_side << index%m_side;
0072 
0073         return false;           // The move is not valid.
0074     }
0075 
0076     if (undodata) {
0077         undodata->oldCubesToWin[Nobody] = m_cubesToWin[Nobody];
0078         undodata->oldCubesToWin[One] = m_cubesToWin[One];
0079         undodata->oldCubesToWin[Two] = m_cubesToWin[Two];
0080     }
0081 
0082     // Bitfield to mark saved cube indices.
0083     quint64 savedCubes[(maxSide * maxSide - 1) / 64 + 1];
0084     for (int i = 0; i < (maxSide * maxSide - 1) / 64 + 1; ++i)
0085         savedCubes[i] = 0ULL;
0086 
0087     // Save old values of changed cubes (owner + value) into the
0088     // MoveUndodata to be restored by undoMove().
0089     int saveCubes = 0;
0090     if (undodata) {
0091         undodata->changedCubes[saveCubes++] = ((index << 8) 
0092                                                | (m_owners[index] << 4)
0093                                                | (m_values[index] << 0));
0094         savedCubes[index / 64] |= 1ULL << (index % 64);
0095 
0096     }
0097 
0098     m_stackPtr = -1;
0099     m_owners[index] = player;       // Take ownership if not already owned.
0100     if (m_maxValues [index] == m_values [index]++) {    // Increase the cube.
0101     m_stack [++m_stackPtr] = index; // Stack an expansion step.
0102     }
0103     if (steps) {
0104         steps->append (index + 1);  // Record the beginning of the move.
0105     }
0106     if (oldOwner != player) {       // If owner changed, update cubesToWin.
0107         m_cubesToWin [oldOwner]++;
0108         m_cubesToWin [player]--;
0109         if (m_cubesToWin [player] <= 0) {
0110             // Append 0 to the move-step list and return player-won.
0111             if (steps) {
0112                 steps->append (0);
0113             }
0114             // printBox();
0115             return true;;
0116         }
0117     }
0118 
0119     while (m_stackPtr >= 0) {
0120         // Pop the stack and decrease an overloaded cube.
0121     index = m_stack [m_stackPtr--];
0122 
0123         m_values[index] = m_values[index] - m_maxValues[index];
0124 
0125         // Append -index-1 to move list, if not still overloaded.
0126         if (steps && (m_values[index] <= m_maxValues[index])) {
0127             steps->append (-index - 1); // Record the end of a step or move.
0128         }
0129 
0130         // Move each neighbor.
0131         int indexN;
0132     int offset = index * 4;
0133         for (int nb = 0; nb < 4; nb++) {
0134             if ((indexN = m_neighbors [offset + nb]) < 0)
0135                 continue;       // No neighbor on this side.
0136 
0137             if (undodata && !(savedCubes[indexN / 64] & (1ULL << (indexN % 64)))) {
0138                 undodata->changedCubes[saveCubes++] = ((indexN << 8) 
0139                                                        | (m_owners[indexN] << 4)
0140                                                        | (m_values[indexN] << 0));
0141                 savedCubes[indexN / 64] |= 1ULL << (indexN % 64);
0142             }
0143 
0144             // Increase the neighbor and take over ownership.
0145             oldOwner = m_owners[indexN];
0146             m_owners[indexN] = player;
0147             if (m_maxValues [indexN] == m_values [indexN]++) {
0148                 // Continue a cascade by pushing a new step onto the stack.
0149                 m_stack [++m_stackPtr] = indexN;
0150             }
0151             if (steps) {
0152                 steps->append (indexN + 1); // Record beginning of move-step.
0153             }
0154             if (oldOwner != player) {   // If owner changed, update cubesToWin.
0155                 m_cubesToWin [oldOwner]++;
0156                 m_cubesToWin [player]--;
0157             }
0158         }
0159         if (m_values[index] > m_maxValues[index]) {
0160             // The cube is still overloaded, so push it back onto the stack.
0161             m_stack [++m_stackPtr] = index;
0162         }
0163         if (m_cubesToWin [player] <= 0) {
0164             // Append 0 to the move-step list and return player-won.
0165             if (steps) {
0166                 steps->append (0);
0167             }
0168 
0169             // Mark the end of changed cubes in the undodata.
0170             if (undodata) {
0171                 undodata->changedCubes[saveCubes++] = 0xffff;
0172             }
0173 
0174             // printBox();
0175             return true;
0176         }
0177         // printBox();
0178     } // End while()
0179 
0180     if (undodata) {
0181         undodata->changedCubes[saveCubes++] = 0xffff;
0182     }
0183 
0184     // printBox();
0185     return false;
0186 }
0187 
0188 void AI_Box::undoMove (MoveUndodata * undodata)
0189 {
0190     m_cubesToWin[Nobody] = undodata->oldCubesToWin[Nobody];
0191     m_cubesToWin[One] = undodata->oldCubesToWin[One];
0192     m_cubesToWin[Two] = undodata->oldCubesToWin[Two];
0193 
0194     for (int i = 0; undodata->changedCubes[i] != 0xffff; ++i) {
0195         int index = (undodata->changedCubes[i] >> 8) & 0xff;
0196         m_owners[index] = Player((undodata->changedCubes[i] >> 4) & 0xf);
0197         m_values[index] = (undodata->changedCubes[i] >> 0) & 0xf;
0198     }
0199 }
0200 
0201 
0202 // ----------------------------------------------------------------
0203 //                         Game history
0204 
0205 
0206 void AI_Box::copyPosition (Player player, bool isAI, int index)
0207 {
0208 #if AILog > 4
0209     qCDebug(KJUMPINGCUBE_LOG) << "AI_Box::copyPosition (" << player << "," << isAI << ")";
0210     printBox();
0211 #endif
0212     if (m_undoIndex >= m_undoList.count()) {
0213 #if AILog > 4
0214     qCDebug(KJUMPINGCUBE_LOG) << "Call emptyPosition (" << m_nCubes << ")";
0215 #endif
0216         m_undoList.append (emptyPosition (m_nCubes));
0217     }
0218 #if AILog > 4
0219     qCDebug(KJUMPINGCUBE_LOG) << "m_undoIndex" << m_undoIndex << "m_undoList.count()" << m_undoList.count();
0220 #endif
0221     Position * pos = m_undoList.at (m_undoIndex);
0222     save (pos, player, isAI);
0223     pos->index = index; // IDW TODO - Do this in save()?
0224     m_owners = pos->owners;
0225     m_values = pos->values;
0226 #if AILog > 4
0227     printBox();
0228 #endif
0229     m_undoIndex++;
0230     m_redoLimit = m_undoIndex;
0231 }
0232 
0233 bool AI_Box::undoPosition (Player & player, bool & isAI, int & index)
0234 {
0235     bool result = undoPosition (player);
0236     if (m_undoIndex > 0) {
0237         Position * pos = m_undoList.at (m_undoIndex);
0238         isAI   = pos->isAI;
0239         index  = pos->index;
0240     }
0241     return result;
0242 }
0243 
0244 bool AI_Box::undoPosition (Player & player)
0245 {
0246     if (m_undoIndex > 1) {
0247     m_undoIndex--;
0248     Position * pos = m_undoList.at (m_undoIndex - 1);
0249     restore (pos);
0250     player = pos->player;
0251     }
0252 #if AILog > 4
0253     qCDebug(KJUMPINGCUBE_LOG) << "AI_Box::undoPosition (player =" << player << "), m_undoIndex" << m_undoIndex << "UNDONE POSITION";
0254     printBox();
0255 #endif
0256     return (m_undoIndex > 1);
0257 }
0258 
0259 bool AI_Box::redoPosition (Player & player, bool & isAI, int & index)
0260 {
0261     if (m_undoIndex < m_redoLimit) {
0262     Position * pos = m_undoList.at (m_undoIndex);
0263     restore (pos);
0264     player = pos->player;
0265     isAI   = pos->isAI;
0266     index  = pos->index;
0267     m_undoIndex++;
0268     }
0269 #if AILog > 4
0270     qCDebug(KJUMPINGCUBE_LOG) << "AI_Box::redoPosition (player =" << player << "), m_undoIndex" << m_undoIndex << "REDONE POSITION";
0271     printBox();
0272 #endif
0273     return (m_undoIndex < m_redoLimit);
0274 }
0275 
0276 void AI_Box::initPosition (const AI_Box * box, Player player, bool isAI)
0277 {
0278     if (box->side() != m_side) return;
0279 
0280     Position * pos = m_undoList.at (0);
0281     m_owners = pos->owners;
0282     m_values = pos->values;
0283 
0284     for (int n = 0; n < m_nCubes; n++) {
0285         m_owners [n] = box->owner (n);
0286         m_values [n] = box->value (n);
0287     }
0288     restore (pos);
0289     pos->player = player;
0290     pos->isAI   = isAI;
0291     // IDW TODO - Need index parameter? initPosition() is used only by AI_Main.
0292     pos->index  = 0;
0293     m_undoIndex = 1;
0294     m_redoLimit = m_undoIndex;
0295 }
0296 
0297 void AI_Box::save (Position * pos, Player player, bool isAI)
0298 {
0299     // The NEXT player will face this position, after THIS player has moved.
0300     pos->player = (player == Two) ? One : Two;
0301     pos->isAI   = isAI;
0302     pos->nCubes = m_nCubes;
0303     for (int n = 0; n < m_nCubes; n++) {
0304     pos->owners [n] = m_owners [n];
0305     pos->values [n] = m_values [n];
0306     }
0307 }
0308 
0309 void AI_Box::restore (Position * pos)
0310 {
0311     if (pos->nCubes != m_nCubes) return;
0312 
0313     m_owners = pos->owners;
0314     m_values = pos->values;
0315 
0316     m_cubesToWin [Nobody] = m_nCubes;
0317     m_cubesToWin [One]    = m_nCubes;
0318     m_cubesToWin [Two]    = m_nCubes;
0319 
0320     for (int n = 0; n < m_nCubes; n++) {
0321     m_cubesToWin [m_owners [n]]--;
0322     }
0323 }
0324 
0325 void AI_Box::discard (Position * pos)
0326 {
0327     delete[] pos->owners;
0328     delete[] pos->values;
0329     delete pos;
0330 }
0331 
0332 AI_Box::Position * AI_Box::emptyPosition (int nCubes)
0333 {
0334     Position * pos = new Position;
0335     pos->nCubes = nCubes;
0336     pos->owners = new Player [nCubes];
0337     pos->values = new int [nCubes];
0338     return pos;
0339 }
0340 
0341 void AI_Box::clear()
0342 {
0343     for (int index = 0; index < m_nCubes; index++) {
0344         m_owners [index] = Nobody;
0345         m_values [index] = 1;
0346     }
0347     m_stackPtr = -1;
0348 
0349     m_cubesToWin [Nobody] = 0;
0350     m_cubesToWin [One]    = m_nCubes;
0351     m_cubesToWin [Two]    = m_nCubes;
0352 
0353     m_undoIndex = 1;
0354     m_redoLimit = m_undoIndex;
0355 }
0356 
0357 void AI_Box::indexNeighbors()
0358 {
0359     int offset = 0;
0360     for (int index = 0; index < m_nCubes; index++) {
0361         m_maxValues [index] = 0;
0362         for (int nb = 0; nb < 4; nb++) {
0363             m_neighbors [offset + nb] = -1;
0364         }
0365 
0366         int x = index / m_side;
0367         int y = index % m_side;
0368         int limit = m_side - 1;
0369         if (y > 0) {
0370             m_maxValues [index]++;  // Has a neighbor on the North side.
0371             m_neighbors [offset]     = index - 1;
0372         }
0373         if (y < limit) {
0374             m_maxValues [index]++;  // Has a neighbor on the South side.
0375             m_neighbors [offset + 1] = index + 1;
0376         }
0377         if (x < limit) {
0378             m_maxValues [index]++;  // Has a neighbor on the East side.
0379             m_neighbors [offset + 2] = index + m_side;
0380         }
0381         if (x > 0) {
0382             m_maxValues [index]++;  // Has a neighbor on the West side.
0383             m_neighbors [offset + 3] = index - m_side;
0384         }
0385     offset = offset + 4;
0386     }
0387 }
0388 
0389 #if AILog > 0
0390 void AI_Box::printBox()
0391 {
0392     // return;
0393     // IDW test. For debugging.
0394     for (int y = 0; y < m_side; y++) {
0395         fprintf (stderr, "   ");
0396         for (int x = 0; x < m_side; x++) {
0397         int index = x * m_side + y;
0398         if (m_owners[index] == Nobody) fprintf (stderr, "  .");
0399         else fprintf (stderr, " %2d", (m_owners[index] == One) ?
0400              m_values[index] : -m_values[index]);
0401         }
0402         fprintf (stderr, "\n");
0403     }
0404 }
0405 #endif
0406 
0407 #include "moc_ai_box.cpp"