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 }