File indexing completed on 2024-04-21 04:02:36
0001 /* 0002 SPDX-FileCopyrightText: 1997 Mathias Mueller <in5y158@public.uni-hamburg.de> 0003 SPDX-FileCopyrightText: 2006-2007 Mauricio Piacentini <mauricio@tabuleiro.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 // own 0009 #include "gamedata.h" 0010 0011 // Qt 0012 #include <QDataStream> 0013 0014 // KMahjongg 0015 #include "boardlayout.h" 0016 #include "kmahjongg_debug.h" 0017 #include "prefs.h" 0018 0019 GameData::GameData(BoardLayout * boardlayout) 0020 { 0021 m_width = boardlayout->getWidth(); 0022 m_height = boardlayout->getHeight(); 0023 m_depth = boardlayout->getDepth(); 0024 m_maxTiles = (m_width * m_height * m_depth) / 4; 0025 0026 m_highlight = QByteArray(m_width * m_height * m_depth, 0); 0027 m_mask = QByteArray(m_width * m_height * m_depth, 0); 0028 m_board = QByteArray(m_width * m_height * m_depth, 0); 0029 0030 POSITION e; //constructor initializes it to 0 0031 0032 m_moveList = QList<POSITION>(m_maxTiles, e); 0033 m_tilePositions = QList<POSITION>(m_maxTiles, e); 0034 m_posTable = QList<POSITION>(m_maxTiles, e); 0035 m_positionDepends = QList<DEPENDENCY>(m_maxTiles); 0036 0037 //Copy board layout over 0038 boardlayout->copyBoardLayout((UCHAR *)getMaskBytes(), m_maxTileNum); //FIXME: is this cast safe? 0039 } 0040 0041 GameData::~GameData() 0042 { 0043 } 0044 0045 void GameData::putTile(short z, short y, short x, UCHAR f) 0046 { 0047 setBoardData(z, y, x, f); 0048 setBoardData(z, y + 1, x, f); 0049 setBoardData(z, y + 1, x + 1, f); 0050 setBoardData(z, y, x + 1, f); 0051 } 0052 0053 bool GameData::tilePresent(int z, int y, int x) const 0054 { 0055 if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) || (z > m_depth - 1)) { 0056 return false; 0057 } 0058 0059 return (BoardData(z, y, x) != 0 && MaskData(z, y, x) == '1'); 0060 } 0061 0062 UCHAR GameData::MaskData(short z, short y, short x) const 0063 { 0064 if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) || (z > m_depth - 1)) { 0065 return 0; 0066 } 0067 0068 return m_mask.at((z * m_width * m_height) + (y * m_width) + x); 0069 } 0070 0071 UCHAR GameData::HighlightData(short z, short y, short x) const 0072 { 0073 if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) || (z > m_depth - 1)) { 0074 return 0; 0075 } 0076 0077 return m_highlight.at((z * m_width * m_height) + (y * m_width) + x); 0078 } 0079 0080 UCHAR GameData::BoardData(short z, short y, short x) const 0081 { 0082 if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) || (z > m_depth - 1)) { 0083 return 0; 0084 } 0085 0086 return m_board.at((z * m_width * m_height) + (y * m_width) + x); 0087 } 0088 0089 void GameData::setBoardData(short z, short y, short x, UCHAR value) 0090 { 0091 if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) 0092 || (z > m_depth - 1)) { 0093 return; 0094 } 0095 0096 m_board[(z * m_width * m_height) + (y * m_width) + x] = value; 0097 } 0098 0099 POSITION & GameData::MoveListData(short i) 0100 { 0101 if ((i >= m_moveList.size()) || (i < 0)) { 0102 qCDebug(KMAHJONGG_LOG) << "Attempt to access GameData::MoveListData with invalid index"; 0103 i = 0; 0104 } 0105 0106 return m_moveList[i]; 0107 } 0108 0109 void GameData::setMoveListData(short i, POSITION & value) 0110 { 0111 if ((i >= m_moveList.size()) || (i < 0)) { 0112 return; 0113 } 0114 0115 m_moveList[i] = value; 0116 } 0117 0118 void GameData::generateTilePositions() 0119 { 0120 // Generate the position data for the layout from contents of Game.Map. 0121 0122 m_numTilesToGenerate = 0; 0123 0124 for (int z = 0; z < m_depth; ++z) { 0125 for (int y = 0; y < m_height; ++y) { 0126 for (int x = 0; x < m_width; ++x) { 0127 setBoardData(z, y, x, 0); 0128 if (MaskData(z, y, x) == '1') { 0129 m_tilePositions[m_numTilesToGenerate].x = x; 0130 m_tilePositions[m_numTilesToGenerate].y = y; 0131 m_tilePositions[m_numTilesToGenerate].z = z; 0132 m_tilePositions[m_numTilesToGenerate].f = 254; 0133 ++m_numTilesToGenerate; 0134 } 0135 } 0136 } 0137 } 0138 } 0139 0140 void GameData::generatePositionDepends() 0141 { 0142 // Generate the dependency data for the layout from the position data. 0143 // Note that the coordinates of each tile in tilePositions are those of 0144 // the upper left quarter of the tile. 0145 0146 // For each tile, 0147 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0148 // Get its basic position data 0149 int x = m_tilePositions[i].x; 0150 int y = m_tilePositions[i].y; 0151 int z = m_tilePositions[i].z; 0152 0153 // LHS dependencies 0154 m_positionDepends[i].lhs_dep[0] = tileAt(x - 1, y, z); 0155 m_positionDepends[i].lhs_dep[1] = tileAt(x - 1, y + 1, z); 0156 0157 // Make them unique 0158 if (m_positionDepends[i].lhs_dep[1] == m_positionDepends[i].lhs_dep[0]) { 0159 m_positionDepends[i].lhs_dep[1] = -1; 0160 } 0161 0162 // RHS dependencies 0163 m_positionDepends[i].rhs_dep[0] = tileAt(x + 2, y, z); 0164 m_positionDepends[i].rhs_dep[1] = tileAt(x + 2, y + 1, z); 0165 0166 // Make them unique 0167 if (m_positionDepends[i].rhs_dep[1] == m_positionDepends[i].rhs_dep[0]) { 0168 m_positionDepends[i].rhs_dep[1] = -1; 0169 } 0170 0171 // Turn dependencies 0172 m_positionDepends[i].turn_dep[0] = tileAt(x, y, z + 1); 0173 m_positionDepends[i].turn_dep[1] = tileAt(x + 1, y, z + 1); 0174 m_positionDepends[i].turn_dep[2] = tileAt(x + 1, y + 1, z + 1); 0175 m_positionDepends[i].turn_dep[3] = tileAt(x, y + 1, z + 1); 0176 0177 // Make them unique 0178 for (int j = 0; j < 3; ++j) { 0179 for (int k = j + 1; k < 4; ++k) { 0180 if (m_positionDepends[i].turn_dep[j] == m_positionDepends[i].turn_dep[k]) { 0181 m_positionDepends[i].turn_dep[k] = -1; 0182 } 0183 } 0184 } 0185 0186 // Placement dependencies 0187 m_positionDepends[i].place_dep[0] = tileAt(x, y, z - 1); 0188 m_positionDepends[i].place_dep[1] = tileAt(x + 1, y, z - 1); 0189 m_positionDepends[i].place_dep[2] = tileAt(x + 1, y + 1, z - 1); 0190 m_positionDepends[i].place_dep[3] = tileAt(x, y + 1, z - 1); 0191 0192 // Make them unique 0193 for (int j = 0; j < 3; ++j) { 0194 for (int k = j + 1; k < 4; ++k) { 0195 if (m_positionDepends[i].place_dep[j] == m_positionDepends[i].place_dep[k]) { 0196 m_positionDepends[i].place_dep[k] = -1; 0197 } 0198 } 0199 } 0200 0201 // Filled and free indicators. 0202 m_positionDepends[i].filled = false; 0203 m_positionDepends[i].free = false; 0204 } 0205 } 0206 0207 int GameData::tileAt(int x, int y, int z) const 0208 { 0209 // x, y, z are the coordinates of a *quarter* tile. This returns the 0210 // index (in positions) of the tile at those coordinates or -1 if there 0211 // is no tile at those coordinates. Note that the coordinates of each 0212 // tile in positions are those of the upper left quarter of the tile. 0213 0214 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0215 if (m_tilePositions[i].z == z) { 0216 if ((m_tilePositions[i].x == x && m_tilePositions[i].y == y) 0217 || (m_tilePositions[i].x == x - 1 && m_tilePositions[i].y == y) 0218 || (m_tilePositions[i].x == x - 1 && m_tilePositions[i].y == y - 1) 0219 || (m_tilePositions[i].x == x && m_tilePositions[i].y == y - 1)) { 0220 return i; 0221 } 0222 } 0223 } 0224 return -1; 0225 } 0226 0227 bool GameData::generateSolvableGame() 0228 { 0229 // Initially we want to mark positions on layer 0 so that we have only 0230 // one free position per apparent horizontal line. 0231 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0232 // Pick a random tile on layer 0 0233 int position, cnt = 0; 0234 0235 do { 0236 position = random.bounded(m_numTilesToGenerate); 0237 0238 if (cnt++ > (m_numTilesToGenerate * m_numTilesToGenerate)) { 0239 return false; // bail 0240 } 0241 } while (m_tilePositions[position].z != 0); 0242 0243 // If there are no other free positions on the same apparent 0244 // horizontal line, we can mark that position as free. 0245 if (onlyFreeInLine(position)) { 0246 m_positionDepends[position].free = true; 0247 } 0248 } 0249 0250 // Check to make sure we really got them all. Very important for 0251 // this algorithm. 0252 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0253 if (m_tilePositions[i].z == 0 && onlyFreeInLine(i)) { 0254 m_positionDepends[i].free = true; 0255 } 0256 } 0257 0258 // Get ready to place the tiles 0259 int lastPosition = -1; 0260 int position = -1; 0261 int position2 = -1; 0262 0263 // For each position, 0264 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0265 // If this is the first tile in a 144 tile set, 0266 if ((i % 144) == 0) { 0267 // Initialise the faces to allocate. For the classic 0268 // dragon board there are 144 tiles. So we allocate and 0269 // randomise the assignment of 144 tiles. If there are > 144 0270 // tiles we will reallocate and re-randomise as we run out. 0271 // One advantage of this method is that the pairs to assign are 0272 // non-linear. In kmahjongg 0.4, If there were > 144 the same 0273 // allocation series was followed. So 154 = 144 + 10 rods. 0274 // 184 = 144 + 40 rods (20 pairs) which overwhemed the board 0275 // with rods and made deadlock games more likely. 0276 randomiseFaces(); 0277 } 0278 0279 // If this is the first half of a pair, there is no previous 0280 // position for the pair. 0281 if ((i & 1) == 0) { 0282 lastPosition = -1; 0283 } 0284 0285 // Select a position for the tile, relative to the position of 0286 // the last tile placed. 0287 if ((position = selectPosition(lastPosition)) < 0) { 0288 return false; // bail 0289 } 0290 0291 if (i < m_numTilesToGenerate - 1) { 0292 if ((position2 = selectPosition(lastPosition)) < 0) { 0293 return false; // bail 0294 } 0295 if (m_tilePositions[position2].z > m_tilePositions[position].z) { 0296 position = position2; // higher is better 0297 } 0298 } 0299 0300 // Place the tile. 0301 placeTile(position, m_tilePair[i % 144]); 0302 0303 // Remember the position 0304 lastPosition = position; 0305 } 0306 0307 // The game is solvable. 0308 return true; 0309 } 0310 0311 bool GameData::onlyFreeInLine(int position) const 0312 { 0313 // Determines whether it is ok to mark this position as "free" because 0314 // there are no other positions marked "free" in its apparent horizontal 0315 // line. 0316 0317 int i, i0, w; 0318 int lin, rin, out; 0319 0320 QList<int> nextLeft = QList<int>(m_maxTiles, 0); 0321 QList<int> nextRight = QList<int>(m_maxTiles, 0); 0322 0323 /* Check left, starting at position */ 0324 lin = 0; 0325 out = 0; 0326 nextLeft[lin++] = position; 0327 0328 do { 0329 w = nextLeft[out++]; 0330 if (m_positionDepends[w].free || m_positionDepends[w].filled) { 0331 return false; 0332 } 0333 0334 if ((i = m_positionDepends[w].lhs_dep[0]) != -1) { 0335 if (lin == m_maxTiles) { 0336 return false; 0337 } 0338 nextLeft[lin++] = i; 0339 } 0340 0341 i0 = i; 0342 0343 if ((i = m_positionDepends[w].lhs_dep[1]) != -1 && i0 != i) { 0344 if (lin == m_maxTiles) { 0345 return false; 0346 } 0347 nextLeft[lin++] = i; 0348 } 0349 } while (lin > out); 0350 0351 /* Check right, starting at position */ 0352 rin = 0; 0353 out = 0; 0354 nextRight[rin++] = position; 0355 0356 do { 0357 w = nextRight[out++]; 0358 0359 if (m_positionDepends[w].free || m_positionDepends[w].filled) { 0360 return false; 0361 } 0362 0363 if ((i = m_positionDepends[w].rhs_dep[0]) != -1) { 0364 if (rin == m_maxTiles) { 0365 return false; 0366 } 0367 nextRight[rin++] = i; 0368 } 0369 0370 i0 = i; 0371 0372 if ((i = m_positionDepends[w].rhs_dep[1]) != -1 && i0 != i) { 0373 if (rin == m_maxTiles) { 0374 return false; 0375 } 0376 nextRight[rin++] = i; 0377 } 0378 } while (rin > out); 0379 0380 // Here, the position can be marked "free" 0381 return true; 0382 } 0383 0384 int GameData::selectPosition(int lastPosition) 0385 { 0386 int position, cnt = 0; 0387 bool goodPosition = false; 0388 0389 // while a good position has not been found, 0390 while (!goodPosition) { 0391 // Select a random, but free, position. 0392 do { 0393 position = random.bounded(m_numTilesToGenerate); 0394 0395 if (cnt++ > (m_numTilesToGenerate * m_numTilesToGenerate)) { 0396 return -1; // bail 0397 } 0398 } while (!m_positionDepends[position].free); 0399 0400 // Found one. 0401 goodPosition = true; 0402 0403 // If there is a previous position to take into account, 0404 if (lastPosition != -1) { 0405 // Check the new position against the last one. 0406 for (int i = 0; i < 4; ++i) { 0407 if (m_positionDepends[position].place_dep[i] == lastPosition) { 0408 goodPosition = false; // not such a good position 0409 } 0410 } 0411 0412 for (int i = 0; i < 2; ++i) { 0413 if ((m_positionDepends[position].lhs_dep[i] == lastPosition) 0414 || (m_positionDepends[position].rhs_dep[i] == lastPosition)) { 0415 goodPosition = false; // not such a good position 0416 } 0417 } 0418 } 0419 } 0420 return position; 0421 } 0422 0423 void GameData::placeTile(int position, int tile) 0424 { 0425 // Install the tile in the specified position 0426 m_tilePositions[position].f = tile; 0427 putTile(m_tilePositions[position]); 0428 0429 // Update position dependency data 0430 m_positionDepends[position].filled = true; 0431 m_positionDepends[position].free = false; 0432 0433 // Now examine the tiles near this to see if this makes them "free". 0434 int depend; 0435 0436 for (int i = 0; i < 4; ++i) { 0437 if ((depend = m_positionDepends[position].turn_dep[i]) != -1) { 0438 updateDepend(depend); 0439 } 0440 } 0441 0442 for (int i = 0; i < 2; ++i) { 0443 if ((depend = m_positionDepends[position].lhs_dep[i]) != -1) { 0444 updateDepend(depend); 0445 } 0446 0447 if ((depend = m_positionDepends[position].rhs_dep[i]) != -1) { 0448 updateDepend(depend); 0449 } 0450 } 0451 } 0452 0453 void GameData::updateDepend(int position) 0454 { 0455 // Updates the free indicator in the dependency data for a position 0456 // based on whether the positions on which it depends are filled. 0457 0458 // If the position is valid and not filled 0459 if (position >= 0 && !m_positionDepends[position].filled) { 0460 // Check placement depends. If they are not filled, the 0461 // position cannot become free. 0462 int depend; 0463 for (int i = 0; i < 4; ++i) { 0464 if ((depend = m_positionDepends[position].place_dep[i]) != -1) { 0465 if (!m_positionDepends[depend].filled) { 0466 return; 0467 } 0468 } 0469 } 0470 0471 // If position is first free on apparent horizontal, it is 0472 // now free to be filled. 0473 if (onlyFreeInLine(position)) { 0474 m_positionDepends[position].free = true; 0475 return; 0476 } 0477 0478 // Assume no LHS positions to fill 0479 bool lfilled = false; 0480 0481 // If positions to LHS 0482 if ((m_positionDepends[position].lhs_dep[0] != -1) 0483 || (m_positionDepends[position].lhs_dep[1] != -1)) { 0484 // Assume LHS positions filled 0485 lfilled = true; 0486 0487 for (int i = 0; i < 2; ++i) { 0488 if ((depend = m_positionDepends[position].lhs_dep[i]) != -1) { 0489 if (!m_positionDepends[depend].filled) { 0490 lfilled = false; 0491 } 0492 } 0493 } 0494 } 0495 0496 // Assume no RHS positions to fill 0497 bool rfilled = false; 0498 0499 // If positions to RHS 0500 if ((m_positionDepends[position].rhs_dep[0] != -1) 0501 || (m_positionDepends[position].rhs_dep[1] != -1)) { 0502 // Assume LHS positions filled 0503 rfilled = true; 0504 0505 for (int i = 0; i < 2; ++i) { 0506 if ((depend = m_positionDepends[position].rhs_dep[i]) != -1) { 0507 if (!m_positionDepends[depend].filled) { 0508 rfilled = false; 0509 } 0510 } 0511 } 0512 } 0513 0514 // If positions to left or right are filled, this position 0515 // is now free to be filled. 0516 m_positionDepends[position].free = (lfilled || rfilled); 0517 } 0518 } 0519 0520 bool GameData::generateStartPosition2() 0521 { 0522 // For each tile, 0523 for (int i = 0; i < m_numTilesToGenerate; ++i) { 0524 // Get its basic position data 0525 int x = m_tilePositions[i].x; 0526 int y = m_tilePositions[i].y; 0527 int z = m_tilePositions[i].z; 0528 0529 // Clear Game.Board at that position 0530 setBoardData(z, y, x, 0); 0531 0532 // Clear tile placed/free indicator(s). 0533 m_positionDepends[i].filled = false; 0534 m_positionDepends[i].free = false; 0535 0536 // Set tile face blank 0537 m_tilePositions[i].f = 254; 0538 } 0539 0540 // If solvable games should be generated, 0541 if (Prefs::solvableGames()) { 0542 if (generateSolvableGame()) { 0543 m_tileNum = m_maxTileNum; 0544 return true; 0545 } else { 0546 return false; 0547 } 0548 } 0549 0550 // Initialise the faces to allocate. For the classic 0551 // dragon board there are 144 tiles. So we allocate and 0552 // randomise the assignment of 144 tiles. If there are > 144 0553 // tiles we will reallocate and re-randomise as we run out. 0554 // One advantage of this method is that the pairs to assign are 0555 // non-linear. In kmahjongg 0.4, If there were > 144 the same 0556 // allocation series was followed. So 154 = 144 + 10 rods. 0557 // 184 = 144 + 40 rods (20 pairs) which overwhemed the board 0558 // with rods and made deadlock games more likely. 0559 0560 int remaining = m_numTilesToGenerate; 0561 randomiseFaces(); 0562 0563 for (int tile = 0; tile < m_numTilesToGenerate; tile += 2) { 0564 int p1; 0565 int p2; 0566 0567 if (remaining > 2) { 0568 p2 = p1 = random.bounded(remaining - 2); 0569 int bail = 0; 0570 0571 while (p1 == p2) { 0572 p2 = random.bounded(remaining - 2); 0573 0574 if (bail >= 100) { 0575 if (p1 != p2) { 0576 break; 0577 } 0578 } 0579 0580 if ((m_tilePositions[p1].y == m_tilePositions[p2].y) 0581 && (m_tilePositions[p1].z == m_tilePositions[p2].z)) { 0582 // skip if on same y line 0583 ++bail; 0584 p2 = p1; 0585 0586 continue; 0587 } 0588 } 0589 } else { 0590 p1 = 0; 0591 p2 = 1; 0592 } 0593 0594 POSITION a; 0595 POSITION b; 0596 0597 a = m_tilePositions[p1]; 0598 b = m_tilePositions[p2]; 0599 0600 m_tilePositions[p1] = m_tilePositions[remaining - 1]; 0601 m_tilePositions[p2] = m_tilePositions[remaining - 2]; 0602 0603 remaining -= 2; 0604 0605 getFaces(a, b); 0606 putTile(a); 0607 putTile(b); 0608 } 0609 0610 m_tileNum = m_maxTileNum; 0611 0612 return 1; 0613 } 0614 0615 void GameData::getFaces(POSITION & a, POSITION & b) 0616 { 0617 a.f = m_tilePair[m_tilesUsed]; 0618 b.f = m_tilePair[m_tilesUsed + 1]; 0619 m_tilesUsed += 2; 0620 0621 if (m_tilesUsed >= 144) { 0622 randomiseFaces(); 0623 } 0624 } 0625 0626 void GameData::randomiseFaces() 0627 { 0628 int nr; 0629 int numAlloced = 0; 0630 // stick in 144 tiles in pairsa. 0631 0632 for (nr = 0; nr < 9 * 4; ++nr) { 0633 m_tilePair[numAlloced++] = TILE_CHARACTER + (nr / 4); // 4*9 Tiles 0634 } 0635 0636 for (nr = 0; nr < 9 * 4; ++nr) { 0637 m_tilePair[numAlloced++] = TILE_BAMBOO + (nr / 4); // 4*9 Tiles 0638 } 0639 0640 for (nr = 0; nr < 9 * 4; ++nr) { 0641 m_tilePair[numAlloced++] = TILE_ROD + (nr / 4); // 4*9 Tiles 0642 } 0643 0644 for (nr = 0; nr < 4; ++nr) { 0645 m_tilePair[numAlloced++] = TILE_FLOWER + nr; // 4 Tiles 0646 } 0647 0648 for (nr = 0; nr < 4; ++nr) { 0649 m_tilePair[numAlloced++] = TILE_SEASON + nr; // 4 Tiles 0650 } 0651 0652 for (nr = 0; nr < 4 * 4; ++nr) { 0653 m_tilePair[numAlloced++] = TILE_WIND + (nr / 4); // 4*4 Tiles 0654 } 0655 0656 for (nr = 0; nr < 3 * 4; ++nr) { 0657 m_tilePair[numAlloced++] = TILE_DRAGON + (nr / 4); // 3*4 Tiles 0658 } 0659 0660 //randomise. Keep pairs together. Ie take two random 0661 //odd numbers (n,x) and swap n, n+1 with x, x+1 0662 0663 int at = 0; 0664 for (int r = 0; r < 200; ++r) { 0665 int to = at; 0666 0667 while (to == at) { 0668 to = random.bounded(144); 0669 0670 if ((to & 1) != 0) { 0671 --to; 0672 } 0673 } 0674 0675 UCHAR tmp = m_tilePair[at]; 0676 m_tilePair[at] = m_tilePair[to]; 0677 m_tilePair[to] = tmp; 0678 tmp = m_tilePair[at + 1]; 0679 m_tilePair[at + 1] = m_tilePair[to + 1]; 0680 m_tilePair[to + 1] = tmp; 0681 0682 at += 2; 0683 0684 if (at >= 144) { 0685 at = 0; 0686 } 0687 } 0688 0689 m_tilesUsed = 0; 0690 } 0691 0692 bool isFlower(UCHAR tile) 0693 { 0694 return (tile >= TILE_FLOWER && tile <= TILE_FLOWER + 3); 0695 } 0696 0697 bool isSeason(UCHAR tile) 0698 { 0699 return (tile >= TILE_SEASON && tile <= TILE_SEASON + 3); 0700 } 0701 0702 bool isBamboo(UCHAR tile) 0703 { 0704 return (tile >= TILE_BAMBOO && tile < TILE_BAMBOO + 9); 0705 } 0706 0707 bool isCharacter(UCHAR tile) 0708 { 0709 return (tile < TILE_CHARACTER + 9); 0710 } 0711 0712 bool isRod(UCHAR tile) 0713 { 0714 return (tile >= TILE_ROD && tile < TILE_ROD + 9); 0715 } 0716 0717 bool isDragon(UCHAR tile) 0718 { 0719 return (tile >= TILE_DRAGON && tile < TILE_DRAGON + 3); 0720 } 0721 0722 bool isWind(UCHAR tile) 0723 { 0724 return (tile >= TILE_WIND && tile < TILE_WIND + 4); 0725 } 0726 0727 bool GameData::isMatchingTile(POSITION & pos1, POSITION & pos2) const 0728 { 0729 // don't compare 'equal' positions 0730 if (memcmp(&pos1, &pos2, sizeof(POSITION))) { 0731 UCHAR FA = BoardData(pos1.z, pos1.y, pos1.x); 0732 UCHAR FB = BoardData(pos2.z, pos2.y, pos2.x); 0733 0734 if ((FA == FB) || (isFlower(FA) && isFlower(FB)) || (isSeason(FA) && isSeason(FB))) { 0735 return true; 0736 } 0737 } 0738 return false; 0739 } 0740 0741 void GameData::setRemovedTilePair(POSITION & a, POSITION & b) 0742 { 0743 if (isFlower(a.f)) { 0744 ++m_removedFlower[a.f - TILE_FLOWER]; 0745 ++m_removedFlower[b.f - TILE_FLOWER]; 0746 0747 return; 0748 } 0749 0750 if (isSeason(a.f)) { 0751 ++m_removedSeason[a.f - TILE_SEASON]; 0752 ++m_removedSeason[b.f - TILE_SEASON]; 0753 0754 return; 0755 } 0756 0757 if (isCharacter(a.f)) { 0758 m_removedCharacter[a.f - TILE_CHARACTER] += 2; 0759 0760 return; 0761 } 0762 0763 if (isBamboo(a.f)) { 0764 m_removedBamboo[a.f - TILE_BAMBOO] += 2; 0765 0766 return; 0767 } 0768 0769 if (isRod(a.f)) { 0770 m_removedRod[a.f - TILE_ROD] += 2; 0771 0772 return; 0773 } 0774 0775 if (isDragon(a.f)) { 0776 m_removedDragon[a.f - TILE_DRAGON] += 2; 0777 0778 return; 0779 } 0780 0781 if (isWind(a.f)) { 0782 m_removedWind[a.f - TILE_WIND] += 2; 0783 0784 return; 0785 } 0786 } 0787 0788 void GameData::clearRemovedTilePair(POSITION & a, POSITION & b) 0789 { 0790 if (isFlower(a.f)) { 0791 --m_removedFlower[a.f - TILE_FLOWER]; 0792 --m_removedFlower[b.f - TILE_FLOWER]; 0793 0794 return; 0795 } 0796 0797 if (isSeason(a.f)) { 0798 --m_removedSeason[a.f - TILE_SEASON]; 0799 --m_removedSeason[b.f - TILE_SEASON]; 0800 0801 return; 0802 } 0803 0804 if (isCharacter(a.f)) { 0805 m_removedCharacter[a.f - TILE_CHARACTER] -= 2; 0806 0807 return; 0808 } 0809 0810 if (isBamboo(a.f)) { 0811 m_removedBamboo[a.f - TILE_BAMBOO] -= 2; 0812 0813 return; 0814 } 0815 0816 if (isRod(a.f)) { 0817 m_removedRod[a.f - TILE_ROD] -= 2; 0818 0819 return; 0820 } 0821 0822 if (isDragon(a.f)) { 0823 m_removedDragon[a.f - TILE_DRAGON] -= 2; 0824 0825 return; 0826 } 0827 0828 if (isWind(a.f)) { 0829 m_removedWind[a.f - TILE_WIND] -= 2; 0830 0831 return; 0832 } 0833 } 0834 0835 bool GameData::findMove(POSITION & posA, POSITION & posB) 0836 { 0837 short posEnde = m_maxTileNum; // End of PosTable 0838 0839 for (short z = 0; z < m_depth; ++z) { 0840 for (short y = 0; y < m_height - 1; ++y) { 0841 for (short x = 0; x < m_width - 1; ++x) { 0842 if (MaskData(z, y, x) != static_cast<UCHAR>('1')) { 0843 continue; 0844 } 0845 0846 if (!BoardData(z, y, x)) { 0847 continue; 0848 } 0849 0850 if (z < m_depth - 1) { 0851 if (BoardData(z + 1, y, x) || BoardData(z + 1, y + 1, x) 0852 || BoardData(z + 1, y, x + 1) || BoardData(z + 1, y + 1, x + 1)) { 0853 continue; 0854 } 0855 } 0856 0857 if (x < m_width - 2 && (BoardData(z, y, x - 1) || BoardData(z, y + 1, x - 1)) 0858 && (BoardData(z, y, x + 2) || BoardData(z, y + 1, x + 2))) { 0859 continue; 0860 } 0861 0862 --posEnde; 0863 m_posTable[posEnde].z = z; 0864 m_posTable[posEnde].y = y; 0865 m_posTable[posEnde].x = x; 0866 m_posTable[posEnde].f = BoardData(z, y, x); 0867 } 0868 } 0869 } 0870 0871 short posCount = 0; // store number of pairs found 0872 0873 // The new tile layout with non-continuous horizantal spans 0874 // can lead to huge numbers of matching pairs being exposed. 0875 // we alter the loop to bail out when BoardLayout::maxTiles/2 pairs are found 0876 // (or less); 0877 while (posEnde < m_maxTileNum - 1 && posCount < m_maxTiles - 2) { 0878 for (short Pos = posEnde + 1; Pos < m_maxTileNum; ++Pos) { 0879 if (isMatchingTile(m_posTable[Pos], m_posTable[posEnde])) { 0880 if (posCount < m_maxTiles - 2) { 0881 m_posTable[posCount++] = m_posTable[posEnde]; 0882 m_posTable[posCount++] = m_posTable[Pos]; 0883 } 0884 } 0885 } 0886 ++posEnde; 0887 } 0888 0889 if (posCount >= 2) { 0890 random.seed(QRandomGenerator::global()->generate()); // WABA: Why is the seed reset? 0891 const quint32 pos = random.bounded(posCount) & -2; // Even value 0892 posA = m_posTable[pos]; 0893 posB = m_posTable[pos + 1]; 0894 0895 return true; 0896 } else { 0897 return false; 0898 } 0899 } 0900 0901 int GameData::moveCount() 0902 { 0903 short posEnde = m_maxTileNum; // End of PosTable 0904 0905 for (short z = 0; z < m_depth; ++z) { 0906 for (short y = 0; y < m_height - 1; ++y) { 0907 for (short x = 0; x < m_width - 1; ++x) { 0908 if (MaskData(z, y, x) != static_cast<UCHAR>('1')) { 0909 continue; 0910 } 0911 0912 if (!BoardData(z, y, x)) { 0913 continue; 0914 } 0915 0916 if (z < m_depth - 1) { 0917 if (BoardData(z + 1, y, x) || BoardData(z + 1, y + 1, x) 0918 || BoardData(z + 1, y, x + 1) || BoardData(z + 1, y + 1, x + 1)) { 0919 continue; 0920 } 0921 } 0922 0923 if (x < m_width - 2 && (BoardData(z, y, x - 1) || BoardData(z, y + 1, x - 1)) 0924 && (BoardData(z, y, x + 2) || BoardData(z, y + 1, x + 2))) { 0925 continue; 0926 } 0927 0928 --posEnde; 0929 m_posTable[posEnde].z = z; 0930 m_posTable[posEnde].y = y; 0931 m_posTable[posEnde].x = x; 0932 m_posTable[posEnde].f = BoardData(z, y, x); 0933 } 0934 } 0935 } 0936 0937 short posCount = 0; // store number of pairs found 0938 0939 while (posEnde < m_maxTileNum - 1 && posCount < m_maxTiles - 2) { 0940 for (short Pos = posEnde + 1; Pos < m_maxTileNum; ++Pos) { 0941 if (isMatchingTile(m_posTable[Pos], m_posTable[posEnde])) { 0942 if (posCount < m_maxTiles - 2) { 0943 m_posTable[posCount++] = m_posTable[posEnde]; 0944 m_posTable[posCount++] = m_posTable[Pos]; 0945 } 0946 } 0947 } 0948 0949 ++posEnde; 0950 } 0951 0952 return posCount / 2; 0953 } 0954 0955 short GameData::findAllMatchingTiles(POSITION & posA) 0956 { 0957 short pos = 0; 0958 0959 for (short z = 0; z < m_depth; ++z) { 0960 for (short y = 0; y < m_height - 1; ++y) { 0961 for (short x = 0; x < m_width - 1; ++x) { 0962 if (MaskData(z, y, x) != static_cast<UCHAR>('1')) { 0963 continue; 0964 } 0965 0966 if (!BoardData(z, y, x)) { 0967 continue; 0968 } 0969 0970 if (z < m_depth - 1) { 0971 if (BoardData(z + 1, y, x) || BoardData(z + 1, y + 1, x) 0972 || BoardData(z + 1, y, x + 1) || BoardData(z + 1, y + 1, x + 1)) { 0973 continue; 0974 } 0975 } 0976 0977 if (x < m_width - 2 && (BoardData(z, y, x - 1) || BoardData(z, y + 1, x - 1)) 0978 && (BoardData(z, y, x + 2) || BoardData(z, y + 1, x + 2))) { 0979 continue; 0980 } 0981 0982 m_posTable[pos].z = z; 0983 m_posTable[pos].y = y; 0984 m_posTable[pos].x = x; 0985 m_posTable[pos].f = BoardData(z, y, x); 0986 0987 if (isMatchingTile(posA, m_posTable[pos])) { 0988 ++pos; 0989 } 0990 } 0991 } 0992 } 0993 return pos; 0994 } 0995 0996 bool GameData::loadFromStream(QDataStream & in) 0997 { 0998 in >> m_board; 0999 in >> m_mask; 1000 in >> m_highlight; 1001 in >> m_allowUndo; 1002 in >> m_allowRedo; 1003 in >> m_tileNum; 1004 in >> m_maxTileNum; 1005 1006 //Read list count 1007 in >> m_maxTiles; 1008 1009 //Reconstruct the MoveList 1010 for (int i = 0; i < m_maxTiles; ++i) { 1011 POSITION thispos; 1012 in >> thispos.z; 1013 in >> thispos.y; 1014 in >> thispos.x; 1015 in >> thispos.f; 1016 setMoveListData(i, thispos); 1017 } 1018 return true; 1019 } 1020 1021 bool GameData::saveToStream(QDataStream & out) const 1022 { 1023 out << m_board; 1024 out << m_mask; 1025 out << m_highlight; 1026 out << m_allowUndo; 1027 out << m_allowRedo; 1028 out << m_tileNum; 1029 out << m_maxTileNum; 1030 1031 //write the size of our lists 1032 out << m_maxTiles; 1033 1034 //and then write all position components for the MoveList 1035 for (int i = 0; i < m_maxTiles; ++i) { 1036 POSITION thispos = m_moveList.at(i); 1037 out << static_cast<quint16>(thispos.z); 1038 out << static_cast<quint16>(thispos.y); 1039 out << static_cast<quint16>(thispos.x); 1040 out << static_cast<quint16>(thispos.f); 1041 } 1042 return true; 1043 } 1044 1045 void GameData::shuffle() 1046 { 1047 int count = 0; 1048 1049 // copy positions and faces of the remaining tiles into 1050 // the pos table 1051 for (int z = 0; z < m_depth; ++z) { 1052 for (int y = 0; y < m_height; ++y) { 1053 for (int x = 0; x < m_width; ++x) { 1054 if (BoardData(z, y, x) && MaskData(z, y, x) == '1') { 1055 m_posTable[count].z = z; 1056 m_posTable[count].y = y; 1057 m_posTable[count].x = x; 1058 m_posTable[count].f = BoardData(z, y, x); 1059 ++count; 1060 } 1061 } 1062 } 1063 } 1064 1065 // now lets randomise the faces, selecting 400 pairs at random and 1066 // swapping the faces. 1067 for (int ran = 0; ran < 400; ++ran) { 1068 int pos1 = random.bounded(count); 1069 int pos2 = random.bounded(count); 1070 1071 if (pos1 == pos2) { 1072 continue; 1073 } 1074 1075 BYTE f = m_posTable[pos1].f; 1076 m_posTable[pos1].f = m_posTable[pos2].f; 1077 m_posTable[pos2].f = f; 1078 } 1079 1080 // put the rearranged tiles back. 1081 for (int p = 0; p < count; ++p) { 1082 putTile(m_posTable[p]); 1083 } 1084 }