1 | /* kmahjongg, the classic mahjongg game for KDE project |
2 | * |
3 | * Copyright (C) 1997 Mathias Mueller <in5y158@public.uni-hamburg.de> |
4 | * Copyright (C) 2006-2007 Mauricio Piacentini <mauricio@tabuleiro.com> |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License |
17 | * along with this program; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ |
19 | |
20 | #include "GameData.h" |
21 | |
22 | #include <KDebug> |
23 | |
24 | |
25 | GameData::GameData(BoardLayout *boardlayout) |
26 | { |
27 | m_width = boardlayout->m_width; |
28 | m_height = boardlayout->m_height; |
29 | m_depth = boardlayout->m_depth; |
30 | m_maxTiles = (m_width * m_height * m_depth) / 4; |
31 | |
32 | Highlight = QByteArray(m_width * m_height * m_depth, 0); |
33 | Mask = QByteArray(m_width * m_height * m_depth, 0); |
34 | Board = QByteArray(m_width * m_height * m_depth, 0); |
35 | |
36 | POSITION e; //constructor initializes it to 0 |
37 | |
38 | MoveList = QVector<POSITION>(m_maxTiles, e); |
39 | tilePositions = QVector<POSITION>(m_maxTiles, e); |
40 | PosTable = QVector<POSITION>(m_maxTiles, e); |
41 | positionDepends = QVector<DEPENDENCY>(m_maxTiles); |
42 | |
43 | //Copy board layout over |
44 | boardlayout->copyBoardLayout((UCHAR *) getMaskBytes(), MaxTileNum); |
45 | } |
46 | |
47 | GameData::~GameData() |
48 | { |
49 | } |
50 | |
51 | void GameData::putTile(short e, short y, short x, UCHAR f) |
52 | { |
53 | setBoardData(e, y, x, f); |
54 | setBoardData(e, y + 1, x, f); |
55 | setBoardData(e, y + 1, x + 1, f); |
56 | setBoardData(e, y, x + 1, f); |
57 | } |
58 | |
59 | bool GameData::tilePresent(int z, int y, int x) |
60 | { |
61 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
62 | || (z > m_depth - 1)) { |
63 | return false; |
64 | } |
65 | |
66 | return (BoardData(z, y, x) != 0 && MaskData(z, y, x) == '1'); |
67 | } |
68 | |
69 | bool GameData::partTile(int z, int y, int x) |
70 | { |
71 | return (BoardData(z, y, x) != 0); |
72 | } |
73 | |
74 | UCHAR GameData::MaskData(short z, short y, short x) |
75 | { |
76 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
77 | || (z > m_depth - 1)) { |
78 | return 0; |
79 | } |
80 | |
81 | return Mask.at((z * m_width * m_height) + (y * m_width) + x); |
82 | } |
83 | |
84 | UCHAR GameData::HighlightData(short z, short y, short x) |
85 | { |
86 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
87 | || (z > m_depth - 1)) { |
88 | return 0; |
89 | } |
90 | |
91 | return Highlight.at((z * m_width * m_height) + (y * m_width) + x); |
92 | } |
93 | |
94 | void GameData::setHighlightData(short z, short y, short x, UCHAR value) |
95 | { |
96 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
97 | || (z > m_depth - 1)) { |
98 | return; |
99 | } |
100 | |
101 | Highlight[(z * m_width * m_height) + (y * m_width) + x] = value; |
102 | } |
103 | |
104 | UCHAR GameData::BoardData(short z, short y, short x) |
105 | { |
106 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
107 | || (z > m_depth - 1)) { |
108 | return 0; |
109 | } |
110 | |
111 | return Board.at((z * m_width * m_height) + (y * m_width) + x); |
112 | } |
113 | |
114 | void GameData::setBoardData(short z, short y, short x, UCHAR value) |
115 | { |
116 | if ((y < 0) || (x < 0) || (z < 0) || (y > m_height - 1) || (x > m_width - 1) |
117 | || (z > m_depth - 1)) { |
118 | return; |
119 | } |
120 | |
121 | Board[(z * m_width * m_height) + (y * m_width) + x] = value; |
122 | } |
123 | |
124 | POSITION& GameData::MoveListData(short i) |
125 | { |
126 | if ((i >= MoveList.size()) || (i < 0)) { |
127 | qDebug() << "Attempt to access GameData::MoveListData with invalid index" ; |
128 | i = 0 ; |
129 | } |
130 | |
131 | return MoveList[i]; |
132 | } |
133 | |
134 | void GameData::setMoveListData(short i, POSITION &value) |
135 | { |
136 | if ((i >= MoveList.size()) || (i < 0)) { |
137 | return ; |
138 | } |
139 | |
140 | MoveList[i] = value; |
141 | } |
142 | |
143 | void GameData::generateTilePositions() |
144 | { |
145 | // Generate the position data for the layout from contents of Game.Map. |
146 | |
147 | numTilesToGenerate = 0; |
148 | |
149 | for (int z = 0; z < m_depth; z++) { |
150 | for (int y = 0; y < m_height; y++) { |
151 | for (int x = 0; x < m_width; x++) { |
152 | setBoardData(z, y, x, 0); |
153 | if (MaskData(z,y,x) == '1') { |
154 | tilePositions[numTilesToGenerate].x = x; |
155 | tilePositions[numTilesToGenerate].y = y; |
156 | tilePositions[numTilesToGenerate].e = z; |
157 | tilePositions[numTilesToGenerate].f = 254; |
158 | numTilesToGenerate++; |
159 | } |
160 | } |
161 | } |
162 | } |
163 | } |
164 | |
165 | void GameData::generatePositionDepends() |
166 | { |
167 | // Generate the dependency data for the layout from the position data. |
168 | // Note that the coordinates of each tile in tilePositions are those of |
169 | // the upper left quarter of the tile. |
170 | |
171 | // For each tile, |
172 | for (int i = 0; i < numTilesToGenerate; i++) { |
173 | |
174 | // Get its basic position data |
175 | int x = tilePositions[i].x; |
176 | int y = tilePositions[i].y; |
177 | int z = tilePositions[i].e; |
178 | |
179 | // LHS dependencies |
180 | positionDepends[i].lhs_dep[0] = tileAt(x - 1, y, z); |
181 | positionDepends[i].lhs_dep[1] = tileAt(x - 1, y + 1, z); |
182 | |
183 | // Make them unique |
184 | if (positionDepends[i].lhs_dep[1] == positionDepends[i].lhs_dep[0]) { |
185 | positionDepends[i].lhs_dep[1] = -1; |
186 | } |
187 | |
188 | // RHS dependencies |
189 | positionDepends[i].rhs_dep[0] = tileAt(x + 2, y, z); |
190 | positionDepends[i].rhs_dep[1] = tileAt(x + 2, y + 1, z); |
191 | |
192 | // Make them unique |
193 | if (positionDepends[i].rhs_dep[1] == positionDepends[i].rhs_dep[0]) { |
194 | positionDepends[i].rhs_dep[1] = -1; |
195 | } |
196 | |
197 | // Turn dependencies |
198 | positionDepends[i].turn_dep[0] = tileAt(x, y, z + 1); |
199 | positionDepends[i].turn_dep[1] = tileAt(x + 1, y, z + 1); |
200 | positionDepends[i].turn_dep[2] = tileAt(x + 1, y + 1, z + 1); |
201 | positionDepends[i].turn_dep[3] = tileAt(x, y + 1, z + 1); |
202 | |
203 | // Make them unique |
204 | for (int j = 0; j < 3; j++) { |
205 | for (int k = j + 1; k < 4; k++) { |
206 | if (positionDepends[i].turn_dep[j] == positionDepends[i].turn_dep[k]) { |
207 | positionDepends[i].turn_dep[k] = -1; |
208 | } |
209 | } |
210 | } |
211 | |
212 | // Placement dependencies |
213 | positionDepends[i].place_dep[0] = tileAt(x, y, z - 1); |
214 | positionDepends[i].place_dep[1] = tileAt(x + 1, y, z - 1); |
215 | positionDepends[i].place_dep[2] = tileAt(x + 1, y + 1, z - 1); |
216 | positionDepends[i].place_dep[3] = tileAt(x, y + 1, z - 1); |
217 | |
218 | // Make them unique |
219 | for (int j = 0; j < 3; j++) { |
220 | for (int k = j + 1; k < 4; k++) { |
221 | if (positionDepends[i].place_dep[j] == positionDepends[i].place_dep[k]) { |
222 | positionDepends[i].place_dep[k] = -1; |
223 | } |
224 | } |
225 | } |
226 | |
227 | // Filled and free indicators. |
228 | positionDepends[i].filled = false; |
229 | positionDepends[i].free = false; |
230 | } |
231 | } |
232 | |
233 | int GameData::tileAt(int x, int y, int z) |
234 | { |
235 | // x, y, z are the coordinates of a *quarter* tile. This returns the |
236 | // index (in positions) of the tile at those coordinates or -1 if there |
237 | // is no tile at those coordinates. Note that the coordinates of each |
238 | // tile in positions are those of the upper left quarter of the tile. |
239 | |
240 | for (int i = 0; i < numTilesToGenerate; i++) { |
241 | if (tilePositions[i].e == z) { |
242 | if ((tilePositions[i].x == x && tilePositions[i].y == y) |
243 | || (tilePositions[i].x == x - 1 && tilePositions[i].y == y) |
244 | || (tilePositions[i].x == x - 1 && tilePositions[i].y == y - 1) |
245 | || (tilePositions[i].x == x && tilePositions[i].y == y - 1)) { |
246 | return i; |
247 | } |
248 | } |
249 | } |
250 | |
251 | return -1; |
252 | } |
253 | |
254 | bool GameData::generateSolvableGame() |
255 | { |
256 | // Initially we want to mark positions on layer 0 so that we have only |
257 | // one free position per apparent horizontal line. |
258 | for (int i = 0; i < numTilesToGenerate; i++) { |
259 | // Pick a random tile on layer 0 |
260 | int position, cnt = 0; |
261 | |
262 | do { |
263 | position = (int) random.getLong(numTilesToGenerate); |
264 | |
265 | if (cnt++ > (numTilesToGenerate*numTilesToGenerate)) { |
266 | return false; // bail |
267 | } |
268 | } while (tilePositions[position].e != 0); |
269 | |
270 | // If there are no other free positions on the same apparent |
271 | // horizontal line, we can mark that position as free. |
272 | if (onlyFreeInLine(position)) { |
273 | positionDepends[position].free = true; |
274 | } |
275 | } |
276 | |
277 | // Check to make sure we really got them all. Very important for |
278 | // this algorithm. |
279 | for (int i = 0; i < numTilesToGenerate; i++) { |
280 | if (tilePositions[i].e == 0 && onlyFreeInLine(i)) { |
281 | positionDepends[i].free = true; |
282 | } |
283 | } |
284 | |
285 | // Get ready to place the tiles |
286 | int lastPosition = -1; |
287 | int position = -1; |
288 | int position2 = -1; |
289 | |
290 | // For each position, |
291 | for (int i = 0; i < numTilesToGenerate; i++) { |
292 | |
293 | // If this is the first tile in a 144 tile set, |
294 | if ((i % 144) == 0) { |
295 | |
296 | // Initialise the faces to allocate. For the classic |
297 | // dragon board there are 144 tiles. So we allocate and |
298 | // randomise the assignment of 144 tiles. If there are > 144 |
299 | // tiles we will reallocate and re-randomise as we run out. |
300 | // One advantage of this method is that the pairs to assign are |
301 | // non-linear. In kmahjongg 0.4, If there were > 144 the same |
302 | // allocation series was followed. So 154 = 144 + 10 rods. |
303 | // 184 = 144 + 40 rods (20 pairs) which overwhemed the board |
304 | // with rods and made deadlock games more likely. |
305 | randomiseFaces(); |
306 | } |
307 | |
308 | // If this is the first half of a pair, there is no previous |
309 | // position for the pair. |
310 | if ((i & 1) == 0) { |
311 | lastPosition = -1; |
312 | } |
313 | |
314 | // Select a position for the tile, relative to the position of |
315 | // the last tile placed. |
316 | if ((position = selectPosition(lastPosition)) < 0) { |
317 | return false; // bail |
318 | } |
319 | |
320 | if (i < numTilesToGenerate - 1) { |
321 | if ((position2 = selectPosition(lastPosition)) < 0) { |
322 | return false; // bail |
323 | } |
324 | if (tilePositions[position2].e > tilePositions[position].e) { |
325 | position = position2; // higher is better |
326 | } |
327 | } |
328 | |
329 | // Place the tile. |
330 | placeTile(position, tilePair[i % 144]); |
331 | |
332 | // Remember the position |
333 | lastPosition = position; |
334 | } |
335 | |
336 | // The game is solvable. |
337 | return true; |
338 | } |
339 | |
340 | bool GameData::onlyFreeInLine(int position) |
341 | { |
342 | // Determines whether it is ok to mark this position as "free" because |
343 | // there are no other positions marked "free" in its apparent horizontal |
344 | // line. |
345 | |
346 | int i, i0, w; |
347 | int lin, rin, out; |
348 | //static int nextLeft[m_maxTiles]; |
349 | //static int nextRight[m_maxTiles]; |
350 | |
351 | QVector<int> nextLeft = QVector<int>(m_maxTiles, 0); |
352 | QVector<int> nextRight = QVector<int>(m_maxTiles, 0); |
353 | |
354 | /* Check left, starting at position */ |
355 | lin = 0; |
356 | out = 0; |
357 | nextLeft[lin++] = position; |
358 | |
359 | do { |
360 | w = nextLeft[out++]; |
361 | if (positionDepends[w].free || positionDepends[w].filled) { |
362 | return false; |
363 | } |
364 | |
365 | if ((i = positionDepends[w].lhs_dep[0]) != -1) { |
366 | nextLeft[lin++] = i; |
367 | } |
368 | |
369 | i0 = i; |
370 | |
371 | if ((i = positionDepends[w].lhs_dep[1]) != -1 && i0 != i) { |
372 | nextLeft[lin++] = i; |
373 | } |
374 | } while (lin > out); |
375 | |
376 | /* Check right, starting at position */ |
377 | rin = 0; |
378 | out = 0; |
379 | nextRight[rin++] = position; |
380 | |
381 | do { |
382 | w = nextRight[out++]; |
383 | |
384 | if (positionDepends[w].free || positionDepends[w].filled) { |
385 | return false; |
386 | } |
387 | |
388 | if ((i = positionDepends[w].rhs_dep[0]) != -1) { |
389 | nextRight[rin++] = i; |
390 | } |
391 | |
392 | i0 = i; |
393 | |
394 | if ((i = positionDepends[w].rhs_dep[1]) != -1 && i0 != i) { |
395 | nextRight[rin++] = i; |
396 | } |
397 | } while (rin > out); |
398 | |
399 | // Here, the position can be marked "free" |
400 | return true; |
401 | } |
402 | |
403 | int GameData::selectPosition(int lastPosition) |
404 | { |
405 | int position, cnt = 0; |
406 | bool goodPosition = false; |
407 | |
408 | // while a good position has not been found, |
409 | while (!goodPosition) { |
410 | |
411 | // Select a random, but free, position. |
412 | do { |
413 | position = random.getLong(numTilesToGenerate); |
414 | |
415 | if (cnt++ > (numTilesToGenerate*numTilesToGenerate)) { |
416 | return -1; // bail |
417 | } |
418 | } while (!positionDepends[position].free); |
419 | |
420 | // Found one. |
421 | goodPosition = true; |
422 | |
423 | // If there is a previous position to take into account, |
424 | if (lastPosition != -1) { |
425 | |
426 | // Check the new position against the last one. |
427 | for (int i = 0; i < 4; i++) { |
428 | if (positionDepends[position].place_dep[i] == lastPosition) { |
429 | goodPosition = false; // not such a good position |
430 | } |
431 | } |
432 | |
433 | for (int i = 0; i < 2; i++) { |
434 | if ((positionDepends[position].lhs_dep[i] == lastPosition) |
435 | || (positionDepends[position].rhs_dep[i] == lastPosition)) { |
436 | goodPosition = false; // not such a good position |
437 | } |
438 | } |
439 | } |
440 | } |
441 | |
442 | return position; |
443 | } |
444 | |
445 | void GameData::placeTile(int position, int tile) |
446 | { |
447 | // Install the tile in the specified position |
448 | tilePositions[position].f = tile; |
449 | putTile(tilePositions[position]); |
450 | |
451 | //added highlight? |
452 | //setHighlightData(E,Y,X,0); |
453 | |
454 | // Update position dependency data |
455 | positionDepends[position].filled = true; |
456 | positionDepends[position].free = false; |
457 | |
458 | // Now examine the tiles near this to see if this makes them "free". |
459 | int depend; |
460 | |
461 | for (int i = 0; i < 4; i++) { |
462 | if ((depend = positionDepends[position].turn_dep[i]) != -1) { |
463 | updateDepend(depend); |
464 | } |
465 | } |
466 | |
467 | for (int i = 0; i < 2; i++) { |
468 | if ((depend = positionDepends[position].lhs_dep[i]) != -1) { |
469 | updateDepend(depend); |
470 | } |
471 | |
472 | if ((depend = positionDepends[position].rhs_dep[i]) != -1) { |
473 | updateDepend(depend); |
474 | } |
475 | } |
476 | } |
477 | |
478 | void GameData::updateDepend(int position) |
479 | { |
480 | // Updates the free indicator in the dependency data for a position |
481 | // based on whether the positions on which it depends are filled. |
482 | |
483 | // If the position is valid and not filled |
484 | if (position >= 0 && !positionDepends[position].filled) { |
485 | |
486 | // Check placement depends. If they are not filled, the |
487 | // position cannot become free. |
488 | int depend; |
489 | for (int i = 0; i < 4; i++) { |
490 | if ((depend = positionDepends[position].place_dep[i]) != -1) { |
491 | if (!positionDepends[depend].filled) { |
492 | return ; |
493 | } |
494 | } |
495 | } |
496 | |
497 | // If position is first free on apparent horizontal, it is |
498 | // now free to be filled. |
499 | if (onlyFreeInLine(position)) { |
500 | positionDepends[position].free = true; |
501 | |
502 | return; |
503 | } |
504 | |
505 | // Assume no LHS positions to fill |
506 | bool lfilled = false; |
507 | |
508 | // If positions to LHS |
509 | if ((positionDepends[position].lhs_dep[0] != -1) |
510 | || (positionDepends[position].lhs_dep[1] != -1)) { |
511 | |
512 | // Assume LHS positions filled |
513 | lfilled = true; |
514 | |
515 | for (int i = 0; i < 2; i++) { |
516 | if ((depend = positionDepends[position].lhs_dep[i]) != -1) { |
517 | if (!positionDepends[depend].filled) { |
518 | lfilled = false; |
519 | } |
520 | } |
521 | } |
522 | } |
523 | |
524 | // Assume no RHS positions to fill |
525 | bool rfilled = false; |
526 | |
527 | // If positions to RHS |
528 | if ((positionDepends[position].rhs_dep[0] != -1) |
529 | || (positionDepends[position].rhs_dep[1] != -1)) { |
530 | |
531 | // Assume LHS positions filled |
532 | rfilled = true; |
533 | |
534 | for (int i = 0; i < 2; i++) { |
535 | if ((depend = positionDepends[position].rhs_dep[i]) != -1) { |
536 | if (!positionDepends[depend].filled) { |
537 | rfilled = false; |
538 | } |
539 | } |
540 | } |
541 | } |
542 | |
543 | // If positions to left or right are filled, this position |
544 | // is now free to be filled. |
545 | positionDepends[position].free = (lfilled || rfilled); |
546 | } |
547 | } |
548 | |
549 | bool GameData::generateStartPosition2() |
550 | { |
551 | // For each tile, |
552 | for (int i = 0; i < numTilesToGenerate; i++) { |
553 | |
554 | // Get its basic position data |
555 | int x = tilePositions[i].x; |
556 | int y = tilePositions[i].y; |
557 | int z = tilePositions[i].e; |
558 | |
559 | // Clear Game.Board at that position |
560 | setBoardData(z, y, x, 0); |
561 | |
562 | // Clear tile placed/free indicator(s). |
563 | positionDepends[i].filled = false; |
564 | positionDepends[i].free = false; |
565 | |
566 | // Set tile face blank |
567 | tilePositions[i].f = 254; |
568 | } |
569 | |
570 | // If solvable games should be generated, |
571 | // if (Prefs::solvableGames()) { |
572 | |
573 | if (generateSolvableGame()) { |
574 | TileNum = MaxTileNum; |
575 | return true; |
576 | } else { |
577 | return false; |
578 | } |
579 | |
580 | // } |
581 | |
582 | // Initialise the faces to allocate. For the classic |
583 | // dragon board there are 144 tiles. So we allocate and |
584 | // randomise the assignment of 144 tiles. If there are > 144 |
585 | // tiles we will reallocate and re-randomise as we run out. |
586 | // One advantage of this method is that the pairs to assign are |
587 | // non-linear. In kmahjongg 0.4, If there were > 144 the same |
588 | // allocation series was followed. So 154 = 144 + 10 rods. |
589 | // 184 = 144 + 40 rods (20 pairs) which overwhemed the board |
590 | // with rods and made deadlock games more likely. |
591 | |
592 | int remaining = numTilesToGenerate; |
593 | randomiseFaces(); |
594 | |
595 | for (int tile = 0; tile < numTilesToGenerate; tile += 2) { |
596 | int p1; |
597 | int p2; |
598 | |
599 | if (remaining > 2) { |
600 | p2 = p1 = random.getLong(remaining - 2); |
601 | int bail = 0; |
602 | |
603 | while (p1 == p2) { |
604 | p2 = random.getLong(remaining - 2); |
605 | |
606 | if (bail >= 100) { |
607 | if (p1 != p2) { |
608 | break; |
609 | } |
610 | } |
611 | |
612 | if ((tilePositions[p1].y == tilePositions[p2].y) |
613 | && (tilePositions[p1].e == tilePositions[p2].e)) { |
614 | // skip if on same y line |
615 | bail++; |
616 | p2=p1; |
617 | |
618 | continue; |
619 | } |
620 | } |
621 | } else { |
622 | p1 = 0; |
623 | p2 = 1; |
624 | } |
625 | |
626 | POSITION a; |
627 | POSITION b; |
628 | |
629 | a = tilePositions[p1]; |
630 | b = tilePositions[p2]; |
631 | |
632 | tilePositions[p1] = tilePositions[remaining - 1]; |
633 | tilePositions[p2] = tilePositions[remaining - 2]; |
634 | |
635 | remaining -= 2; |
636 | |
637 | getFaces(a, b); |
638 | putTile(a); |
639 | putTile(b); |
640 | } |
641 | |
642 | TileNum = MaxTileNum; |
643 | |
644 | return 1; |
645 | } |
646 | |
647 | void GameData::getFaces(POSITION &a, POSITION &b) |
648 | { |
649 | a.f = tilePair[tilesUsed]; |
650 | b.f = tilePair[tilesUsed + 1]; |
651 | tilesUsed += 2; |
652 | |
653 | if (tilesUsed >= 144) { |
654 | randomiseFaces(); |
655 | } |
656 | } |
657 | |
658 | void GameData::randomiseFaces() |
659 | { |
660 | int nr; |
661 | int numAlloced = 0; |
662 | // stick in 144 tiles in pairsa. |
663 | |
664 | for (nr = 0; nr < 9 * 4; ++nr) { |
665 | tilePair[numAlloced++] = TILE_CHARACTER + (nr / 4); // 4*9 Tiles |
666 | } |
667 | |
668 | for (nr = 0; nr < 9 * 4; ++nr) { |
669 | tilePair[numAlloced++] = TILE_BAMBOO + (nr / 4); // 4*9 Tiles |
670 | } |
671 | |
672 | for (nr = 0; nr < 9 * 4; ++nr) { |
673 | tilePair[numAlloced++] = TILE_ROD + (nr / 4); // 4*9 Tiles |
674 | } |
675 | |
676 | for (nr = 0; nr < 4; ++nr) { |
677 | tilePair[numAlloced++] = TILE_FLOWER + nr; // 4 Tiles |
678 | } |
679 | |
680 | for (nr = 0; nr < 4; ++nr) { |
681 | tilePair[numAlloced++] = TILE_SEASON + nr; // 4 Tiles |
682 | } |
683 | |
684 | for (nr = 0; nr < 4 * 4; ++nr) { |
685 | tilePair[numAlloced++] = TILE_WIND + (nr / 4); // 4*4 Tiles |
686 | } |
687 | |
688 | for (nr = 0; nr < 3 * 4; ++nr) { |
689 | tilePair[numAlloced++] = TILE_DRAGON + (nr / 4); // 3*4 Tiles |
690 | } |
691 | |
692 | //randomise. Keep pairs together. Ie take two random |
693 | //odd numbers (n,x) and swap n, n+1 with x, x+1 |
694 | |
695 | int at = 0; |
696 | int to = 0; |
697 | for (int r = 0; r < 200; r++) { |
698 | to = at; |
699 | |
700 | while (to==at) { |
701 | to = random.getLong(144); |
702 | |
703 | if ((to & 1) != 0) { |
704 | to--; |
705 | } |
706 | |
707 | } |
708 | |
709 | UCHAR tmp = tilePair[at]; |
710 | tilePair[at] = tilePair[to]; |
711 | tilePair[to] = tmp; |
712 | tmp = tilePair[at + 1]; |
713 | tilePair[at + 1] = tilePair[to + 1]; |
714 | tilePair[to + 1] = tmp; |
715 | |
716 | at += 2; |
717 | |
718 | if (at >= 144) { |
719 | at = 0; |
720 | } |
721 | } |
722 | |
723 | tilesAllocated = numAlloced; |
724 | tilesUsed = 0; |
725 | } |
726 | |
727 | bool isFlower(UCHAR Tile) |
728 | { |
729 | return (Tile >= TILE_FLOWER && Tile <=TILE_FLOWER + 3); |
730 | } |
731 | |
732 | bool isSeason(UCHAR Tile) |
733 | { |
734 | return (Tile >= TILE_SEASON && Tile <= TILE_SEASON + 3); |
735 | } |
736 | |
737 | bool isBamboo(UCHAR t) |
738 | { |
739 | return (t >= TILE_BAMBOO && t < TILE_BAMBOO + 9); |
740 | } |
741 | |
742 | bool isCharacter(UCHAR t) |
743 | { |
744 | return (t < TILE_CHARACTER + 9); |
745 | } |
746 | |
747 | bool isRod(UCHAR t) |
748 | { |
749 | return (t >= TILE_ROD && t < TILE_ROD + 9); |
750 | } |
751 | |
752 | bool isDragon(UCHAR t) |
753 | { |
754 | return (t >= TILE_DRAGON && t < TILE_DRAGON + 3); |
755 | } |
756 | |
757 | bool isWind(UCHAR t) |
758 | { |
759 | return (t >= TILE_WIND && t < TILE_WIND + 4); |
760 | } |
761 | |
762 | bool GameData::isMatchingTile(POSITION &Pos1, POSITION &Pos2) |
763 | { |
764 | // don't compare 'equal' positions |
765 | if (memcmp(&Pos1, &Pos2, sizeof(POSITION))) { |
766 | UCHAR FA = Pos1.f; |
767 | UCHAR FB = Pos2.f; |
768 | |
769 | if ((FA == FB) || (isFlower(FA) && isFlower(FB)) || (isSeason(FA) && isSeason(FB))) { |
770 | return true; |
771 | } |
772 | } |
773 | |
774 | return false; |
775 | } |
776 | |
777 | void GameData::setRemovedTilePair(POSITION &a, POSITION &b) |
778 | { |
779 | if (isFlower(a.f)) { |
780 | removedFlower[a.f - TILE_FLOWER]++; |
781 | removedFlower[b.f - TILE_FLOWER]++; |
782 | |
783 | return; |
784 | } |
785 | |
786 | if (isSeason(a.f)) { |
787 | removedSeason[a.f - TILE_SEASON]++; |
788 | removedSeason[b.f - TILE_SEASON]++; |
789 | |
790 | return; |
791 | } |
792 | |
793 | if (isCharacter(a.f)) { |
794 | removedCharacter[a.f - TILE_CHARACTER] += 2; |
795 | |
796 | return; |
797 | } |
798 | |
799 | if (isBamboo(a.f)) { |
800 | removedBamboo[a.f - TILE_BAMBOO] += 2; |
801 | |
802 | return; |
803 | } |
804 | |
805 | if (isRod(a.f)) { |
806 | removedRod[a.f - TILE_ROD] += 2; |
807 | |
808 | return; |
809 | } |
810 | |
811 | if (isDragon(a.f)){ |
812 | removedDragon[a.f - TILE_DRAGON] += 2; |
813 | |
814 | return; |
815 | } |
816 | |
817 | if (isWind(a.f)){ |
818 | removedWind[a.f - TILE_WIND] += 2; |
819 | |
820 | return; |
821 | } |
822 | } |
823 | |
824 | void GameData::clearRemovedTilePair(POSITION &a, POSITION &b) |
825 | { |
826 | if (isFlower(a.f)) { |
827 | removedFlower[a.f - TILE_FLOWER]--; |
828 | removedFlower[b.f - TILE_FLOWER]--; |
829 | |
830 | return; |
831 | } |
832 | |
833 | if (isSeason(a.f)) { |
834 | removedSeason[a.f - TILE_SEASON]--; |
835 | removedSeason[b.f - TILE_SEASON]--; |
836 | |
837 | return; |
838 | } |
839 | |
840 | if (isCharacter(a.f)) { |
841 | removedCharacter[a.f - TILE_CHARACTER] -= 2; |
842 | |
843 | return; |
844 | } |
845 | |
846 | if (isBamboo(a.f)) { |
847 | removedBamboo[a.f - TILE_BAMBOO] -= 2; |
848 | |
849 | return; |
850 | } |
851 | |
852 | if (isRod(a.f)) { |
853 | removedRod[a.f - TILE_ROD] -= 2; |
854 | |
855 | return; |
856 | } |
857 | |
858 | if (isDragon(a.f)) { |
859 | removedDragon[a.f - TILE_DRAGON] -= 2; |
860 | |
861 | return; |
862 | } |
863 | |
864 | if (isWind(a.f)) { |
865 | removedWind[a.f - TILE_WIND] -= 2; |
866 | |
867 | return; |
868 | } |
869 | } |
870 | |
871 | void GameData::initialiseRemovedTiles() |
872 | { |
873 | for (int pos = 0; pos < 9; pos++) { |
874 | removedCharacter[pos] = 0; |
875 | removedBamboo[pos] = 0; |
876 | removedRod[pos] = 0; |
877 | removedDragon[pos % 3] = 0; |
878 | removedFlower[pos % 4] = 0; |
879 | removedWind[pos % 4] = 0; |
880 | removedSeason[pos % 4] = 0; |
881 | } |
882 | } |
883 | |
884 | bool GameData::findMove(POSITION &posA, POSITION &posB) |
885 | { |
886 | short Pos_Ende = MaxTileNum; // Ende der PosTable |
887 | |
888 | for (short E = 0; E < m_depth; E++) { |
889 | for (short Y = 0; Y < m_height - 1; Y++) { |
890 | for (short X = 0; X < m_width - 1; X++) { |
891 | if (MaskData(E, Y, X) != (UCHAR) '1') { |
892 | continue; |
893 | } |
894 | |
895 | if (!BoardData(E, Y, X)) { |
896 | continue; |
897 | } |
898 | |
899 | if (E < m_depth - 1) { |
900 | if (BoardData(E + 1, Y, X) || BoardData(E + 1, Y + 1, X) |
901 | || BoardData(E + 1, Y, X + 1) || BoardData(E + 1, Y + 1, X + 1)) { |
902 | continue; |
903 | } |
904 | } |
905 | |
906 | if (X < m_width - 2 && (BoardData(E, Y, X - 1) || BoardData(E, Y + 1, X - 1)) |
907 | && (BoardData(E, Y, X + 2) || BoardData(E, Y + 1, X + 2))) { |
908 | continue; |
909 | } |
910 | |
911 | Pos_Ende--; |
912 | PosTable[Pos_Ende].e = E; |
913 | PosTable[Pos_Ende].y = Y; |
914 | PosTable[Pos_Ende].x = X; |
915 | PosTable[Pos_Ende].f = BoardData(E, Y, X); |
916 | } |
917 | } |
918 | } |
919 | |
920 | short iPosCount = 0; // Hier Anzahl der gefunden Paare merken |
921 | |
922 | // The new tile layout with non-contiguos horizantle spans |
923 | // can lead to huge numbers of matching pairs being exposed. |
924 | // we alter the loop to bail out when BoardLayout::maxTiles/2 pairs are found |
925 | // (or less); |
926 | while (Pos_Ende < MaxTileNum - 1 && iPosCount < m_maxTiles - 2) { |
927 | for (short Pos = Pos_Ende + 1; Pos < MaxTileNum; Pos++) { |
928 | if (isMatchingTile(PosTable[Pos], PosTable[Pos_Ende])) { |
929 | if (iPosCount < m_maxTiles - 2) { |
930 | PosTable[iPosCount++] = PosTable[Pos_Ende]; |
931 | PosTable[iPosCount++] = PosTable[Pos]; |
932 | } |
933 | } |
934 | } |
935 | |
936 | Pos_Ende++; |
937 | } |
938 | |
939 | if (iPosCount >= 2) { |
940 | random.setSeed(0); // WABA: Why is the seed reset? |
941 | short Pos = random.getLong(iPosCount) & -2; // Gerader Wert |
942 | posA = PosTable[Pos]; |
943 | posB = PosTable[Pos + 1]; |
944 | |
945 | return true; |
946 | } else { |
947 | return false; |
948 | } |
949 | } |
950 | |
951 | int GameData::moveCount() |
952 | { |
953 | short Pos_Ende = MaxTileNum; // end of PosTable |
954 | |
955 | for (short E = 0; E < m_depth; E++) { |
956 | for (short Y = 0; Y < m_height - 1; Y++) { |
957 | for (short X = 0; X < m_width - 1; X++) { |
958 | if (MaskData(E, Y, X) != (UCHAR) '1') { |
959 | continue; |
960 | } |
961 | |
962 | if (!BoardData(E, Y, X)) { |
963 | continue; |
964 | } |
965 | |
966 | if (E < m_depth - 1) { |
967 | if (BoardData(E + 1, Y, X) || BoardData(E + 1, Y + 1,X) |
968 | || BoardData(E + 1, Y, X + 1) || BoardData(E + 1, Y + 1, X + 1)) { |
969 | continue; |
970 | } |
971 | } |
972 | |
973 | if (X < m_width - 2 && (BoardData(E, Y, X - 1) || BoardData(E, Y + 1, X - 1)) |
974 | && (BoardData(E, Y, X + 2) || BoardData(E, Y + 1, X + 2))) { |
975 | continue; |
976 | } |
977 | |
978 | Pos_Ende--; |
979 | PosTable[Pos_Ende].e = E; |
980 | PosTable[Pos_Ende].y = Y; |
981 | PosTable[Pos_Ende].x = X; |
982 | PosTable[Pos_Ende].f = BoardData(E, Y, X); |
983 | } |
984 | } |
985 | } |
986 | |
987 | short iPosCount = 0; // store number of pairs found |
988 | |
989 | while (Pos_Ende < MaxTileNum - 1 && iPosCount < m_maxTiles - 2) { |
990 | for (short Pos = Pos_Ende + 1; Pos < MaxTileNum; Pos++) { |
991 | if (isMatchingTile(PosTable[Pos], PosTable[Pos_Ende])) { |
992 | if (iPosCount < m_maxTiles - 2) { |
993 | PosTable[iPosCount++] = PosTable[Pos_Ende]; |
994 | PosTable[iPosCount++] = PosTable[Pos]; |
995 | } |
996 | } |
997 | } |
998 | |
999 | Pos_Ende++; |
1000 | } |
1001 | |
1002 | return iPosCount / 2; |
1003 | } |
1004 | |
1005 | short GameData::findAllMatchingTiles(POSITION &posA) |
1006 | { |
1007 | short Pos = 0; |
1008 | |
1009 | for (short E = 0; E < m_depth; E++) { |
1010 | for (short Y = 0; Y < m_height - 1; Y++) { |
1011 | for (short X = 0; X < m_width - 1; X++) { |
1012 | if (MaskData(E, Y, X) != (UCHAR) '1') { |
1013 | continue; |
1014 | } |
1015 | |
1016 | if (!BoardData(E, Y, X)) { |
1017 | continue; |
1018 | } |
1019 | |
1020 | if (E < m_depth - 1) { |
1021 | if (BoardData(E + 1, Y, X) || BoardData(E + 1, Y + 1, X) |
1022 | || BoardData(E + 1, Y, X + 1) || BoardData(E + 1, Y + 1, X + 1)) { |
1023 | continue; |
1024 | } |
1025 | } |
1026 | |
1027 | if (X < m_width - 2 && (BoardData(E, Y, X - 1) || BoardData(E, Y + 1, X - 1)) |
1028 | && (BoardData(E, Y, X + 2) || BoardData(E, Y + 1, X + 2))) { |
1029 | continue; |
1030 | } |
1031 | |
1032 | PosTable[Pos].e = E; |
1033 | PosTable[Pos].y = Y; |
1034 | PosTable[Pos].x = X; |
1035 | PosTable[Pos].f = BoardData(E, Y, X); |
1036 | |
1037 | if (isMatchingTile(posA, PosTable[Pos])) { |
1038 | Pos++; |
1039 | } |
1040 | } |
1041 | } |
1042 | } |
1043 | |
1044 | return Pos; |
1045 | } |
1046 | |
1047 | bool GameData::loadFromStream(QDataStream &in) |
1048 | { |
1049 | in >> Board; |
1050 | in >> Mask; |
1051 | in >> Highlight; |
1052 | in >> allow_undo; |
1053 | in >> allow_redo; |
1054 | in >> TileNum; |
1055 | in >> MaxTileNum; |
1056 | |
1057 | //Read list count |
1058 | in >> m_maxTiles; |
1059 | |
1060 | //Reconstruct the MoveList |
1061 | for (int i = 0; i < m_maxTiles; ++i) { |
1062 | POSITION thispos; |
1063 | in >> thispos.e; |
1064 | in >> thispos.y; |
1065 | in >> thispos.x; |
1066 | in >> thispos.f; |
1067 | setMoveListData(i, thispos); |
1068 | } |
1069 | |
1070 | return true; |
1071 | } |
1072 | |
1073 | bool GameData::saveToStream(QDataStream &out) |
1074 | { |
1075 | out << Board; |
1076 | out << Mask; |
1077 | out << Highlight; |
1078 | out << allow_undo; |
1079 | out << allow_redo; |
1080 | out << TileNum; |
1081 | out << MaxTileNum; |
1082 | |
1083 | //write the size of our lists |
1084 | out << m_maxTiles; |
1085 | |
1086 | //and then write all position components for the MoveList |
1087 | for (int i = 0; i < m_maxTiles; ++i) { |
1088 | POSITION thispos = MoveList.at(i); |
1089 | out << (quint16) thispos.e; |
1090 | out << (quint16) thispos.y; |
1091 | out << (quint16) thispos.x; |
1092 | out << (quint16) thispos.f; |
1093 | } |
1094 | |
1095 | return true; |
1096 | } |
1097 | |
1098 | void GameData::shuffle() |
1099 | { |
1100 | int count = 0; |
1101 | |
1102 | // copy positions and faces of the remaining tiles into |
1103 | // the pos table |
1104 | for (int e = 0; e < m_depth; e++) { |
1105 | for (int y = 0; y < m_height; y++) { |
1106 | for (int x = 0; x < m_width; x++) { |
1107 | if (BoardData(e, y, x) && MaskData(e, y, x) == '1') { |
1108 | PosTable[count].e = e; |
1109 | PosTable[count].y = y; |
1110 | PosTable[count].x = x; |
1111 | PosTable[count].f = BoardData(e, y, x); |
1112 | count++; |
1113 | } |
1114 | } |
1115 | } |
1116 | } |
1117 | |
1118 | // now lets randomise the faces, selecting 400 pairs at random and |
1119 | // swapping the faces. |
1120 | for (int ran = 0; ran < 400; ran++) { |
1121 | int pos1 = random.getLong(count); |
1122 | int pos2 = random.getLong(count); |
1123 | |
1124 | if (pos1 == pos2) { |
1125 | continue; |
1126 | } |
1127 | |
1128 | BYTE f = PosTable[pos1].f; |
1129 | PosTable[pos1].f = PosTable[pos2].f; |
1130 | PosTable[pos2].f = f; |
1131 | } |
1132 | |
1133 | // put the rearranged tiles back. |
1134 | for (int p = 0; p < count; p++) { |
1135 | putTile(PosTable[p]); |
1136 | } |
1137 | } |
1138 | |