• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdegames API Reference
  • KDE Home
  • Contact Us
 

KShisen

  • sources
  • kde-4.14
  • kdegames
  • kshisen
  • src
board.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * KShisen - A japanese game similar to mahjongg *
3  * Copyright 1997 Mario Weilguni <mweilguni@sime.com> *
4  * Copyright 2002-2004 Dave Corrie <kde@davecorrie.com> *
5  * Copyright 2007 Mauricio Piacentini <mauricio@tabuleiro.com> *
6  * Copyright 2009-2012 Frederik Schwarzer <schwarzer@kde.org> *
7  * *
8  * This program is free software; you can redistribute it and/or modify *
9  * it under the terms of the GNU General Public License as published by *
10  * the Free Software Foundation; either version 2 of the License, or *
11  * (at your option) any later version. *
12  * *
13  * This program is distributed in the hope that it will be useful, *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16  * GNU General Public License for more details. *
17  * *
18  * You should have received a copy of the GNU General Public License *
19  * along with this program. If not, see <http://www.gnu.org/licenses/>. *
20  ***************************************************************************/
21 
22 #include "board.h"
23 #include "prefs.h"
24 
25 #include <kdebug.h>
26 #include <kglobalsettings.h>
27 #include <klocale.h>
28 #include <kstandarddirs.h>
29 
30 #include <QMouseEvent>
31 #include <QPainter>
32 #include <QTimer>
33 
34 #include <cstring>
35 
36 
37 #define EMPTY 0
38 #define SEASONS_START 28
39 #define FLOWERS_START 39
40 
41 static int s_delay[5] = {1000, 750, 500, 250, 125};
42 
43 bool PossibleMove::isInPath(int x, int y) const
44 {
45  if (x == m_path.last().x && y == m_path.last().y) {
46  return false;
47  }
48  kDebug() << "isInPath:" << x << "," << y;
49  Debug();
50  QList<Position>::const_iterator iter;
51  // a path has at least 2 positions
52  iter = m_path.constBegin();
53  int pathX = iter->x;
54  int pathY = iter->y;
55  ++iter;
56  for (; iter != m_path.constEnd(); ++iter) {
57  // to fix
58  if ((x == iter->x && ((y > pathY && y <= iter->y) || (y < pathY && y >= iter->y)))
59  || (y == iter->y && ((x > pathX && x <= iter->x) || (x < pathX && x >= iter->x)))) {
60  kDebug() << "isInPath:" << x << "," << y << "found in path" << pathX << "," << pathY << " => " << iter->x << "," << iter->y;
61  return true;
62  }
63  pathX = iter->x;
64  pathY = iter->y;
65  }
66  return false;
67 }
68 
69 Board::Board(QWidget *parent)
70  : QWidget(parent),
71  m_markX(0), m_markY(0),
72  m_field(0),
73  m_xTiles(0), m_yTiles(0),
74  m_delay(0), m_level(0), m_shuffle(0),
75  m_gameState(Normal), m_cheat(false),
76  m_gravityFlag(true), m_solvableFlag(false), m_chineseStyleFlag(false), m_tilesCanSlideFlag(false),
77  m_highlightedTile(-1),
78  m_paintConnection(false), m_paintPossibleMoves(false), m_paintInProgress(false),
79  m_soundPick(KStandardDirs::locate("sound", "kshisen/tile-touch.ogg")),
80  m_soundFall(KStandardDirs::locate("sound", "kshisen/tile-fall-tile.ogg"))
81 {
82  m_tileRemove1.first = -1;
83 
84  m_random.setSeed(0);
85  resetTimer();
86 
87  QPalette palette;
88  palette.setBrush(backgroundRole(), m_background.getBackground());
89  setPalette(palette);
90 
91  loadSettings();
92 }
93 
94 Board::~Board()
95 {
96  delete [] m_field;
97 }
98 
99 void Board::loadSettings()
100 {
101  if (!loadTileset(Prefs::tileSet())) {
102  kDebug() << "An error occurred when loading the tileset" << Prefs::tileSet() << "KShisen will continue with the default tileset.";
103  }
104 
105  // Load background
106  if (!loadBackground(Prefs::background())) {
107  kDebug() << "An error occurred when loading the background" << Prefs::background() << "KShisen will continue with the default background.";
108  }
109 
110  // There are tile sets, that have only one tile for e.g. the flowers group.
111  // If these tile sets are played in none-chineseStyle, this one tile face
112  // appears too often and not every tile matches another one with the same
113  // face because they are technically different (e.g different flowers).
114  // The solution is to enforce chineseStyle gameplay for tile sets that are
115  // known to be reduced. Those are Egypt and Alphabet for now.
116  if (Prefs::tileSet().endsWith(QLatin1String("egypt.desktop")) || Prefs::tileSet().endsWith(QLatin1String("alphabet.desktop"))) {
117  setChineseStyleFlag(true);
118  } else {
119  setChineseStyleFlag(Prefs::chineseStyle());
120  }
121  setTilesCanSlideFlag(Prefs::tilesCanSlide());
122  // Need to load solvable before size because setSize calls newGame which
123  // uses the solvable flag. Same with shuffle.
124  setSolvableFlag(Prefs::solvable());
125  m_shuffle = Prefs::level() * 4 + 1;
126  setSize(sizeX[Prefs::size()], sizeY[Prefs::size()]);
127  setGravityFlag(Prefs::gravity());
128  setDelay(s_delay[Prefs::speed()]);
129  setSoundsEnabled(Prefs::sounds());
130 
131  if (m_level != Prefs::level()) {
132  newGame();
133  }
134  m_level = Prefs::level();
135 }
136 
137 bool Board::loadTileset(const QString &pathToTileset)
138 {
139  if (m_tiles.loadTileset(pathToTileset)) {
140  if (m_tiles.loadGraphics()) {
141  Prefs::setTileSet(pathToTileset);
142  Prefs::self()->writeConfig();
143  resizeBoard();
144  }
145  return true;
146  }
147  //Try default
148  if (m_tiles.loadDefault()) {
149  if (m_tiles.loadGraphics()) {
150  Prefs::setTileSet(m_tiles.path());
151  Prefs::self()->writeConfig();
152  resizeBoard();
153  }
154  }
155  return false;
156 }
157 
158 bool Board::loadBackground(const QString &pathToBackground)
159 {
160  if (m_background.load(pathToBackground, width(), height())) {
161  if (m_background.loadGraphics()) {
162  Prefs::setBackground(pathToBackground);
163  Prefs::self()->writeConfig();
164  resizeBoard();
165  return true;
166  }
167  }
168  //Try default
169  if (m_background.loadDefault()) {
170  if (m_background.loadGraphics()) {
171  Prefs::setBackground(m_background.path());
172  Prefs::self()->writeConfig();
173  resizeBoard();
174  }
175  }
176  return false;
177 }
178 
179 int Board::xTiles() const
180 {
181  return m_xTiles;
182 }
183 
184 int Board::yTiles() const
185 {
186  return m_yTiles;
187 }
188 
189 void Board::setField(int x, int y, int value)
190 {
191  if (x < 0 || y < 0 || x >= xTiles() || y >= yTiles()) {
192  kFatal() << "Attempted write to invalid field position "
193  "(" << x << "," << y << ")";
194  }
195 
196  m_field[y * xTiles() + x] = value;
197 }
198 
199 // TODO: Why is this called every second?
200 int Board::field(int x, int y) const
201 {
202 #ifdef DEBUGGING
203  if (x < -1 || y < -1 || x > xTiles() || y > yTiles()) {
204  kFatal() << "Attempted read from invalid field position "
205  "(" << x << "," << y << ")";
206  }
207 #endif
208 
209  if (x < 0 || y < 0 || x >= xTiles() || y >= yTiles()) {
210  return EMPTY;
211  }
212 
213  return m_field[y * xTiles() + x];
214 }
215 
216 void Board::gravity(bool update)
217 {
218  m_gravCols.clear();
219  if (!m_gravityFlag) {
220  return;
221  }
222  bool fallingTiles = false;
223  for (int i = 0; i < xTiles(); ++i) {
224  if (gravity(i, update)) {
225  fallingTiles = true;
226  m_gravCols.append(i);
227  }
228  }
229  if (Prefs::sounds() && fallingTiles) {
230  m_soundFall.start();
231  }
232 }
233 
234 bool Board::gravity(int column, bool update)
235 {
236  bool isAffected = false;
237  if (m_gravityFlag) {
238  int rptr = yTiles() - 1;
239  int wptr = yTiles() - 1;
240  while (rptr >= 0) {
241  if (field(column, wptr) != EMPTY) {
242  --rptr;
243  --wptr;
244  } else {
245  if (field(column, rptr) != EMPTY) {
246  setField(column, wptr, field(column, rptr));
247  setField(column, rptr, EMPTY);
248  isAffected = true;
249  if (update) {
250  updateField(column, rptr);
251  updateField(column, wptr);
252  }
253  --wptr;
254  --rptr;
255  } else {
256  --rptr;
257  }
258  }
259  }
260  }
261  return isAffected;
262 }
263 
264 void Board::unmarkTile()
265 {
266  // if nothing is marked, nothing to do
267  if (m_markX == -1 || m_markY == -1) {
268  return;
269  }
270  drawPossibleMoves(false);
271  m_possibleMoves.clear();
272  // We need to set m_markX and m_markY to -1 before calling
273  // updateField() to ensure the tile is redrawn as unmarked.
274  int oldMarkX = m_markX;
275  int oldMarkY = m_markY;
276  m_markX = -1;
277  m_markY = -1;
278  updateField(oldMarkX, oldMarkY);
279 }
280 
281 void Board::mousePressEvent(QMouseEvent *e)
282 {
283  // Do not process mouse events while the connection is drawn.
284  // Clicking on one of the already connected tiles would have selected
285  // it before removing it. This is more a workaround than a proper fix
286  // but I have to understand the usage of m_paintConnection first in
287  // order to consider its reusage here. (schwarzer)
288  if (m_paintInProgress) {
289  return;
290  }
291  switch (m_gameState) {
292  case Normal:
293  break;
294  case Over:
295  newGame();
296  return;
297  case Paused:
298  setPauseEnabled(false);
299  return;
300  case Stuck:
301  return;
302  }
303  // Calculate field position
304  int posX = (e->pos().x() - xOffset()) / (m_tiles.qWidth() * 2);
305  int posY = (e->pos().y() - yOffset()) / (m_tiles.qHeight() * 2);
306 
307  if (e->pos().x() < xOffset() || e->pos().y() < yOffset() ||
308  posX >= xTiles() || posY >= yTiles()) {
309  posX = -1;
310  posY = -1;
311  }
312 
313  // Mark tile
314  if (e->button() == Qt::LeftButton) {
315  clearHighlight();
316 
317  if (posX != -1) {
318  marked(posX, posY);
319  } else {
320  // unmark when clicking outside the board
321  unmarkTile();
322  }
323  }
324 
325  // Assist by highlighting all tiles of same type
326  if (e->button() == Qt::RightButton) {
327  int clickedTile = field(posX, posY);
328 
329  // Clear marked tile
330  if (m_markX != -1 && field(m_markX, m_markY) != clickedTile) {
331  unmarkTile();
332  } else {
333  m_markX = -1;
334  m_markY = -1;
335  }
336 
337  // Perform highlighting
338  if (clickedTile != m_highlightedTile) {
339  int oldHighlighted = m_highlightedTile;
340  m_highlightedTile = clickedTile;
341  for (int i = 0; i < xTiles(); ++i) {
342  for (int j = 0; j < yTiles(); ++j) {
343  const int fieldTile = field(i, j);
344  if (fieldTile != EMPTY) {
345  if (fieldTile == oldHighlighted) {
346  updateField(i, j);
347  } else if (fieldTile == clickedTile) {
348  updateField(i, j);
349  } else if (m_chineseStyleFlag) {
350  if (clickedTile >= SEASONS_START && clickedTile <= (SEASONS_START + 3) && fieldTile >= SEASONS_START && fieldTile <= (SEASONS_START + 3)) {
351  updateField(i, j);
352  } else if (clickedTile >= FLOWERS_START && clickedTile <= (FLOWERS_START + 3) && fieldTile >= FLOWERS_START && fieldTile <= (FLOWERS_START + 3)) {
353  updateField(i, j);
354  }
355  // oldHighlighted
356  if (oldHighlighted >= SEASONS_START && oldHighlighted <= (SEASONS_START + 3) && fieldTile >= SEASONS_START && fieldTile <= (SEASONS_START + 3)) {
357  updateField(i, j);
358  } else if (oldHighlighted >= FLOWERS_START && oldHighlighted <= (FLOWERS_START + 3) && fieldTile >= FLOWERS_START && fieldTile <= (FLOWERS_START + 3)) {
359  updateField(i, j);
360  }
361  }
362  }
363  }
364  }
365  }
366  }
367 }
368 
369 int Board::xOffset() const
370 {
371  int tw = m_tiles.qWidth() * 2;
372  return (width() - (tw * xTiles())) / 2;
373 }
374 
375 int Board::yOffset() const
376 {
377  int th = m_tiles.qHeight() * 2;
378  return (height() - (th * yTiles())) / 2;
379 }
380 
381 void Board::setSize(int x, int y)
382 {
383  if (x == xTiles() && y == yTiles()) {
384  return;
385  }
386 
387  delete [] m_field;
388 
389  m_field = new int[ x * y ];
390  m_xTiles = x;
391  m_yTiles = y;
392  for (int i = 0; i < x; ++i) {
393  for (int j = 0; j < y; ++j) {
394  setField(i, j, EMPTY);
395  }
396  }
397 
398  // set the minimum size of the scalable window
399  const double MINIMUM_SCALE = 0.2;
400  int w = qRound(m_tiles.qWidth() * 2.0 * MINIMUM_SCALE) * xTiles();
401  int h = qRound(m_tiles.qHeight() * 2.0 * MINIMUM_SCALE) * yTiles();
402  w += m_tiles.width();
403  h += m_tiles.width();
404 
405  setMinimumSize(w, h);
406 
407  resizeBoard();
408  newGame();
409  emit changed();
410 }
411 
412 void Board::resizeEvent(QResizeEvent *e)
413 {
414  kDebug() << "[resizeEvent]";
415  if (e->spontaneous()) {
416  kDebug() << "[resizeEvent] spontaneous";
417  }
418  resizeBoard();
419  emit resized();
420 }
421 
422 void Board::resizeBoard()
423 {
424  // calculate tile size required to fit all tiles in the window
425  QSize newsize = m_tiles.preferredTileSize(QSize(width(), height()), xTiles(), yTiles());
426  m_tiles.reloadTileset(newsize);
427  //recalculate bg, if needed
428  m_background.sizeChanged(width(), height());
429  //reload our bg brush, using the cache in libkmahjongg if possible
430  QPalette palette;
431  palette.setBrush(backgroundRole(), m_background.getBackground());
432  setPalette(palette);
433 }
434 
435 
436 void Board::newGame()
437 {
438  m_gameState = Normal;
439  setCheatModeEnabled(false);
440 
441  m_markX = -1;
442  m_markY = -1;
443  m_highlightedTile = -1; // will clear previous highlight
444 
445  resetUndo();
446  resetRedo();
447  m_connection.clear();
448  m_possibleMoves.clear();
449 
450  // distribute all tiles on board
451  int curTile = 1;
452  int tileCount = 0;
453 
454  /*
455  * Note by jwickers: i changed the way to distribute tiles
456  * in chinese mahjongg there are 4 tiles of each
457  * except flowers and seasons (4 flowers and 4 seasons,
458  * but one unique tile of each, that is why they are
459  * the only ones numbered)
460  * That uses the chineseStyle flag
461  */
462  for (int y = 0; y < yTiles(); ++y) {
463  for (int x = 0; x < xTiles(); ++x) {
464  // do not duplicate flowers or seasons
465  if (!m_chineseStyleFlag || !((curTile >= SEASONS_START && curTile <= (SEASONS_START + 3)) || (curTile >= FLOWERS_START && curTile <= (FLOWERS_START + 3)))) {
466  setField(x, y, curTile);
467  if (++tileCount >= 4) {
468  tileCount = 0;
469  ++curTile;
470  }
471  } else {
472  tileCount = 0;
473  setField(x, y, curTile++);
474  }
475  if (curTile > Board::nTiles) {
476  curTile = 1;
477  }
478  }
479  }
480 
481  if (m_shuffle == 0) {
482  update();
483  resetTimer();
484  emit newGameStarted();
485  emit changed();
486  return;
487  }
488 
489  // shuffle the field
490  int tx = xTiles();
491  int ty = yTiles();
492  for (int i = 0; i < tx * ty * m_shuffle; ++i) {
493  int x1 = m_random.getLong(tx);
494  int y1 = m_random.getLong(ty);
495  int x2 = m_random.getLong(tx);
496  int y2 = m_random.getLong(ty);
497  // keep and use t, because the next setField() call changes what field() will return
498  // so there would a significant impact on shuffling with the field() call put into the
499  // place where 't' is used
500  int t = field(x1, y1);
501  setField(x1, y1, field(x2, y2));
502  setField(x2, y2, t);
503  }
504 
505  // if m_solvableFlag is false, the game does not need to be solvable; we can drop out here
506  if (!m_solvableFlag) {
507  update();
508  resetTimer();
509  emit newGameStarted();
510  emit changed();
511  return;
512  }
513 
514 
515  int fsize = xTiles() * yTiles() * sizeof(int);
516  int *oldfield = new int[xTiles() * yTiles()];
517  memcpy(oldfield, m_field, fsize); // save field
518  int *tiles = new int[xTiles() * yTiles()];
519  int *pos = new int[xTiles() * yTiles()];
520  //jwickers: in case the game cannot made solvable we do not want to run an infinite loop
521  int maxAttempts = 200;
522 
523  while (!solvable(true) && maxAttempts > 0) {
524  // generate a list of free tiles and positions
525  int numberOfTiles = 0;
526  for (int i = 0; i < xTiles() * yTiles(); ++i) {
527  if (m_field[i] != EMPTY) {
528  pos[numberOfTiles] = i;
529  tiles[numberOfTiles] = m_field[i];
530  ++numberOfTiles;
531  }
532  }
533 
534  // restore field
535  memcpy(m_field, oldfield, fsize);
536 
537  // redistribute unsolved tiles
538  while (numberOfTiles > 0) {
539  // get a random tile
540  int r1 = m_random.getLong(numberOfTiles);
541  int r2 = m_random.getLong(numberOfTiles);
542  int tile = tiles[r1];
543  int apos = pos[r2];
544 
545  // truncate list
546  tiles[r1] = tiles[numberOfTiles-1];
547  pos[r2] = pos[numberOfTiles-1];
548  --numberOfTiles;
549 
550  // put this tile on the new position
551  m_field[apos] = tile;
552  }
553 
554  // remember field
555  memcpy(oldfield, m_field, fsize);
556  --maxAttempts;
557  }
558  // debug, tell if make solvable failed
559  if (maxAttempts == 0) {
560  kDebug() << "NewGame make solvable failed";
561  }
562 
563 
564  // restore field
565  memcpy(m_field, oldfield, fsize);
566  delete [] tiles;
567  delete [] pos;
568  delete [] oldfield;
569 
570  update();
571  resetTimer();
572  emit changed();
573 }
574 
575 bool Board::tilesMatch(int tile1, int tile2) const
576 {
577  // identical tiles always match
578  if (tile1 == tile2) {
579  return true;
580  }
581  // when chinese style is set, there are special rules
582  // for flowers and seasons
583  if (m_chineseStyleFlag) {
584  // if both tiles are seasons
585  if (tile1 >= SEASONS_START && tile1 <= SEASONS_START + 3
586  && tile2 >= SEASONS_START && tile2 <= SEASONS_START + 3) {
587  return true;
588  }
589  // if both tiles are flowers
590  if (tile1 >= FLOWERS_START && tile1 <= FLOWERS_START + 3
591  && tile2 >= FLOWERS_START && tile2 <= FLOWERS_START + 3) {
592  return true;
593  }
594  }
595  return false;
596 }
597 
598 bool Board::isTileHighlighted(int x, int y) const
599 {
600  if (x == m_markX && y == m_markY) {
601  return true;
602  }
603 
604  if (tilesMatch(m_highlightedTile, field(x, y))) {
605  return true;
606  }
607 
608  // m_tileRemove1.first != -1 is used because the repaint of the first if
609  // on undrawConnection highlighted the tiles that fell because of gravity
610  if (!m_connection.isEmpty() && m_tileRemove1.first != -1) {
611  if (x == m_connection.first().x && y == m_connection.first().y) {
612  return true;
613  }
614 
615  if (x == m_connection.last().x && y == m_connection.last().y) {
616  return true;
617  }
618  }
619 
620  return false;
621 }
622 
623 void Board::updateField(int x, int y)
624 {
625  QRect r(xOffset() + x * m_tiles.qWidth() * 2,
626  yOffset() + y * m_tiles.qHeight() * 2,
627  m_tiles.width(),
628  m_tiles.height());
629 
630  update(r);
631 }
632 
633 void Board::showInfoRect(QPainter &p, const QString &message)
634 {
635  int boxWidth = width() * 0.6;
636  int boxHeight = height() * 0.6;
637  QRect contentsRect = QRect((width() - boxWidth) / 2, (height() - boxHeight) / 2, boxWidth, boxHeight);
638  QFont font;
639  int fontsize = boxHeight / 13;
640  font.setPointSize(fontsize);
641  p.setFont(font);
642  p.setBrush(QBrush(QColor(100, 100, 100, 150)));
643  p.setRenderHint(QPainter::Antialiasing);
644  p.drawRoundedRect(contentsRect, 10, 10);
645 
646  p.drawText(contentsRect, Qt::AlignCenter | Qt::TextWordWrap, message);
647 }
648 
649 void Board::drawTiles(QPainter &p, QPaintEvent *e)
650 {
651  int w = m_tiles.width();
652  int h = m_tiles.height();
653  int fw = m_tiles.qWidth() * 2;
654  int fh = m_tiles.qHeight() * 2;
655  for (int i = 0; i < xTiles(); ++i) {
656  for (int j = 0; j < yTiles(); ++j) {
657  int tile = field(i, j);
658  if (tile == EMPTY) {
659  continue;
660  }
661 
662  int xpos = xOffset() + i * fw;
663  int ypos = yOffset() + j * fh;
664  QRect r(xpos, ypos, w, h);
665  if (e->rect().intersects(r)) {
666  if (isTileHighlighted(i, j)) {
667  p.drawPixmap(xpos, ypos, m_tiles.selectedTile(1));
668  } else {
669  p.drawPixmap(xpos, ypos, m_tiles.unselectedTile(1));
670  }
671 
672  //draw face
673  p.drawPixmap(xpos, ypos, m_tiles.tileface(tile - 1));
674  }
675  }
676  }
677 }
678 
679 void Board::paintEvent(QPaintEvent *e)
680 {
681  QRect ur = e->rect(); // rectangle to update
682  QPainter p(this);
683  p.fillRect(ur, m_background.getBackground());
684 
685  switch (m_gameState) {
686  case Normal:
687  drawTiles(p, e);
688  break;
689  case Paused:
690  showInfoRect(p, i18n("Game Paused\nClick to resume game."));
691  break;
692  case Stuck:
693  drawTiles(p, e);
694  showInfoRect(p, i18n("Game Stuck\nNo more moves possible."));
695  break;
696  case Over:
697  showInfoRect(p, i18n("Game Over\nClick to start a new game."));
698  break;
699  }
700 
701  if (m_paintConnection) {
702  p.setPen(QPen(QColor("red"), lineWidth()));
703 
704  Path::const_iterator pt1 = m_connection.constBegin();
705  Path::const_iterator pt2 = pt1 + 1;
706  while (pt2 != m_connection.constEnd()) {
707  p.drawLine(midCoord(pt1->x, pt1->y), midCoord(pt2->x, pt2->y));
708  ++pt1;
709  ++pt2;
710  }
711  QTimer::singleShot(delay(), this, SLOT(undrawConnection()));
712  m_paintConnection = false;
713  }
714  if (m_paintPossibleMoves) {
715  p.setPen(QPen(QColor("blue"), lineWidth()));
716  // paint all possible moves
717  for (QList<PossibleMove>::const_iterator iter = m_possibleMoves.constBegin(); iter != m_possibleMoves.constEnd(); ++iter) {
718  Path::const_iterator pt1 = iter->m_path.constBegin();
719  Path::const_iterator pt2 = pt1 + 1;
720  while (pt2 != iter->m_path.constEnd()) {
721  p.drawLine(midCoord(pt1->x, pt1->y), midCoord(pt2->x, pt2->y));
722  ++pt1;
723  ++pt2;
724  }
725  }
726  m_paintConnection = false;
727  }
728  p.end();
729 }
730 
731 void Board::reverseSlide(int x, int y, int slideX1, int slideY1, int slideX2, int slideY2)
732 {
733  // slide[XY]2 is the current location of the last tile to slide
734  // slide[XY]1 is its destination
735  // calculate the offset for the tiles to slide
736  int dx = slideX1 - slideX2;
737  int dy = slideY1 - slideY2;
738  int current_tile;
739  // move all tiles between slideX2, slideY2 and x, y to slide with that offset
740  if (dx == 0) {
741  if (y < slideY2) {
742  for (int i = y + 1; i <= slideY2; ++i) {
743  current_tile = field(x, i);
744  if (current_tile == EMPTY) {
745  continue;
746  }
747  setField(x, i, EMPTY);
748  setField(x, i + dy, current_tile);
749  updateField(x, i);
750  updateField(x, i + dy);
751  }
752  } else {
753  for (int i = y - 1; i >= slideY2; --i) {
754  current_tile = field(x, i);
755  if (current_tile == EMPTY) {
756  continue;
757  }
758  setField(x, i, EMPTY);
759  setField(x, i + dy, current_tile);
760  updateField(x, i);
761  updateField(x, i + dy);
762  }
763  }
764  } else if (dy == 0) {
765  if (x < slideX2) {
766  for (int i = x + 1; i <= slideX2; ++i) {
767  current_tile = field(i, y);
768  if (current_tile == EMPTY) {
769  continue;
770  }
771  setField(i, y, EMPTY);
772  setField(i + dx, y, current_tile);
773  updateField(i, y);
774  updateField(i + dx, y);
775  }
776  } else {
777  for (int i = x - 1; i >= slideX2; --i) {
778  current_tile = field(i, y);
779  if (current_tile == EMPTY) {
780  continue;
781  }
782  setField(i, y, EMPTY);
783  setField(i + dx, y, current_tile);
784  updateField(i, y);
785  updateField(i + dx, y);
786  }
787  }
788  }
789 }
790 
791 void Board::performSlide(int x, int y, Path &slide)
792 {
793  // check if there is something to slide
794  if (slide.isEmpty()) {
795  return;
796  }
797 
798  // slide.first is the current location of the last tile to slide
799  // slide.last is its destination
800  // calculate the offset for the tiles to slide
801  int dx = slide.last().x - slide.first().x;
802  int dy = slide.last().y - slide.first().y;
803  int current_tile;
804  // move all tiles between m_markX, m_markY and the last tile to slide with that offset
805  if (dx == 0) {
806  if (y < slide.first().y) {
807  for (int i = slide.first().y; i > y; --i) {
808  current_tile = field(x, i);
809  setField(x, i, EMPTY);
810  setField(x, i + dy, current_tile);
811  updateField(x, i);
812  updateField(x, i + dy);
813  }
814  } else {
815  for (int i = slide.first().y; i < y; ++i) {
816  current_tile = field(x, i);
817  setField(x, i, EMPTY);
818  setField(x, i + dy, current_tile);
819  updateField(x, i);
820  updateField(x, i + dy);
821  }
822  }
823  } else if (dy == 0) {
824  if (x < slide.first().x) {
825  for (int i = slide.first().x; i > x; --i) {
826  current_tile = field(i, y);
827  setField(i, y, EMPTY);
828  setField(i + dx, y, current_tile);
829  updateField(i, y);
830  updateField(i + dx, y);
831  }
832  } else {
833  for (int i = slide.first().x; i < x; ++i) {
834  current_tile = field(i, y);
835  setField(i, y, EMPTY);
836  setField(i + dx, y, current_tile);
837  updateField(i, y);
838  updateField(i + dx, y);
839  }
840  }
841  }
842 }
843 
844 void Board::performMove(PossibleMove &possibleMoves)
845 {
846  m_connection = possibleMoves.m_path;
847 #ifdef DEBUGGING
848  // DEBUG undo, save board state
849  int fsize = xTiles() * yTiles() * sizeof(int);
850  int *saved1 = new int[xTiles() * yTiles()];
851  memcpy(saved1, m_field, fsize);
852 #endif
853  // if the tiles can slide, we have to update the slided tiles too
854  // and store the slide in a Move
855  if (possibleMoves.m_hasSlide) {
856  performSlide(m_markX, m_markY, possibleMoves.m_slide);
857  madeMove(m_markX, m_markY, possibleMoves.m_path.last().x, possibleMoves.m_path.last().y, possibleMoves.m_slide);
858  } else {
859  madeMove(m_markX, m_markY, possibleMoves.m_path.last().x, possibleMoves.m_path.last().y);
860  }
861  drawPossibleMoves(false);
862  drawConnection();
863  m_tileRemove1 = QPair<int, int>(m_markX, m_markY);
864  m_tileRemove2 = QPair<int, int>(possibleMoves.m_path.last().x, possibleMoves.m_path.last().y);
865  m_markX = -1;
866  m_markY = -1;
867  m_possibleMoves.clear();
868 #ifdef DEBUGGING
869  // DEBUG undo, force gravity
870  undrawConnection();
871  // DEBUG undo, save board2 state
872  int *saved2 = new int[xTiles() * yTiles()];
873  int *saved3 = new int[xTiles() * yTiles()]; // after undo
874  int *saved4 = new int[xTiles() * yTiles()]; // after redo
875  memcpy(saved2, m_field, fsize);
876  // DEBUG undo, undo move
877  bool errorFound = false;
878  if (canUndo()) {
879  undo();
880  // DEBUG undo, compare to saved board state
881  for (int i = 0; i < xTiles() * yTiles(); ++i) {
882  if (saved1[i] != m_field[i]) {
883  kDebug() << "[DEBUG Undo 1], tile (" << i << ") was" << saved1[i] << "before more, it is" << m_field[i] << "after undo.";
884  errorFound = true;
885  }
886  }
887  // DEBUG undo, save board state
888  memcpy(saved3, m_field, fsize);
889  // DEBUG undo, redo
890  if (canRedo()) {
891  redo();
892  undrawConnection();
893  // DEBUG undo, compare to saved board2 state
894  for (int i = 0; i < xTiles() * yTiles(); ++i) {
895  if (saved2[i] != m_field[i]) {
896  kDebug() << "[DEBUG Undo 2], tile (" << i << ") was" << saved2[i] << "after more, it is" << m_field[i] << "after redo.";
897  errorFound = true;
898  }
899  }
900  // DEBUG undo, save board state
901  memcpy(saved4, m_field, fsize);
902  }
903  }
904  // dumpBoard on error
905  if (errorFound) {
906  kDebug() << "[DEBUG] Before move";
907  dumpBoard(saved1);
908  kDebug() << "[DEBUG] After move";
909  dumpBoard(saved2);
910  kDebug() << "[DEBUG] Undo";
911  dumpBoard(saved3);
912  kDebug() << "[DEBUG] Redo";
913  dumpBoard(saved4);
914  }
915 
916  // DEBUG undo, free saved boards
917  delete[] saved1;
918  delete[] saved2;
919  delete[] saved3;
920  delete[] saved4;
921 #endif
922 }
923 
924 void Board::marked(int x, int y)
925 {
926  if (field(x, y) == EMPTY) { // click on empty space on the board
927  if (m_possibleMoves.count() > 1) { // if the click is on any of the current possible moves, make that move
928  for (QList<PossibleMove>::iterator iter = m_possibleMoves.begin(); iter != m_possibleMoves.end(); ++iter) {
929  if (iter->isInPath(x, y)) {
930  performMove(*iter);
931  emit selectATile();
932  return;
933  }
934  }
935  } else {
936  // unmark when not clicking on a tile
937  unmarkTile();
938  return;
939  }
940  }
941  // make sure that the previous connection is correctly undrawn
942  undrawConnection(); // is this still needed? (schwarzer)
943 
944  if (Prefs::sounds()) {
945  m_soundPick.start();
946  }
947 
948  if (x == m_markX && y == m_markY) { // the piece is already marked
949  // unmark the piece
950  unmarkTile();
951  emit selectATile();
952  return;
953  }
954 
955  if (m_markX == -1) { // nothing is selected so far
956  m_markX = x;
957  m_markY = y;
958  drawPossibleMoves(false);
959  m_possibleMoves.clear();
960  updateField(x, y);
961  emit selectAMatchingTile();
962  return;
963  } else if (m_possibleMoves.count() > 1) { // if the click is on any of the current possible moves, make that move
964 
965  for (QList<PossibleMove>::iterator iter = m_possibleMoves.begin(); iter != m_possibleMoves.end(); ++iter) {
966  if (iter->isInPath(x, y)) {
967  performMove(*iter);
968  emit selectATile();
969  return;
970  }
971  }
972  }
973 
974  int tile1 = field(m_markX, m_markY);
975  int tile2 = field(x, y);
976 
977  // both tiles do not match
978  if (!tilesMatch(tile1, tile2)) {
979  unmarkTile();
980  emit tilesDoNotMatch();
981  return;
982  }
983 
984  // trace and perform the move and get the list of possible moves
985  if (findPath(m_markX, m_markY, x, y, m_possibleMoves) > 0) {
986  if (m_possibleMoves.count() > 1) {
987  int withSlide = 0;
988  for (QList<PossibleMove>::const_iterator iter = m_possibleMoves.constBegin(); iter != m_possibleMoves.constEnd(); ++iter) {
989  iter->Debug();
990  if (iter->m_hasSlide) {
991  ++withSlide;
992  }
993  }
994  // if all moves have no slide, it doesn't matter
995  if (withSlide > 0) {
996  drawPossibleMoves(true);
997  emit selectAMove();
998  return;
999  }
1000  }
1001 
1002  // only one move possible, perform it
1003  performMove(m_possibleMoves.first());
1004  emit selectATile();
1005  // game is over?
1006  // Must delay until after tiles fall to make this test
1007  // See undrawConnection GP.
1008  } else {
1009  emit invalidMove();
1010  m_connection.clear();
1011  }
1012 }
1013 
1014 
1015 void Board::clearHighlight()
1016 {
1017  if (m_highlightedTile == -1) {
1018  return;
1019  }
1020  int oldHighlighted = m_highlightedTile;
1021  m_highlightedTile = -1;
1022 
1023  for (int i = 0; i < xTiles(); ++i) {
1024  for (int j = 0; j < yTiles(); ++j) {
1025  if (tilesMatch(oldHighlighted, field(i, j))) {
1026  updateField(i, j);
1027  }
1028  }
1029  }
1030 }
1031 
1032 bool Board::canMakePath(int x1, int y1, int x2, int y2) const
1033 {
1034  if (x1 == x2) {
1035  for (int i = qMin(y1, y2) + 1; i < qMax(y1, y2); ++i) {
1036  if (field(x1, i) != EMPTY) {
1037  return false;
1038  }
1039  }
1040  return true;
1041  }
1042 
1043  if (y1 == y2) {
1044  for (int i = qMin(x1, x2) + 1; i < qMax(x1, x2); ++i) {
1045  if (field(i, y1) != EMPTY) {
1046  return false;
1047  }
1048  }
1049  return true;
1050  }
1051 
1052  return false;
1053 }
1054 
1055 bool Board::canSlideTiles(int x1, int y1, int x2, int y2, Path &path) const
1056 {
1057  int distance = -1;
1058  path.clear();
1059  if (x1 == x2) {
1060  if (y1 > y2) {
1061  distance = y1 - y2;
1062  // count how much free space we have for sliding
1063  int start_free = -1;
1064  int end_free = -1;
1065  // find first tile empty
1066  for (int i = y1 - 1; i >= 0; --i) {
1067  if (field(x1, i) == EMPTY) {
1068  start_free = i;
1069  break;
1070  }
1071  }
1072  // if not found, cannot slide
1073  // if the first free tile is just next to the sliding tile, no slide (should be a normal move)
1074  if (start_free == -1 || start_free == (y1 - 1)) {
1075  return false;
1076  }
1077  // find last tile empty
1078  for (int i = start_free - 1; i >= 0; --i) {
1079  if (field(x1, i) != EMPTY) {
1080  end_free = i;
1081  break;
1082  }
1083  }
1084  // if not found, it is the border: 0
1085 
1086  // so we can slide of start_free - end_free, compare this to the distance
1087  if (distance <= (start_free - end_free)) {
1088  // first position of the last slided tile
1089  path.append(Position(x1, start_free + 1));
1090  // final position of the last slided tile
1091  path.append(Position(x1, start_free + 1 - distance));
1092  return true;
1093  } else {
1094  return false;
1095  }
1096  } else if (y2 > y1) {
1097  distance = y2 - y1;
1098  // count how much free space we have for sliding
1099  int start_free = -1;
1100  int end_free = yTiles();
1101  // find first tile empty
1102  for (int i = y1 + 1; i < yTiles(); ++i) {
1103  if (field(x1, i) == EMPTY) {
1104  start_free = i;
1105  break;
1106  }
1107  }
1108  // if not found, cannot slide
1109  // if the first free tile is just next to the sliding tile, no slide (should be a normal move)
1110  if (start_free == -1 || start_free == y1 + 1) {
1111  return false;
1112  }
1113  // find last tile empty
1114  for (int i = start_free + 1; i < yTiles(); ++i) {
1115  if (field(x1, i) != EMPTY) {
1116  end_free = i;
1117  break;
1118  }
1119  }
1120  // if not found, it is the border: yTiles()-1
1121 
1122  // so we can slide of end_free - start_free, compare this to the distance
1123  if (distance <= (end_free - start_free)) {
1124  // first position of the last slided tile
1125  path.append(Position(x1, start_free - 1));
1126  // final position of the last slided tile
1127  path.append(Position(x1, start_free - 1 + distance));
1128  return true;
1129  } else {
1130  return false;
1131  }
1132  }
1133  // y1 == y2 ?!
1134  return false;
1135  }
1136 
1137  if (y1 == y2) {
1138  if (x1 > x2) {
1139  distance = x1 - x2;
1140  // count how much free space we have for sliding
1141  int start_free = -1;
1142  int end_free = -1;
1143  // find first tile empty
1144  for (int i = x1 - 1; i >= 0; --i) {
1145  if (field(i, y1) == EMPTY) {
1146  start_free = i;
1147  break;
1148  }
1149  }
1150  // if not found, cannot slide
1151  // if the first free tile is just next to the sliding tile, no slide (should be a normal move)
1152  if (start_free == -1 || start_free == x1 - 1) {
1153  return false;
1154  }
1155  // find last tile empty
1156  for (int i = start_free - 1; i >= 0; --i) {
1157  if (field(i, y1) != EMPTY) {
1158  end_free = i;
1159  break;
1160  }
1161  }
1162  // if not found, it is the border: 0
1163 
1164  // so we can slide of start_free - end_free, compare this to the distance
1165  if (distance <= (start_free - end_free)) {
1166  // first position of the last slided tile
1167  path.append(Position(start_free + 1, y1));
1168  // final position of the last slided tile
1169  path.append(Position(start_free + 1 - distance, y1));
1170  return true;
1171  } else {
1172  return false;
1173  }
1174  } else if (x2 > x1) {
1175  distance = x2 - x1;
1176  // count how much free space we have for sliding
1177  int start_free = -1;
1178  int end_free = xTiles();
1179  // find first tile empty
1180  for (int i = x1 + 1; i < xTiles(); ++i) {
1181  if (field(i, y1) == EMPTY) {
1182  start_free = i;
1183  break;
1184  }
1185  }
1186  // if not found, cannot slide
1187  // if the first free tile is just next to the sliding tile, no slide (should be a normal move)
1188  if (start_free == -1 || start_free == x1 + 1) {
1189  return false;
1190  }
1191  // find last tile empty
1192  for (int i = start_free + 1; i < xTiles(); ++i) {
1193  if (field(i, y1) != EMPTY) {
1194  end_free = i;
1195  break;
1196  }
1197  }
1198  // if not found, it is the border: xTiles()-1
1199 
1200  // so we can slide of end_free - start_free, compare this to the distance
1201  if (distance <= (end_free - start_free)) {
1202  // first position of the last slided tile
1203  path.append(Position(start_free - 1, y1));
1204  // final position of the last slided tile
1205  path.append(Position(start_free - 1 + distance, y1));
1206  return true;
1207  } else {
1208  return false;
1209  }
1210  }
1211  // x1 == x2 ?!
1212  return false;
1213  }
1214  return false;
1215 }
1216 
1217 int Board::findPath(int x1, int y1, int x2, int y2, PossibleMoves &possibleMoves) const
1218 {
1219  possibleMoves.clear();
1220 
1221  int numberOfPaths = 0;
1222  int simplePath = 0;
1223 
1224  // first find the simple paths
1225  numberOfPaths = findSimplePath(x1, y1, x2, y2, possibleMoves);
1226 
1227  // if the tiles can slide, 2 lines max is allowed
1228  if (m_tilesCanSlideFlag) {
1229  return numberOfPaths;
1230  }
1231 
1232  // Find paths of 3 segments
1233  const int dx[4] = { 1, 0, -1, 0 };
1234  const int dy[4] = { 0, 1, 0, -1 };
1235 
1236  for (int i = 0; i < 4; ++i) {
1237  int newX = x1 + dx[i];
1238  int newY = y1 + dy[i];
1239  while (newX >= -1 && newX <= xTiles() &&
1240  newY >= -1 && newY <= yTiles() &&
1241  field(newX, newY) == EMPTY) {
1242  if ((simplePath = findSimplePath(newX, newY, x2, y2, possibleMoves)) > 0) {
1243  possibleMoves.last().m_path.prepend(Position(x1, y1));
1244  numberOfPaths += simplePath;
1245  }
1246  newX += dx[i];
1247  newY += dy[i];
1248  }
1249  }
1250  return numberOfPaths;
1251 }
1252 
1253 int Board::findSimplePath(int x1, int y1, int x2, int y2, PossibleMoves &possibleMoves) const
1254 {
1255  int numberOfPaths = 0;
1256  Path path;
1257  // Find direct line (path of 1 segment)
1258  if (canMakePath(x1, y1, x2, y2)) {
1259  path.append(Position(x1, y1));
1260  path.append(Position(x2, y2));
1261  possibleMoves.append(PossibleMove(path));
1262  ++numberOfPaths;
1263  }
1264 
1265  // If the tiles are in the same row or column, then a
1266  // a 'simple path' cannot be found between them
1267  // That is, canMakePath should have returned true above if
1268  // that was possible
1269  if (x1 == x2 || y1 == y2) {
1270  return numberOfPaths;
1271  }
1272 
1273  // I isolate the special code when tiles can slide even if it duplicates code for now
1274  // Can we make a path sliding tiles ?, the slide move is always first, then a normal path
1275  if (m_tilesCanSlideFlag) {
1276  Path slidePath;
1277  // Find path of 2 segments (route A)
1278  if (canSlideTiles(x1, y1, x2, y1, slidePath) && canMakePath(x2, y1, x2, y2)) {
1279  path.clear();
1280  path.append(Position(x1, y1));
1281  path.append(Position(x2, y1));
1282  path.append(Position(x2, y2));
1283  possibleMoves.append(PossibleMove(path, slidePath));
1284  ++numberOfPaths;
1285  }
1286 
1287  // Find path of 2 segments (route B)
1288  if (canSlideTiles(x1, y1, x1, y2, slidePath) && canMakePath(x1, y2, x2, y2)) {
1289  path.clear();
1290  path.append(Position(x1, y1));
1291  path.append(Position(x1, y2));
1292  path.append(Position(x2, y2));
1293  possibleMoves.append(PossibleMove(path, slidePath));
1294  ++numberOfPaths;
1295  }
1296  }
1297 
1298  // Even if tiles can slide, a path could still be done without sliding
1299 
1300  // Find path of 2 segments (route A)
1301  if (field(x2, y1) == EMPTY && canMakePath(x1, y1, x2, y1) &&
1302  canMakePath(x2, y1, x2, y2)) {
1303  path.clear();
1304  path.append(Position(x1, y1));
1305  path.append(Position(x2, y1));
1306  path.append(Position(x2, y2));
1307  possibleMoves.append(PossibleMove(path));
1308  ++numberOfPaths;
1309  }
1310 
1311  // Find path of 2 segments (route B)
1312  if (field(x1, y2) == EMPTY && canMakePath(x1, y1, x1, y2) &&
1313  canMakePath(x1, y2, x2, y2)) {
1314  path.clear();
1315  path.append(Position(x1, y1));
1316  path.append(Position(x1, y2));
1317  path.append(Position(x2, y2));
1318  possibleMoves.append(PossibleMove(path));
1319  ++numberOfPaths;
1320  }
1321 
1322  return numberOfPaths;
1323 }
1324 
1325 void Board::drawPossibleMoves(bool b)
1326 {
1327  if (m_possibleMoves.isEmpty()) {
1328  return;
1329  }
1330 
1331  m_paintPossibleMoves = b;
1332  update();
1333 }
1334 
1335 void Board::drawConnection()
1336 {
1337  m_paintInProgress = true;
1338  if (m_connection.isEmpty()) {
1339  return;
1340  }
1341 
1342  int x1 = m_connection.first().x;
1343  int y1 = m_connection.first().y;
1344  int x2 = m_connection.last().x;
1345  int y2 = m_connection.last().y;
1346  // lighten the fields
1347  updateField(x1, y1);
1348  updateField(x2, y2);
1349 
1350  m_paintConnection = true;
1351  update();
1352 }
1353 
1354 void Board::undrawConnection()
1355 {
1356  if (m_tileRemove1.first != -1) {
1357  setField(m_tileRemove1.first, m_tileRemove1.second, EMPTY);
1358  setField(m_tileRemove2.first, m_tileRemove2.second, EMPTY);
1359  m_tileRemove1.first = -1;
1360  update();
1361  }
1362 
1363  gravity(true); // why is this called here? (schwarzer)
1364 
1365  // is already undrawn?
1366  if (m_connection.isEmpty()) {
1367  return;
1368  }
1369 
1370  // Redraw all affected fields
1371  Path oldConnection = m_connection;
1372  m_connection.clear();
1373  m_paintConnection = false;
1374 
1375  Path::const_iterator pt1 = oldConnection.constBegin();
1376  Path::const_iterator pt2 = pt1 + 1;
1377  while (pt2 != oldConnection.constEnd()) {
1378  if (pt1->y == pt2->y) {
1379  for (int i = qMin(pt1->x, pt2->x); i <= qMax(pt1->x, pt2->x); ++i) {
1380  updateField(i, pt1->y);
1381  }
1382  } else {
1383  for (int i = qMin(pt1->y, pt2->y); i <= qMax(pt1->y, pt2->y); ++i) {
1384  updateField(pt1->x, i);
1385  }
1386  }
1387  ++pt1;
1388  ++pt2;
1389  }
1390 
1391  PossibleMoves dummyPossibleMoves;
1392  // game is over?
1393  if (!hint_I(dummyPossibleMoves)) {
1394  m_gameClock.pause();
1395  emit endOfGame();
1396  }
1397  m_paintInProgress = false;
1398 }
1399 
1400 QPoint Board::midCoord(int x, int y) const
1401 {
1402  QPoint p;
1403  int w = m_tiles.qWidth() * 2;
1404  int h = m_tiles.qHeight() * 2;
1405 
1406  if (x == -1) {
1407  p.setX(xOffset() - (w / 4));
1408  } else if (x == xTiles()) {
1409  p.setX(xOffset() + (w * xTiles()) + (w / 4));
1410  } else {
1411  p.setX(xOffset() + (w * x) + (w / 2));
1412  }
1413 
1414  if (y == -1) {
1415  p.setY(yOffset() - (w / 4));
1416  } else if (y == yTiles()) {
1417  p.setY(yOffset() + (h * yTiles()) + (w / 4));
1418  } else {
1419  p.setY(yOffset() + (h * y) + (h / 2));
1420  }
1421 
1422  return p;
1423 }
1424 
1425 void Board::setDelay(int newValue)
1426 {
1427  if (m_delay == newValue) {
1428  return;
1429  }
1430  m_delay = newValue;
1431 }
1432 
1433 int Board::delay() const
1434 {
1435  return m_delay;
1436 }
1437 
1438 void Board::madeMove(int x1, int y1, int x2, int y2, Path slide)
1439 {
1440  Move *move;
1441  if (slide.empty()) {
1442  move = new Move(x1, y1, x2, y2, field(x1, y1), field(x2, y2));
1443  } else {
1444  move = new Move(x1, y1, x2, y2, field(x1, y1), field(x2, y2), slide.first().x, slide.first().y, slide.last().x, slide.last().y);
1445  }
1446  m_undo.append(move);
1447  while (m_redo.count()) {
1448  delete m_redo.first();
1449  m_redo.removeFirst();
1450  }
1451  emit changed();
1452 }
1453 
1454 bool Board::canUndo() const
1455 {
1456  return !m_undo.isEmpty();
1457 }
1458 
1459 bool Board::canRedo() const
1460 {
1461  return !m_redo.isEmpty();
1462 }
1463 
1464 void Board::undo()
1465 {
1466  if (!canUndo()) {
1467  return;
1468  }
1469 
1470  clearHighlight();
1471  undrawConnection();
1472  Move *move = m_undo.takeLast();
1473  if (gravityFlag()) {
1474  int y;
1475 
1476  // When both tiles reside in the same column, the order of undo is
1477  // significant (we must undo the lower tile first).
1478  // Also in that case there cannot be a slide
1479  if (move->m_x1 == move->m_x2 && move->m_y1 < move->m_y2) {
1480  qSwap(move->m_x1, move->m_x2);
1481  qSwap(move->m_y1, move->m_y2);
1482  qSwap(move->m_tile1, move->m_tile2);
1483  }
1484 
1485  // if there is no slide, keep previous implementation: move both column up
1486  if (!move->m_hasSlide) {
1487 #ifdef DEBUGGING
1488  kDebug() << "[undo] gravity from a no slide move";
1489 #endif
1490  // move tiles from the first column up
1491  for (y = 0; y < move->m_y1; ++y) {
1492  setField(move->m_x1, y, field(move->m_x1, y + 1));
1493  updateField(move->m_x1, y);
1494  }
1495 
1496  // move tiles from the second column up
1497  for (y = 0; y < move->m_y2; ++y) {
1498  setField(move->m_x2, y, field(move->m_x2, y + 1));
1499  updateField(move->m_x2, y);
1500  }
1501  } else { // else check all tiles from the slide that may have fallen down
1502 #ifdef DEBUGGING
1503  kDebug() << "[undo] gravity from slide s1(" << move->m_slideX1 << "," << move->m_slideY1 << ")=>s2(" << move->m_slideX2 << "," << move->m_slideY2 << ") matching (" << move->m_x1 << "," << move->m_y1 << ")=>(" << move->m_x2 << "," << move->m_y2 << ")";
1504 #endif
1505  // horizontal slide
1506  // because tiles that slides horizontaly may fall down
1507  // in columns different than the taken tiles columns
1508  // we need to take them back up then undo the slide
1509  if (move->m_slideY1 == move->m_slideY2) {
1510 #ifdef DEBUGGING
1511  kDebug() << "[undo] gravity from horizontal slide";
1512 #endif
1513  // last slide tile went from slide_x1 -> slide_x2
1514  // the number of slided tiles is n = abs(x1 - slide_x1)
1515  int n = move->m_x1 - move->m_slideX1;
1516  if (n < 0) {
1517  n = -n;
1518  }
1519  // distance slided is
1520  int dx = move->m_slideX2 - move->m_slideX1;
1521  if (dx < 0) {
1522  dx = -dx;
1523  }
1524 #ifdef DEBUGGING
1525  kDebug() << "[undo] n =" << n;
1526 #endif
1527  // slided tiles may fall down after the slide
1528  // so any tiles on top of the columns between
1529  // slide_x2 -> slide_x2 +/- n (excluded) should go up to slide_y1
1530  if (move->m_slideX2 > move->m_slideX1) { // slide to the right
1531 #ifdef DEBUGGING
1532  kDebug() << "[undo] slide right";
1533 #endif
1534  for (int i = move->m_slideX2; i > move->m_slideX2 - n; --i) {
1535  // find top tile
1536  int j;
1537  for (j = 0; j < yTiles(); ++j) {
1538  if (field(i, j) != EMPTY) {
1539  break;
1540  }
1541  }
1542 
1543  // ignore if the tile did not fall
1544  if (j <= move->m_slideY1) {
1545  continue;
1546  }
1547 #ifdef DEBUGGING
1548  kDebug() << "[undo] moving (" << i << "," << j << ") up to (" << i << "," << move->m_slideY1 << ")";
1549 #endif
1550  // put it back up
1551  setField(i, move->m_slideY1, field(i, j));
1552  setField(i, j, EMPTY);
1553  updateField(i, j);
1554  updateField(i, move->m_slideY1);
1555  }
1556  } else { // slide to the left
1557 #ifdef DEBUGGING
1558  kDebug() << "[undo] slide left";
1559 #endif
1560  for (int i = move->m_slideX2; i < move->m_slideX2 + n; ++i) {
1561  // find top tile
1562  int j;
1563  for (j = 0; j < yTiles(); ++j) {
1564  if (field(i, j) != EMPTY) {
1565  break;
1566  }
1567  }
1568 
1569  // ignore if the tile did not fall
1570  if (j <= move->m_slideY1) {
1571  continue;
1572  }
1573 #ifdef DEBUGGING
1574  kDebug() << "[undo] moving (" << i << "," << j << ") up to (" << i << "," << move->m_slideY1 << ")";
1575 #endif
1576  // put it back up
1577  setField(i, move->m_slideY1, field(i, j));
1578  setField(i, j, EMPTY);
1579  updateField(i, j);
1580  updateField(i, move->m_slideY1);
1581  }
1582  }
1583  // move tiles from the second column up
1584 #ifdef DEBUGGING
1585  kDebug() << "[undo] moving up column x2" << move->m_x2;
1586 #endif
1587  for (y = 0; y <= move->m_y2; ++y) {
1588 #ifdef DEBUGGING
1589  kDebug() << "[undo] moving up tile" << y + 1;
1590 #endif
1591  setField(move->m_x2, y, field(move->m_x2, y + 1));
1592  updateField(move->m_x2, y);
1593  }
1594  // and all columns that fell after the tiles slided between
1595  // only if they were not replaced by a sliding tile !!
1596  // x1 -> x1+dx should go up one
1597  // if their height > slide_y1
1598  // because they have fallen after the slide
1599  if (move->m_slideX2 > move->m_slideX1) { // slide to the right
1600  if (move->m_slideY1 > 0) {
1601  for (int i = move->m_x1 + dx; i >= move->m_x1; --i) {
1602 #ifdef DEBUGGING
1603  kDebug() << "[undo] moving up column" << i << "until" << move->m_slideY1;
1604 #endif
1605  for (int j = 0; j < move->m_slideY1; ++j) {
1606 #ifdef DEBUGGING
1607  kDebug() << "[undo] moving up tile" << j + 1;
1608 #endif
1609  setField(i, j, field(i, j + 1));
1610  updateField(i, j);
1611  }
1612 #ifdef DEBUGGING
1613  kDebug() << "[undo] clearing last tile" << move->m_slideY1;
1614 #endif
1615  setField(i, move->m_slideY1, EMPTY);
1616  updateField(i, move->m_slideY1);
1617  }
1618  }
1619  } else { // slide to the left
1620  if (move->m_slideY1 > 0) {
1621  for (int i = move->m_x1 - dx; i <= move->m_x1; ++i) {
1622 #ifdef DEBUGGING
1623  kDebug() << "[undo] moving up column" << i << "until" << move->m_slideY1;
1624 #endif
1625  for (int j = 0; j < move->m_slideY1; ++j) {
1626 #ifdef DEBUGGING
1627  kDebug() << "[undo] moving up tile" << j + 1;
1628 #endif
1629  setField(i, j, field(i, j + 1));
1630  updateField(i, j);
1631  }
1632 #ifdef DEBUGGING
1633  kDebug() << "[undo] clearing last tile" << move->m_slideY1;
1634 #endif
1635  setField(i, move->m_slideY1, EMPTY);
1636  updateField(i, move->m_slideY1);
1637  }
1638  }
1639  }
1640 
1641  // then undo the slide to put the tiles back to their original location
1642 #ifdef DEBUGGING
1643  kDebug() << "[undo] reversing slide";
1644 #endif
1645  reverseSlide(move->m_x1, move->m_y1, move->m_slideX1, move->m_slideY1, move->m_slideX2, move->m_slideY2);
1646 
1647  } else {
1648  // vertical slide, in fact nothing special is necessary
1649  // the default implementation works because it only affects
1650  // the two columns were tiles were taken
1651 #ifdef DEBUGGING
1652  kDebug() << "[undo] gravity from vertical slide";
1653 #endif
1654 
1655  // move tiles from the first column up
1656  for (y = 0; y < move->m_y1; ++y) {
1657  setField(move->m_x1, y, field(move->m_x1, y + 1));
1658  updateField(move->m_x1, y);
1659  }
1660 
1661  // move tiles from the second column up
1662  for (y = 0; y < move->m_y2; ++y) {
1663  setField(move->m_x2, y, field(move->m_x2, y + 1));
1664  updateField(move->m_x2, y);
1665  }
1666  }
1667  }
1668  } else { // no gravity
1669  // undo slide if any
1670  if (move->m_hasSlide) {
1671  // perform the slide in reverse
1672  reverseSlide(move->m_x1, move->m_y1, move->m_slideX1, move->m_slideY1, move->m_slideX2, move->m_slideY2);
1673  }
1674  }
1675 
1676  // replace taken tiles
1677  setField(move->m_x1, move->m_y1, move->m_tile1);
1678  setField(move->m_x2, move->m_y2, move->m_tile2);
1679  updateField(move->m_x1, move->m_y1);
1680  updateField(move->m_x2, move->m_y2);
1681 
1682  m_redo.prepend(move);
1683  emit changed();
1684 }
1685 
1686 void Board::redo()
1687 {
1688  if (canRedo()) {
1689  clearHighlight();
1690  undrawConnection();
1691  Move *move = m_redo.takeFirst();
1692  // redo the slide if any
1693  if (move->m_hasSlide) {
1694  Path s;
1695  s.append(Position(move->m_slideX1, move->m_slideY1));
1696  s.append(Position(move->m_slideX2, move->m_slideY2));
1697  performSlide(move->m_x1, move->m_y1, s);
1698  }
1699  setField(move->m_x1, move->m_y1, EMPTY);
1700  setField(move->m_x2, move->m_y2, EMPTY);
1701  updateField(move->m_x1, move->m_y1);
1702  updateField(move->m_x2, move->m_y2);
1703  gravity(true);
1704  m_undo.append(move);
1705  emit changed();
1706  }
1707 }
1708 
1709 void Board::showHint()
1710 {
1711  undrawConnection();
1712 
1713  if (hint_I(m_possibleMoves)) {
1714  m_connection = m_possibleMoves.first().m_path;
1715  drawConnection();
1716  }
1717 }
1718 
1719 
1720 #ifdef DEBUGGING
1721 void Board::makeHintMove()
1722 {
1723  PossibleMoves possibleMoves;
1724 
1725  if (hint_I(possibleMoves)) {
1726  m_markX = -1;
1727  m_markY = -1;
1728  marked(possibleMoves.first().m_path.first().x, possibleMoves.first().m_path.first().y);
1729  marked(possibleMoves.first().m_path.last().x, possibleMoves.first().m_path.last().y);
1730  }
1731 }
1732 
1733 
1734 void Board::dumpBoard() const
1735 {
1736  kDebug() << "Board contents:";
1737  for (int y = 0; y < yTiles(); ++y) {
1738  QString row;
1739  for (int x = 0; x < xTiles(); ++x) {
1740  int tile = field(x, y);
1741  if (tile == EMPTY) {
1742  row += " --";
1743  } else {
1744  row += QString("%1").arg(tile, 3);
1745  }
1746  }
1747  kDebug() << row;
1748  }
1749 }
1750 
1751 void Board::dumpBoard(const int *board) const
1752 {
1753  kDebug() << "Board contents:";
1754  for (int y = 0; y < yTiles(); ++y) {
1755  QString row;
1756  for (int x = 0; x < xTiles(); ++x) {
1757  int tile = board[y * xTiles() + x];
1758  if (tile == EMPTY) {
1759  row += " --";
1760  } else {
1761  row += QString("%1").arg(tile, 3);
1762  }
1763  }
1764  kDebug() << row;
1765  }
1766 }
1767 #endif
1768 
1769 int Board::lineWidth() const
1770 {
1771  int width = qRound(m_tiles.height() / 10.0);
1772  if (width < 3) {
1773  width = 3;
1774  }
1775 
1776  return width;
1777 }
1778 
1779 bool Board::hint_I(PossibleMoves &possibleMoves) const
1780 {
1781  short done[Board::nTiles];
1782  for (short i = 0; i < Board::nTiles; ++i) {
1783  done[i] = 0;
1784  }
1785 
1786  for (int x = 0; x < xTiles(); ++x) {
1787  for (int y = 0; y < yTiles(); ++y) {
1788  int tile = field(x, y);
1789  if (tile != EMPTY && done[tile - 1] != 4) {
1790  // for all these types of tile search paths
1791  for (int xx = 0; xx < xTiles(); ++xx) {
1792  for (int yy = 0; yy < yTiles(); ++yy) {
1793  if (xx != x || yy != y) {
1794  if (tilesMatch(field(xx, yy), tile)) {
1795  if (findPath(x, y, xx, yy, possibleMoves) > 0) {
1796  return true;
1797  }
1798  }
1799  }
1800  }
1801  }
1802  done[tile - 1]++;
1803  }
1804  }
1805  }
1806  return false;
1807 }
1808 
1809 int Board::tilesLeft() const
1810 {
1811  int left = 0;
1812 
1813  for (int i = 0; i < xTiles(); ++i) {
1814  for (int j = 0; j < yTiles(); ++j) {
1815  if (field(i, j) != EMPTY) {
1816  ++left;
1817  }
1818  }
1819  }
1820 
1821  return left;
1822 }
1823 
1824 int Board::currentTime() const
1825 {
1826  return m_gameClock.seconds();
1827 }
1828 
1829 bool Board::solvable(bool noRestore)
1830 {
1831  int *oldField = 0;
1832 
1833  if (!noRestore) {
1834  oldField = new int [xTiles() * yTiles()];
1835  memcpy(oldField, m_field, xTiles() * yTiles() * sizeof(int));
1836  }
1837 
1838  PossibleMoves p;
1839  while (hint_I(p)) {
1840  kFatal(!tilesMatch(field(p.first().m_path.first().x, p.first().m_path.first().y), field(p.first().m_path.last().x, p.first().m_path.last().y)))
1841  << "Removing unmatched tiles: (" << p.first().m_path.first().x << "," << p.first().m_path.first().y << ") => "
1842  << field(p.first().m_path.first().x, p.first().m_path.first().y) << " (" << p.first().m_path.last().x << "," << p.first().m_path.last().y << ") => "
1843  << field(p.first().m_path.last().x, p.first().m_path.last().y);
1844  setField(p.first().m_path.first().x, p.first().m_path.first().y, EMPTY);
1845  setField(p.first().m_path.last().x, p.first().m_path.last().y, EMPTY);
1846  }
1847 
1848  int left = tilesLeft();
1849 
1850  if (!noRestore) {
1851  memcpy(m_field, oldField, xTiles() * yTiles() * sizeof(int));
1852  delete [] oldField;
1853  }
1854 
1855  return left == 0;
1856 }
1857 
1858 bool Board::solvableFlag() const
1859 {
1860  return m_solvableFlag;
1861 }
1862 
1863 void Board::setSolvableFlag(bool enabled)
1864 {
1865  if (m_solvableFlag == enabled) {
1866  return;
1867  }
1868  m_solvableFlag = enabled;
1869  // if the solvable flag was set and the current game is not solvable, start a new game
1870  if (m_solvableFlag && !solvable()) {
1871  newGame();
1872  }
1873 }
1874 
1875 bool Board::gravityFlag() const
1876 {
1877  return m_gravityFlag;
1878 }
1879 
1880 void Board::setGravityFlag(bool enabled)
1881 {
1882  if (m_gravityFlag == enabled) {
1883  return;
1884  }
1885  m_gravityFlag = enabled;
1886  // start a new game if the player is in the middle of a game
1887  if (canUndo() || canRedo()) {
1888  newGame();
1889  }
1890 }
1891 
1892 void Board::setChineseStyleFlag(bool enabled)
1893 {
1894  if (m_chineseStyleFlag == enabled) {
1895  return;
1896  }
1897  m_chineseStyleFlag = enabled;
1898  // we need to force a newGame() because board generation is different
1899  newGame();
1900 }
1901 
1902 void Board::setTilesCanSlideFlag(bool enabled)
1903 {
1904  if (m_tilesCanSlideFlag == enabled) {
1905  return;
1906  }
1907  m_tilesCanSlideFlag = enabled;
1908  // start a new game if the player is in the middle of a game
1909  if (canUndo() || canRedo()) {
1910  newGame();
1911  }
1912 }
1913 
1914 void Board::setPauseEnabled(bool enabled)
1915 {
1916  if ((m_gameState == Paused && enabled) || m_gameState == Stuck) {
1917  return;
1918  }
1919  if (enabled) {
1920  m_gameState = Paused;
1921  m_gameClock.pause();
1922  } else {
1923  m_gameState = Normal;
1924  m_gameClock.resume();
1925  }
1926  emit changed();
1927  update();
1928 }
1929 
1930 QSize Board::sizeHint() const
1931 {
1932  int dpi = logicalDpiX();
1933  if (dpi < 75) {
1934  dpi = 75;
1935  }
1936  return QSize(9 * dpi, 7 * dpi);
1937 }
1938 
1939 void Board::resetTimer()
1940 {
1941  m_gameClock.restart();
1942 }
1943 
1944 void Board::resetUndo()
1945 {
1946  if (!canUndo()) {
1947  return;
1948  }
1949  qDeleteAll(m_undo);
1950  m_undo.clear();
1951 }
1952 
1953 void Board::resetRedo()
1954 {
1955  if (!canRedo()) {
1956  return;
1957  }
1958  qDeleteAll(m_redo);
1959  m_redo.clear();
1960 }
1961 
1962 void Board::setGameStuckEnabled(bool enabled)
1963 {
1964  if (m_gameState == Stuck && enabled) {
1965  return;
1966  }
1967  if (enabled) {
1968  m_gameState = Stuck;
1969  m_gameClock.pause();
1970  } else {
1971  m_gameState = Normal;
1972  m_gameClock.resume();
1973  }
1974  emit changed();
1975  update();
1976 }
1977 
1978 void Board::setGameOverEnabled(bool enabled)
1979 {
1980  if (m_gameState == Over && enabled) {
1981  return;
1982  }
1983  m_gameState = Over;
1984  emit changed();
1985  update();
1986 }
1987 
1988 void Board::setCheatModeEnabled(bool enabled)
1989 {
1990  if (m_cheat == enabled) {
1991  return;
1992  }
1993  m_cheat = enabled;
1994  emit cheatStatusChanged();
1995 }
1996 
1997 bool Board::isOver() const
1998 {
1999  return m_gameState == Over;
2000 }
2001 
2002 bool Board::isPaused() const
2003 {
2004  return m_gameState == Paused;
2005 }
2006 
2007 bool Board::isStuck() const
2008 {
2009  return m_gameState == Stuck;
2010 }
2011 
2012 bool Board::hasCheated() const
2013 {
2014  return m_cheat;
2015 }
2016 
2017 void Board::setSoundsEnabled(bool enabled)
2018 {
2019  Prefs::setSounds(enabled);
2020  Prefs::self()->writeConfig();
2021 }
2022 
2023 #include "board.moc"
2024 
2025 // vim: expandtab:tabstop=4:shiftwidth=4
2026 // kate: space-indent on; indent-width 4
PossibleMove::m_slide
Path m_slide
path representing the movement of the last sliding tile
Definition: board.h:98
QPalette::setBrush
void setBrush(ColorRole role, const QBrush &brush)
QList::clear
void clear()
Board::canUndo
bool canUndo() const
Returns if undo step is available.
Definition: board.cpp:1454
Prefs::setBackground
static void setBackground(const QString &v)
Set The background to use.
Definition: prefs.h:39
QResizeEvent
QWidget
QFont::setPointSize
void setPointSize(int pointSize)
Board::yTiles
int yTiles() const
Definition: board.cpp:184
QWidget::palette
const QPalette & palette() const
QPainter::end
bool end()
QPainter::fillRect
void fillRect(const QRectF &rectangle, const QBrush &brush)
Prefs::gravity
static bool gravity()
Get Gravity.
Definition: prefs.h:125
QPainter::setRenderHint
void setRenderHint(RenderHint hint, bool on)
PossibleMove::m_hasSlide
bool m_hasSlide
flag set if the move requires a slide
Definition: board.h:97
QWidget::contentsRect
QRect contentsRect() const
Board::tilesDoNotMatch
void tilesDoNotMatch()
Board::setGameStuckEnabled
void setGameStuckEnabled(bool enabled)
Sets whether there are no matching tiles left.
Definition: board.cpp:1962
Board::loadTileset
bool loadTileset(const QString &)
Loads the given tileset.
Definition: board.cpp:137
Board::hasCheated
bool hasCheated() const
Returns whether player is in cheat mode.
Definition: board.cpp:2012
Board::loadSettings
void loadSettings()
Loads the game settings.
Definition: board.cpp:99
EMPTY
#define EMPTY
Definition: board.cpp:37
FLOWERS_START
#define FLOWERS_START
Definition: board.cpp:39
QFont
Board::isOver
bool isOver() const
Returns whether the game is over.
Definition: board.cpp:1997
Move::m_tile1
int m_tile1
type of tile at first set of coordinates
Definition: board.h:123
QList::removeFirst
void removeFirst()
Prefs::speed
static int speed()
Get Speed.
Definition: prefs.h:175
QWidget::y
int y() const
QRect::intersects
bool intersects(const QRect &rectangle) const
PossibleMove::isInPath
bool isInPath(int x, int y) const
Definition: board.cpp:43
Board::solvableFlag
bool solvableFlag() const
Definition: board.cpp:1858
QBrush
Board::setChineseStyleFlag
void setChineseStyleFlag(bool b)
Definition: board.cpp:1892
QPoint
Board::tilesLeft
int tilesLeft() const
Returns the number of tiles left on the board.
Definition: board.cpp:1809
prefs.h
QMouseEvent
Board::showHint
void showHint()
Definition: board.cpp:1709
Prefs::chineseStyle
static bool chineseStyle()
Get ChineseStyle.
Definition: prefs.h:68
Prefs::level
static int level()
Get Level.
Definition: prefs.h:237
Board::nTiles
static const int nTiles
Definition: board.h:144
QPainter::drawLine
void drawLine(const QLineF &line)
Board::solvable
bool solvable(bool noRestore=false)
Returns whether the current game is solvable.
Definition: board.cpp:1829
Board::resizeBoard
void resizeBoard()
Definition: board.cpp:422
QWidget::update
void update()
QPoint::x
int x() const
QPoint::y
int y() const
Board::sizeHint
virtual QSize sizeHint() const
Definition: board.cpp:1930
QWidget::width
int width() const
Board::isPaused
bool isPaused() const
Returns whether the game is in pause mode.
Definition: board.cpp:2002
Board::gravityFlag
bool gravityFlag() const
Definition: board.cpp:1875
Prefs::self
static Prefs * self()
Definition: prefs.cpp:17
Move::m_y2
int m_y2
coordinates of the two tiles that matched
Definition: board.h:122
QPaintEvent::rect
const QRect & rect() const
Board::Board
Board(QWidget *parent=0)
Definition: board.cpp:69
QWidget::setMinimumSize
void setMinimumSize(const QSize &)
Move::m_slideY1
int m_slideY1
original y coordinate of the last slided tile
Definition: board.h:127
Prefs::sounds
static bool sounds()
Get Sounds.
Definition: prefs.h:144
QRect
Move::m_slideX2
int m_slideX2
final x coordinate of the last slided tile
Definition: board.h:128
QPainter::setFont
void setFont(const QFont &font)
PossibleMove::m_path
Path m_path
path used to connect the two tiles
Definition: board.h:96
QWidget::enabled
enabled
QList::count
int count(const T &value) const
Board::setSize
void setSize(int x, int y)
Definition: board.cpp:381
QList::append
void append(const T &value)
Board::setTilesCanSlideFlag
void setTilesCanSlideFlag(bool b)
Definition: board.cpp:1902
Move::m_y1
int m_y1
Definition: board.h:122
Board::resetUndo
void resetUndo()
Resets the undo history.
Definition: board.cpp:1944
QList::empty
bool empty() const
QWidget::x
int x() const
Board::isStuck
bool isStuck() const
Returns whether there are still matching tiles left.
Definition: board.cpp:2007
QPainter::setPen
void setPen(const QColor &color)
QMouseEvent::button
Qt::MouseButton button() const
QPainter::drawRoundedRect
void drawRoundedRect(const QRectF &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
QWidget::backgroundRole
QPalette::ColorRole backgroundRole() const
QPainter::drawPixmap
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
QList::isEmpty
bool isEmpty() const
Board::resizeEvent
virtual void resizeEvent(QResizeEvent *e)
Definition: board.cpp:412
QPainter
Board::delay
int delay() const
Definition: board.cpp:1433
Move
Class holding a move on the board made by the player.
Definition: board.h:112
Board::paintEvent
virtual void paintEvent(QPaintEvent *e)
Definition: board.cpp:679
s_delay
static int s_delay[5]
Definition: board.cpp:41
QWidget::pos
QPoint pos() const
PossibleMove
Class holding a possible move and its functions.
Definition: board.h:71
sizeX
static int sizeX[6]
Definition: board.h:45
QPainter::setBrush
void setBrush(const QBrush &brush)
QPainter::drawText
void drawText(const QPointF &position, const QString &text)
Board::selectATile
void selectATile()
QEvent::spontaneous
bool spontaneous() const
Move::m_tile2
int m_tile2
type of tile at second set of coordinates
Definition: board.h:124
Prefs::setTileSet
static void setTileSet(const QString &v)
Set The tile set to use.
Definition: prefs.h:20
Board::cheatStatusChanged
void cheatStatusChanged()
QList::first
T & first()
QString
QList
Board::loadBackground
bool loadBackground(const QString &)
Loads the given background.
Definition: board.cpp:158
QColor
Board::setSoundsEnabled
void setSoundsEnabled(bool enabled)
Enables / disables sounds.
Definition: board.cpp:2017
Board::invalidMove
void invalidMove()
QPair< int, int >
QPaintDevice::logicalDpiX
int logicalDpiX() const
PossibleMove::Debug
void Debug() const
Definition: board.h:81
QList::end
iterator end()
Board::endOfGame
void endOfGame()
QSize
QWidget::font
const QFont & font() const
Board::changed
void changed()
Board::selectAMove
void selectAMove()
Prefs::tileSet
static QString tileSet()
Get The tile set to use.
Definition: prefs.h:30
Board::~Board
~Board()
Definition: board.cpp:94
Board::xTiles
int xTiles() const
Definition: board.cpp:179
Prefs::solvable
static bool solvable()
Get Solvable.
Definition: prefs.h:106
SEASONS_START
#define SEASONS_START
Definition: board.cpp:38
QList::takeLast
T takeLast()
Board::newGame
void newGame()
Does most of the newGame work.
Definition: board.cpp:436
QList::takeFirst
T takeFirst()
QLatin1String
Board::setSolvableFlag
void setSolvableFlag(bool b)
Definition: board.cpp:1863
Board::newGameStarted
void newGameStarted()
Board::currentTime
int currentTime() const
Returns the current game time in seconds.
Definition: board.cpp:1824
Board::resized
void resized()
Prefs::size
static int size()
Get Size.
Definition: prefs.h:206
QList::last
T & last()
Board::resetRedo
void resetRedo()
Resets the redo history.
Definition: board.cpp:1953
QPoint::setX
void setX(int x)
QPoint::setY
void setY(int y)
Position
Struct holding a position on the board (x,y)
Definition: board.h:51
QPen
Move::m_slideX1
int m_slideX1
original x coordinate of the last slided tile
Definition: board.h:126
Prefs::tilesCanSlide
static bool tilesCanSlide()
Get TilesCanSlide.
Definition: prefs.h:87
QList::prepend
void prepend(const T &value)
Board::setGravityFlag
void setGravityFlag(bool b)
Definition: board.cpp:1880
QMouseEvent::pos
const QPoint & pos() const
Board::setPauseEnabled
void setPauseEnabled(bool enabled)
Controls the pause mode.
Definition: board.cpp:1914
QPaintEvent
Board::canRedo
bool canRedo() const
Returns if redo step is available.
Definition: board.cpp:1459
sizeY
static int sizeY[6]
Definition: board.h:46
QList::constEnd
const_iterator constEnd() const
Board::redo
void redo()
Redoes one step.
Definition: board.cpp:1686
Prefs::background
static QString background()
Get The background to use.
Definition: prefs.h:49
QList::constBegin
const_iterator constBegin() const
Board::resetTimer
void resetTimer()
Resets the game timer.
Definition: board.cpp:1939
Board::mousePressEvent
virtual void mousePressEvent(QMouseEvent *e)
Definition: board.cpp:281
Board::setDelay
void setDelay(int)
Definition: board.cpp:1425
Board::setGameOverEnabled
void setGameOverEnabled(bool enabled)
Sets whether the game is over.
Definition: board.cpp:1978
Prefs::setSounds
static void setSounds(bool v)
Set Sounds.
Definition: prefs.h:134
Move::m_hasSlide
bool m_hasSlide
if we performed a slide during the move
Definition: board.h:125
Board::undo
void undo()
Undoes one step.
Definition: board.cpp:1464
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
Move::m_x2
int m_x2
Definition: board.h:122
board.h
QList::begin
iterator begin()
Board::selectAMatchingTile
void selectAMatchingTile()
Move::m_slideY2
int m_slideY2
final y coordinate of the last slided tile
Definition: board.h:129
QPalette
QWidget::height
int height() const
Board::setCheatModeEnabled
void setCheatModeEnabled(bool enabled)
Sets whether the game is in cheat mode.
Definition: board.cpp:1988
Board::hint_I
bool hint_I(PossibleMoves &possibleMoves) const
Definition: board.cpp:1779
QTimer::singleShot
singleShot
Move::m_x1
int m_x1
Definition: board.h:122
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:18:36 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KShisen

Skip menu "KShisen"
  • Main Page
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members

kdegames API Reference

Skip menu "kdegames API Reference"
  • granatier
  • kapman
  • kblackbox
  • kgoldrunner
  • kigo
  • kmahjongg
  • KShisen
  • ksquares
  • libkdegames
  •   highscore
  •   libkdegamesprivate
  •     kgame
  • libkmahjongg
  • palapeli
  •   libpala

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal