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"