1/* Copyright (C) 1997 Mathias Mueller <in5y158@public.uni-hamburg.de>
2 * Copyright (C) 2006 Mauricio Piacentini <mauricio@tabuleiro.com>
3 *
4 * Kmahjongg is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
17
18#include "boardwidget.h"
19#include "prefs.h"
20
21#include <QTimer>
22#include <qpainter.h>
23#include <qapplication.h>
24
25#include <klocale.h>
26#include <kstandarddirs.h>
27#include <kmessagebox.h>
28#include <krandom.h>
29#include <kconfig.h>
30#include <kglobal.h>
31#include <KDebug>
32
33
34BoardWidget::BoardWidget(QWidget *parent)
35 : KGameCanvasWidget(parent),
36 theTiles()
37{
38 QPalette palette;
39 palette.setColor(backgroundRole(), Qt::black);
40 setPalette(palette);
41
42 timer = new QTimer(this);
43 connect(timer, SIGNAL(timeout()), this, SLOT(helpMoveTimeout()));
44
45 TimerState = Stop;
46 gamePaused = false;
47 iTimerStep = 0;
48 matchCount = 0;
49 showMatch = false;
50 showHelp = false;
51
52 //memset( &Game->Mask, 0, sizeof( Game->Mask ) );
53 gameGenerationNum = 0;
54 m_angle = (TileViewAngle) Prefs::angle();
55
56 // Load layout first
57 loadBoardLayout(Prefs::layout());
58
59 //Initialize our Game structure
60 Game = new GameData(theBoardLayout.board());
61
62 MouseClickPos1.e = Game->m_depth; // mark tile position as invalid
63 MouseClickPos2.e = Game->m_depth;
64
65 //Animation timers
66 animateForwardTimer = new QTimer(this);
67 animateForwardTimer->setSingleShot(true);
68 animateForwardTimer->setInterval(100);
69 connect(animateForwardTimer, SIGNAL(timeout()), SLOT(animatingMoveListForward()));
70 animateForwardTimer->stop();
71
72 animateBackwardsTimer = new QTimer(this);
73 animateBackwardsTimer->setSingleShot(true);
74 animateBackwardsTimer->setInterval(100);
75 connect(animateBackwardsTimer, SIGNAL(timeout()), SLOT(animatingMoveListBackwards()));
76 animateBackwardsTimer->stop();
77
78 loadSettings();
79}
80
81BoardWidget::~BoardWidget()
82{
83 delete Game;
84}
85
86void BoardWidget::loadSettings()
87{
88 // Load tileset. First try to load the last use tileset
89
90 if (!loadTileset(Prefs::tileSet())) {
91 kDebug() << "An error occurred when loading the tileset" << Prefs::tileSet() << "KMahjongg "
92 "will continue with the default tileset.";
93 }
94
95 // Load background
96 if (!loadBackground(Prefs::background(), false)) {
97 kDebug() << "An error occurred when loading the background" << Prefs::background() << "KMah"
98 "jongg will continue with the default background.";
99 }
100
101 setShowMatch(Prefs::showMatchingTiles());
102
103 // If the random layout is activated, we won't load a boardlayout, as it will be created
104 // randomly in calculateNewGame().
105 if (!Prefs::randomLayout()) {
106 if (QString::compare(Prefs::layout(), theBoardLayout.path(), Qt::CaseSensitive) !=0 ) {
107 //TODO: WARN USER HERE ABOUT DESTRUCTIVE OPERATION!!!
108 loadBoardLayout(Prefs::layout());
109 calculateNewGame();
110 }
111 } else {
112 calculateNewGame();
113 }
114
115 setDisplayedWidth();
116 drawBoard(true);
117
118 //Store our updated settings, some values might have been changed to defaults
119 saveSettings();
120}
121
122void BoardWidget::resizeEvent(QResizeEvent *event)
123{
124 if (event->spontaneous()) {
125 return;
126 }
127
128 resizeTileset(event->size());
129 theBackground.sizeChanged(requiredWidth(), requiredHeight());
130 drawBoard(true);
131}
132
133void BoardWidget::resizeTileset(const QSize &wsize)
134{
135 QSize newtiles = theTiles.preferredTileSize(wsize, requiredHorizontalCells(),
136 requiredVerticalCells());
137
138 theTiles.reloadTileset(newtiles);
139 stopMatchAnimation();
140}
141
142void BoardWidget::saveSettings()
143{
144 Prefs::setTileSet(theTiles.path());
145 Prefs::setLayout(theBoardLayout.path());
146 Prefs::setBackground(theBackground.path());
147 Prefs::setAngle(m_angle);
148 Prefs::self()->writeConfig();
149}
150
151void BoardWidget::setDisplayedWidth()
152{
153 //TODO remove method and let the layout handle it
154 //for now we just force our resizeEvent() to be called
155 resize(width(), height());
156}
157
158void BoardWidget::populateSpriteMap()
159{
160 //Delete previous sprites (full update), synchronize state with GameData
161 while (!items()->isEmpty()) {
162 delete items()->first();
163 }
164
165 //Clear our spritemap as well
166 spriteMap.clear();
167
168 //Recreate our background
169 QPalette palette;
170 palette.setBrush(backgroundRole(), theBackground.getBackground());
171 setPalette(palette);
172 setAutoFillBackground(true);
173
174 //create the sprites
175 for (int z = 0; z < Game->m_depth; z++) {
176 // we draw down the board so the tile below over rights our border
177 for (int y = 0; y < Game->m_height; y++) {
178 // drawing right to left to prevent border overwrite
179 for (int x = Game->m_width - 1; x >= 0; x--) {
180
181 // skip if no tile to display
182 if (!Game->tilePresent(z, y, x)) {
183 continue;
184 }
185
186 QPixmap s;
187 QPixmap us;
188 QPixmap f;
189 bool selected = false;
190 s = theTiles.selectedTile(m_angle);
191 us = theTiles.unselectedTile(m_angle);
192 f = theTiles.tileface(Game->BoardData(z, y, x) - TILE_OFFSET);
193
194 if (Game->HighlightData(z, y, x)) {
195 selected = true;
196 }
197
198 TileSprite *thissprite = new TileSprite(this, us, s, f, m_angle, selected);
199
200 spriteMap.insert(TileCoord(x, y, z), thissprite);
201 }
202 }
203 }
204
205 updateSpriteMap();
206}
207
208void BoardWidget::updateSpriteMap()
209{
210 // initial offset on the screen of tile 0,0
211 // think of it as what is left if you add all tilefaces (qwidth/heigh*2) plus one
212 // (wholetile - shadow(3dindent) - tileface), and divide by 2
213 int xOffset = (width() - (Game->m_width * (theTiles.qWidth())) - (theTiles.width()
214 - (theTiles.qWidth() * 2))) / 2;
215 int yOffset = (height() - (Game->m_height * (theTiles.qHeight())) - (theTiles.height()
216 - (theTiles.qHeight() * 2))) / 2;
217
218 // we iterate over the depth stacking order. Each successive level is
219 // drawn one indent up and to the right. The indent is the width
220 // of the 3d relief on the tile left (tile shadow width)
221 switch (m_angle) {
222 case NW:
223 //remove shadow from margin calculation
224 xOffset += theTiles.levelOffsetX() / 2;
225 yOffset += theTiles.levelOffsetY() / 2;
226
227 //Position
228 for (int z = 0; z < Game->m_depth; z++) {
229 // we draw down the board so the tile below over rights our border
230 for (int y = 0; y < Game->m_height; y++) {
231 // drawing right to left to prevent border overwrite
232 for (int x=Game->m_width - 1; x >= 0; x--) {
233 int sx = x * (theTiles.qWidth()) + xOffset;
234 int sy = y * (theTiles.qHeight()) + yOffset;
235
236 // skip if no tile to display
237 if (!Game->tilePresent(z, y, x)) {
238 continue;
239 }
240
241 TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));
242
243 if (thissprite) {
244 thissprite->moveTo(sx, sy);
245 }
246
247 if (thissprite) {
248 thissprite->show();
249 }
250 }
251 }
252
253 xOffset += theTiles.levelOffsetX();
254 yOffset -= theTiles.levelOffsetY();
255 }
256
257 //Layer
258 for (int z = 0; z < Game->m_depth; z++) {
259 // start drawing in diagonal for correct layering. For this we double the board,
260 //actually starting outside of it, in the bottom right corner, so our first diagonal ends
261 // at the actual top right corner of the board
262 for (int x = Game->m_width * 2; x >= 0; x--) {
263 // reset the offset
264 int offset = 0;
265
266 for (int y = Game->m_height - 1; y >= 0; y--) {
267 if (Game->tilePresent(z, y, x - offset)) {
268 TileSprite *thissprite = spriteMap.value(TileCoord(x - offset, y, z));
269
270 if (thissprite) {
271 thissprite->raise();
272 }
273 }
274
275 //at each pass, move one place to the left
276 offset++;
277 }
278 }
279 }
280
281 break;
282
283 case NE:
284 xOffset -= theTiles.levelOffsetX() / 2;
285 yOffset += theTiles.levelOffsetY() / 2;
286
287 //Position
288 for (int z = 0; z < Game->m_depth; z++) {
289 // we draw down the board so the tile below over rights our border
290 for (int y = 0; y < Game->m_height; y++) {
291 // drawing right to left to prevent border overwrite
292 for (int x = 0; x <= Game->m_width - 1; x++) {
293 int sx = x * (theTiles.qWidth()) + xOffset;
294 int sy = y * (theTiles.qHeight()) + yOffset;
295
296 // skip if no tile to display
297 if (!Game->tilePresent(z, y, x)) {
298 continue;
299 }
300
301 TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));
302
303 if (thissprite) {
304 thissprite->moveTo(sx, sy);
305 }
306
307 if (thissprite) {
308 thissprite->show();
309 }
310 }
311 }
312
313 xOffset -= theTiles.levelOffsetX();
314 yOffset -= theTiles.levelOffsetY();
315 }
316
317 //Layer
318 for (int z = 0; z < Game->m_depth; z++) {
319 // start drawing in diagonal for correct layering. For this we double the board,
320 //actually starting outside of it, in the bottom right corner, so our first diagonal ends
321 // at the actual top right corner of the board
322 for (int x =- (Game->m_width); x <= Game->m_width - 1; x++) {
323 // reset the offset
324 int offset = 0;
325
326 for (int y = Game->m_height - 1; y >= 0; y--) {
327 if (Game->tilePresent(z, y, x + offset)) {
328 TileSprite *thissprite = spriteMap.value(TileCoord(x + offset, y, z));
329
330 if (thissprite) {
331 thissprite->raise();
332 }
333 }
334
335 //at each pass, move one place to the right
336 offset++;
337 }
338 }
339 }
340
341 break;
342
343 case SE:
344 xOffset -= theTiles.levelOffsetX() / 2;
345 yOffset -= theTiles.levelOffsetY() / 2;
346
347 //Position
348 for (int z = 0; z < Game->m_depth; z++) {
349 for (int y = Game->m_height - 1; y >= 0; y--) {
350 for (int x = 0; x <= Game->m_width - 1; x++) {
351 int sx = x * (theTiles.qWidth()) + xOffset;
352 int sy = y * (theTiles.qHeight()) + yOffset;
353
354 if (!Game->tilePresent(z, y, x)) {
355 continue;
356 }
357
358 TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));
359
360 if (thissprite) {
361 thissprite->moveTo(sx, sy);
362 }
363
364 if (thissprite) {
365 thissprite->show();
366 }
367 }
368 }
369
370 xOffset -= theTiles.levelOffsetX();
371 yOffset += theTiles.levelOffsetY();
372 }
373
374 //Layer
375 for (int z = 0; z < Game->m_depth; z++) {
376 for (int x =- (Game->m_width); x <= Game->m_width - 1; x++) {
377 int offset = 0;
378
379 for (int y = 0; y < Game->m_height; y++) {
380 if (Game->tilePresent(z, y, x + offset)) {
381 TileSprite *thissprite = spriteMap.value(TileCoord(x + offset, y, z));
382 if (thissprite) {
383 thissprite->raise();
384 }
385 }
386
387 offset++;
388 }
389 }
390 }
391
392 break;
393
394 case SW:
395 xOffset += theTiles.levelOffsetX() / 2;
396 yOffset -= theTiles.levelOffsetY() / 2;
397
398 //Position
399 for (int z = 0; z < Game->m_depth; z++) {
400 for (int y = Game->m_height - 1; y >= 0; y--) {
401 for (int x = Game->m_width - 1; x >= 0; x--) {
402 int sx = x * (theTiles.qWidth()) + xOffset;
403 int sy = y * (theTiles.qHeight()) + yOffset;
404
405 if (!Game->tilePresent(z, y, x)) {
406 continue;
407 }
408
409 TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));
410
411 if (thissprite) {
412 thissprite->moveTo(sx, sy);
413 }
414
415 if (thissprite) {
416 thissprite->show();
417 }
418 }
419 }
420
421 xOffset += theTiles.levelOffsetX();
422 yOffset += theTiles.levelOffsetY();
423 }
424
425 //Layer
426 for (int z = 0; z < Game->m_depth; z++) {
427 for (int x = Game->m_width * 2; x >= 0; x--) {
428 int offset = 0;
429
430 for (int y = 0; y < Game->m_height; y++) {
431 if (Game->tilePresent(z, y, x - offset)) {
432 TileSprite *thissprite = spriteMap.value(TileCoord(x - offset, y, z));
433
434 if (thissprite) {
435 thissprite->raise();
436 }
437 }
438
439 offset++;
440 }
441 }
442 }
443
444 break;
445 }
446}
447
448void BoardWidget::pause()
449{
450 gamePaused = !gamePaused;
451 drawBoard(!gamePaused);
452}
453
454void BoardWidget::gameLoaded()
455{
456 int i;
457 Game->initialiseRemovedTiles();
458 i = Game->TileNum;
459
460 // use the history of moves to put in the removed tiles area the correct tiles
461 while (i < Game->MaxTileNum) {
462 Game->setRemovedTilePair(Game->MoveListData(i), Game->MoveListData(i + 1));
463 i += 2;
464 }
465
466 populateSpriteMap();
467 drawBoard(true);
468}
469
470int BoardWidget::undoMove()
471{
472 cancelUserSelectedTiles();
473
474 if (Game->TileNum < Game->MaxTileNum)
475 {
476 Game->clearRemovedTilePair(Game->MoveListData(Game->TileNum), Game->MoveListData(
477 Game->TileNum + 1));
478 putTileInBoard(Game->MoveListData(Game->TileNum), false);
479 Game->TileNum++;
480 putTileInBoard(Game->MoveListData(Game->TileNum));
481 Game->TileNum++;
482 drawTileNumber();
483 setStatusText(i18n("Undo operation done successfully."));
484
485 return 1;
486 } else {
487 setStatusText(i18n("What do you want to undo? You have done nothing!"));
488
489 return 0;
490 }
491}
492
493void BoardWidget::helpMove()
494{
495 cancelUserSelectedTiles();
496 if (showHelp) helpMoveStop();
497
498 if (Game->findMove(TimerPos1, TimerPos2)) {
499 cheatsUsed++;
500 iTimerStep = 1;
501 showHelp = true;
502 helpMoveTimeout();
503 } else {
504 setStatusText(i18n("Sorry, you have lost the game."));
505 }
506}
507
508void BoardWidget::helpMoveTimeout()
509{
510 if (iTimerStep & 1) {
511 hilightTile(TimerPos1, true, false);
512 hilightTile(TimerPos2, true);
513 } else {
514 hilightTile(TimerPos1, false, false);
515 hilightTile(TimerPos2, false);
516 }
517
518 // restart timer
519 if (iTimerStep++ < 8) {
520 timer->setSingleShot(true);
521 timer->start(ANIMSPEED);
522 } else {
523 showHelp = false;
524 }
525}
526
527void BoardWidget::helpMoveStop()
528{
529 timer->stop();
530 iTimerStep = 8;
531 hilightTile(TimerPos1, false, false);
532 hilightTile(TimerPos2, false);
533 showHelp = false;
534}
535
536void BoardWidget::startDemoMode()
537{
538 calculateNewGame();
539
540 if (TimerState == Stop) {
541 TimerState = Demo;
542 iTimerStep = 0;
543 emit demoModeChanged(true);
544 setStatusText(i18n("Demo mode. Click mousebutton to stop."));
545 demoMoveTimeout();
546 }
547}
548
549void BoardWidget::stopDemoMode()
550{
551 TimerState = Stop; // stop demo
552 calculateNewGame();
553 setStatusText(i18n("Now it is you again."));
554
555 emit demoModeChanged(false);
556 emit gameCalculated();
557}
558
559void BoardWidget::demoMoveTimeout()
560{
561 if (TimerState == Demo) {
562 switch (iTimerStep++ % 6) {
563 case 0:
564 if (!Game->findMove(TimerPos1, TimerPos2)) {
565 // if computer has won
566 if (Game->TileNum == 0) {
567 animateMoveList();
568 } else {
569 setStatusText(i18n("Your computer has lost the game."));
570
571 while (Game->TileNum < Game->MaxTileNum) {
572 putTileInBoard(Game->MoveListData(Game->TileNum), false);
573 Game->TileNum++;
574 putTileInBoard(Game->MoveListData(Game->TileNum));
575 Game->TileNum++;
576 drawTileNumber();
577 }
578 }
579
580 TimerState = Stop;
581 //startDemoMode();
582 //do not loop demo
583 stopDemoMode();
584
585 return;
586 }
587
588 break;
589
590 case 1:
591 case 3:
592 hilightTile(TimerPos1, true, false);
593 hilightTile(TimerPos2, true);
594
595 break;
596
597 case 2:
598 case 4:
599 hilightTile(TimerPos1, false, false);
600 hilightTile(TimerPos2, false);
601
602 break;
603
604 case 5:
605 Game->setRemovedTilePair(TimerPos1, TimerPos2);
606 removeTile(TimerPos1, false);
607 removeTile(TimerPos2);
608 drawTileNumber();
609
610 break;
611 }
612
613 // restart timer
614 QTimer::singleShot(ANIMSPEED, this, SLOT(demoMoveTimeout()));
615 }
616}
617
618void BoardWidget::setShowMatch(bool show)
619{
620 if (showMatch) {
621 stopMatchAnimation();
622 }
623
624 showMatch = show;
625}
626
627void BoardWidget::matchAnimationTimeout()
628{
629 if (matchCount == 0) {
630 return;
631 }
632
633 if (iTimerStep++ & 1) {
634 for (short Pos = 0; Pos < matchCount; Pos++) {
635 hilightTile(Game->getFromPosTable(Pos), true);
636 }
637 } else {
638 for (short Pos = 0; Pos < matchCount; Pos++) {
639 hilightTile(Game->getFromPosTable(Pos), false);
640 }
641 }
642
643 if (TimerState == Match) {
644 QTimer::singleShot(ANIMSPEED, this, SLOT(matchAnimationTimeout()));
645 }
646}
647
648void BoardWidget::stopMatchAnimation()
649{
650 for (short Pos = 0; Pos < matchCount; Pos++) {
651 hilightTile(Game->getFromPosTable(Pos), false);
652 }
653
654 TimerState = Stop;
655 matchCount = 0;
656}
657
658void BoardWidget::redoMove()
659{
660 Game->setRemovedTilePair(Game->MoveListData(Game->TileNum - 1), Game->MoveListData(Game->TileNum
661 - 2));
662 removeTile(Game->MoveListData(Game->TileNum - 1), false);
663 removeTile(Game->MoveListData(Game->TileNum - 1));
664 drawTileNumber();
665}
666
667void BoardWidget::animateMoveList()
668{
669 setStatusText(i18n("Congratulations. You have won!"));
670 animatingMoveListForward();
671}
672
673void BoardWidget::animatingMoveListForward()
674{
675 if (Game->TileNum < Game->MaxTileNum) {
676 // put back all tiles
677 putTileInBoard(Game->MoveListData(Game->TileNum));
678 Game->TileNum++;
679 putTileInBoard(Game->MoveListData(Game->TileNum), false);
680 Game->TileNum++;
681 drawTileNumber();
682 animateForwardTimer->start(); //it is a single shot timer
683 } else {
684 //start removal
685 animateBackwardsTimer->start(); //it is a single shot timer
686 }
687}
688
689void BoardWidget::animatingMoveListBackwards()
690{
691 if (Game->TileNum > 0) {
692 // remove all tiles
693 removeTile(Game->MoveListData(Game->TileNum - 1), false);
694 removeTile(Game->MoveListData(Game->TileNum - 1));
695 drawTileNumber();
696 animateBackwardsTimer->start(); //it is a single shot timer
697 } else {
698 //end of animation
699 stopEndAnimation();
700 }
701}
702
703void BoardWidget::stopEndAnimation()
704{
705 animateForwardTimer->stop();
706 animateBackwardsTimer->stop();
707}
708
709QString BoardWidget::getRandomLayoutName() const
710{
711 QStringList tilesAvailable = KGlobal::dirs()->findAllResources("kmahjongglayout",
712 QString("*.desktop"), KStandardDirs::Recursive);
713
714 return tilesAvailable.at(qrand() % tilesAvailable.size());
715}
716
717void BoardWidget::calculateNewGame(int gNumber)
718{
719 cancelUserSelectedTiles();
720 stopMatchAnimation();
721 stopEndAnimation();
722 Game->initialiseRemovedTiles();
723
724 // If random layout is true, we will create a new random layout from the existing layouts.
725 if (Prefs::randomLayout()) {
726 QString layoutName = getRandomLayoutName();
727 loadBoardLayout(layoutName);
728 }
729
730 setStatusText(i18n("Calculating new game..."));
731
732 if (!loadBoard()) {
733 setStatusText(i18n("Error converting board information!"));
734
735 return;
736 }
737
738 if (gNumber == -1) {
739 gameGenerationNum = KRandom::random();
740 } else {
741 gameGenerationNum = gNumber;
742 }
743
744 Game->random.setSeed(gameGenerationNum);
745
746 // Translate Game->Map to an array of POSITION data. We only need to
747 // do this once for each new game.
748 Game->generateTilePositions();
749
750 // Now use the tile position data to generate tile dependency data.
751 // We only need to do this once for each new game.
752 Game->generatePositionDepends();
753
754 // Now try to position tiles on the board, 64 tries max.
755 for (short nr = 0; nr < 64; nr++) {
756 if (Game->generateStartPosition2()) {
757 drawBoard(true);
758 setStatusText(i18n("Ready. Now it is your turn."));
759 cheatsUsed = 0;
760 emit gameCalculated();
761
762 return;
763 }
764 }
765
766 drawBoard(false);
767 setStatusText(i18n("Error generating new game!"));
768}
769
770void BoardWidget::hilightTile(POSITION& Pos, bool on, bool doRepaint)
771{
772 TileSprite *atile = 0;
773 TileCoord coord = TileCoord(Pos.x, Pos.y, Pos.e);
774
775 if (spriteMap.contains(coord)) {
776 atile = spriteMap.value(coord);
777 }
778
779 if (on) {
780 Game->setHighlightData(Pos.e, Pos.y, Pos.x, 1);
781 if (atile) {
782 atile->setSelected(true);
783 }
784 } else {
785 Game->setHighlightData(Pos.e, Pos.y, Pos.x, 0);
786 if (atile) {
787 atile->setSelected(false);
788 }
789 }
790}
791
792void BoardWidget::drawBoard(bool showTiles)
793{
794 if (gamePaused) {
795 showTiles = false;
796 }
797
798 if (showTiles) {
799 populateSpriteMap();
800 drawTileNumber();
801 } else {
802 //Delete previous sprites
803 while (!items()->isEmpty()) {
804 delete items()->first();
805 }
806
807 //Clear our spritemap as well
808 spriteMap.clear();
809
810 //Recreate our background
811 QPalette palette;
812 palette.setBrush(backgroundRole(), theBackground.getBackground());
813 setPalette(palette);
814 setAutoFillBackground(true);
815 }
816}
817
818void BoardWidget::putTileInBoard(POSITION& Pos, bool doRepaint)
819{
820 short E = Pos.e;
821 short Y = Pos.y;
822 short X = Pos.x;
823
824 // we ensure that any tile we put on has highlighting off
825 Game->putTile(E, Y, X, Pos.f);
826 Game->setHighlightData(E, Y, X, 0);
827
828 QPixmap s;
829 QPixmap us;
830 QPixmap f;
831 s = theTiles.selectedTile(m_angle);
832 us = theTiles.unselectedTile(m_angle);
833 f = theTiles.tileface(Game->BoardData(E, Y, X) - TILE_OFFSET);
834 TileSprite *thissprite = new TileSprite(this, us, s, f, m_angle, false);
835 thissprite->show();
836 spriteMap.insert(TileCoord(X, Y, E), thissprite);
837
838 updateSpriteMap();
839}
840
841void BoardWidget::removeTile(POSITION& Pos, bool doRepaint)
842{
843 short E = Pos.e;
844 short Y = Pos.y;
845 short X = Pos.x;
846
847 Game->TileNum--; // Eine Figur weniger
848 Game->setMoveListData(Game->TileNum,Pos); // Position ins Protokoll eintragen
849
850 delete spriteMap.take(TileCoord(X,Y,E));
851
852 // remove tile from game board
853 Game->putTile(E, Y, X, 0);
854}
855
856void BoardWidget::mousePressEvent(QMouseEvent *event)
857{
858 if (gamePaused) {
859 return;
860 }
861
862 if (event->button() == Qt::LeftButton) {
863 if (TimerState == Demo) {
864 stopDemoMode();
865 } else if (showMatch) {
866 stopMatchAnimation();
867 }
868
869 if (showHelp) { // stop hilighting tiles
870 helpMoveStop();
871 }
872
873 if (MouseClickPos1.e == Game->m_depth) { // first tile
874 transformPointToPosition(event->pos(), MouseClickPos1);
875
876 if (MouseClickPos1.e != Game->m_depth && showMatch) {
877 matchCount = Game->findAllMatchingTiles(MouseClickPos1);
878 TimerState = Match;
879 iTimerStep = 1;
880 matchAnimationTimeout();
881 cheatsUsed++;
882 }
883 } else { // second tile
884 transformPointToPosition(event->pos(), MouseClickPos2);
885 if (MouseClickPos2.e == Game->m_depth) {
886 cancelUserSelectedTiles();
887 } else {
888 if (Game->isMatchingTile(MouseClickPos1, MouseClickPos2)) {
889 // update the removed tiles (we do this before the remove below
890 // so that we only require 1 screen paint for both actions)
891 Game->setRemovedTilePair(MouseClickPos1, MouseClickPos2);
892
893 // now we remove the tiles from the board*t,
894 removeTile(MouseClickPos1, false);
895 removeTile(MouseClickPos2);
896
897 // removing a tile means redo is impossible without
898 // a further undo.
899 Game->allow_redo = false;
900 demoModeChanged(false);
901 drawTileNumber();
902
903 // if no tiles are left, the player has `won`, so celebrate
904 if (Game->TileNum == 0) {
905 gameOver(Game->MaxTileNum,cheatsUsed);
906 } else {
907 // else if no more moves are possible, display the sour grapes dialog
908 validMovesAvailable();
909 }
910 } else {
911 // redraw tiles in normal state
912 hilightTile(MouseClickPos1, false, false);
913 hilightTile(MouseClickPos2, false);
914 }
915
916 MouseClickPos1.e = Game->m_depth; // mark tile position as invalid
917 MouseClickPos2.e = Game->m_depth;
918 }
919 }
920 }
921}
922
923KGameCanvasItem* BoardWidget::itemAt(const QPoint& point) const
924{
925 // Get the shadows...
926 // theTiles.width() == The whole tile width including offset and shadow.
927 // theTiles.qWidth() == Half of the width of the tile face (without the offset and the shadow).
928 // theTiles.levelOffsetX() == The level of the tile (the perspective height of the tile)
929 int shadowWidth = theTiles.width() - (theTiles.qWidth() * 2 + theTiles.levelOffsetX());
930 int shadowHeight = theTiles.height() - (theTiles.qHeight() * 2 + theTiles.levelOffsetY());
931
932 for (int i = m_items.size() - 1; i >= 0; i--) {
933 KGameCanvasItem *canvasItem = m_items[i];
934
935 // Cause of the shadow, that is not really a part of a tile. Correct the rect positions
936 // related to the angle. Actually we just correct the rect of the canvas item cause of the
937 // shadow, that is not part of the clickable tile.
938 QRect oldRect = canvasItem->rect();
939 QRect rectCorrection = oldRect;
940
941 // Correct the positions related to the angle we had set.
942 switch (m_angle) {
943 case NW:
944 rectCorrection.setRect(oldRect.x() + shadowWidth, oldRect.y(),
945 oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
946 break;
947 case NE:
948 rectCorrection.setRect(oldRect.x(), oldRect.y(), oldRect.width() - shadowWidth,
949 oldRect.height() - shadowHeight);
950 break;
951 case SW:
952 rectCorrection.setRect(oldRect.x() + shadowWidth, oldRect.y() + shadowHeight,
953 oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
954 break;
955 case SE:
956 rectCorrection.setRect(oldRect.x(), oldRect.y() + shadowHeight,
957 oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
958 break;
959 }
960
961 // if the canvas is visible and we click inside the corrected rect of the canvas item, we
962 // can return the actual canvas item.
963 if (canvasItem->visible() && rectCorrection.contains(point)) {
964 return canvasItem;
965 }
966 }
967
968 return NULL;
969}
970
971void BoardWidget::transformPointToPosition(const QPoint &point, POSITION &MouseClickPos)
972{
973 int E;
974 int X;
975 int Y;
976
977 TileSprite *clickedItem = NULL;
978 clickedItem = (TileSprite *) itemAt(point);
979
980 if (!clickedItem) {
981 //no item under mouse
982 qDebug() << "no tile registered";
983
984 return;
985 }
986
987 TileCoord coord = spriteMap.key(clickedItem);
988 E = coord.z();
989 X = coord.x();
990 Y = coord.y();
991
992 //sanity checking
993 if (X < 0 || X >= Game->m_width || Y < 0 || Y >= Game->m_height) {
994 return;
995 }
996
997 // if gameboard is empty, skip
998 //sanity checking
999 if (!Game->BoardData(E, Y, X)) {
1000 qDebug() << "Tile not in BoardData. Fading out?";
1001
1002 return;
1003 }
1004
1005 // tile must be 'free' (nothing left, right or above it)
1006 //Optimization, skip "over" test for the top layer
1007 if (E < Game->m_depth - 1) {
1008 if (Game->BoardData(E + 1, Y, X)
1009 || (Y < Game->m_height - 1 && Game->BoardData(E + 1, Y + 1, X))
1010 || (X < Game->m_width - 1 && Game->BoardData(E + 1, Y, X + 1))
1011 || (X < Game->m_width - 1 && Y < Game->m_height - 1
1012 && Game->BoardData(E + 1, Y + 1, X + 1))) {
1013 return;
1014 }
1015 }
1016
1017 // No left test on left edge
1018 if (( X > 0) && (Game->BoardData(E, Y, X - 1) || Game->BoardData(E, Y + 1, X - 1))) {
1019 if ((X < Game->m_width - 2)
1020 && (Game->BoardData(E, Y, X + 2) || Game->BoardData(E, Y + 1, X + 2))) {
1021 return;
1022 }
1023 }
1024
1025 // if we reach here, position is legal
1026 MouseClickPos.e = E;
1027 MouseClickPos.y = Y;
1028 MouseClickPos.x = X;
1029 MouseClickPos.f = Game->BoardData(E, Y, X);
1030
1031 // give visible feedback
1032 hilightTile( MouseClickPos );
1033}
1034
1035bool BoardWidget::loadBoard( )
1036{
1037 delete Game;
1038 Game = new GameData(theBoardLayout.board());
1039
1040 return true;
1041}
1042
1043void BoardWidget::setStatusText(const QString &pszText)
1044{
1045 emit statusTextChanged(pszText, gameGenerationNum);
1046}
1047
1048bool BoardWidget::loadBackground(const QString &pszFileName, bool bShowError)
1049{
1050 if (theBackground.load(pszFileName, requiredWidth(), requiredHeight())) {
1051 if (theBackground.loadGraphics()) {
1052 return true;
1053 }
1054 }
1055
1056 //Try default
1057 if (theBackground.loadDefault()) {
1058 if (theBackground.loadGraphics()) {
1059 }
1060 }
1061
1062 return false;
1063}
1064
1065void BoardWidget::drawTileNumber()
1066{
1067 emit tileNumberChanged(Game->MaxTileNum, Game->TileNum, Game->moveCount());
1068}
1069
1070void BoardWidget::cancelUserSelectedTiles()
1071{
1072 if (MouseClickPos1.e != Game->m_depth) {
1073 hilightTile(MouseClickPos1, false); // redraw tile
1074 MouseClickPos1.e = Game->m_depth; // mark tile invalid
1075 }
1076}
1077
1078bool BoardWidget::loadTileset(const QString &path)
1079{
1080 if (theTiles.loadTileset(path)) {
1081 if (theTiles.loadGraphics()) {
1082 resizeTileset(size());
1083
1084 return true;
1085 }
1086 }
1087
1088 //Tileset or graphics could not be loaded, try default
1089 if (theTiles.loadDefault()) {
1090 if (theTiles.loadGraphics()) {
1091 resizeTileset(size());
1092 }
1093 }
1094
1095 return false;
1096}
1097
1098bool BoardWidget::loadBoardLayout(const QString &file)
1099{
1100 if (theBoardLayout.load(file)) {
1101 return true;
1102 } else {
1103 if (theBoardLayout.loadDefault()) {
1104 return false;
1105 } else {
1106 return false;
1107 }
1108 }
1109}
1110
1111int BoardWidget::requiredHorizontalCells()
1112{
1113 int res = (Game->m_width / 2);
1114 /*if (Prefs::showRemoved())
1115 res = res + 3; //space for removed tiles*/
1116
1117 return res;
1118}
1119
1120int BoardWidget::requiredVerticalCells()
1121{
1122 int res = (Game->m_height / 2);
1123
1124 return res;
1125}
1126
1127int BoardWidget::requiredWidth()
1128{
1129 //int res = ((BoardLayout::width+12)* theTiles.qWidth());
1130 int res = width();
1131
1132 return res;
1133}
1134
1135int BoardWidget::requiredHeight()
1136{
1137 //int res = ((BoardLayout::height+3)* theTiles.qHeight());
1138 int res = height();
1139
1140 return res;
1141}
1142
1143void BoardWidget::angleSwitchCCW()
1144{
1145 switch (m_angle) {
1146 case NW :
1147 m_angle = NE;
1148
1149 break;
1150
1151 case NE :
1152 m_angle = SE;
1153
1154 break;
1155
1156 case SE :
1157 m_angle = SW;
1158
1159 break;
1160
1161 case SW :
1162 m_angle = NW;
1163
1164 break;
1165 }
1166
1167 QHashIterator<TileCoord, TileSprite*> i(spriteMap);
1168 while (i.hasNext()) {
1169 i.next();
1170 QPixmap u = theTiles.unselectedTile(m_angle);
1171 QPixmap s = theTiles.selectedTile(m_angle);
1172 i.value()->setAngle(m_angle, u, s);
1173 }
1174
1175 //re-position and re-layer
1176 updateSpriteMap();
1177
1178 //save angle
1179 saveSettings();
1180}
1181
1182void BoardWidget::angleSwitchCW()
1183{
1184 switch (m_angle) {
1185 case NW :
1186 m_angle = SW;
1187
1188 break;
1189
1190 case NE :
1191 m_angle = NW;
1192
1193 break;
1194
1195 case SE :
1196 m_angle = NE;
1197
1198 break;
1199
1200 case SW :
1201 m_angle = SE;
1202
1203 break;
1204 }
1205
1206 QHashIterator<TileCoord, TileSprite *> i(spriteMap);
1207 while (i.hasNext()) {
1208 i.next();
1209 QPixmap u = theTiles.unselectedTile(m_angle);
1210 QPixmap s = theTiles.selectedTile(m_angle);
1211 i.value()->setAngle(m_angle, u, s);
1212 }
1213
1214 //re-position and re-layer
1215 updateSpriteMap();
1216
1217 //save settings
1218 saveSettings();
1219}
1220
1221void BoardWidget::shuffle()
1222{
1223 cancelUserSelectedTiles();
1224 Game->shuffle();
1225
1226 // force a full redraw
1227 populateSpriteMap();
1228
1229 // I consider this s very bad cheat so, I punish the user
1230 // 300 points per use
1231 cheatsUsed += 15;
1232 drawTileNumber();
1233
1234 // Test if any moves are available
1235 validMovesAvailable();
1236}
1237
1238bool BoardWidget::validMovesAvailable()
1239{
1240 if (!Game->findMove(TimerPos1, TimerPos2)) {
1241 KMessageBox::information(this, i18n("Game over: You have no moves left."));
1242
1243 return false;
1244 }
1245
1246 return true;
1247}
1248
1249QString BoardWidget::getLayoutName()
1250{
1251 QString key("Name");
1252
1253 return theBoardLayout.authorProperty(key);
1254}
1255
1256void BoardWidget::wheelEvent(QWheelEvent *event)
1257{
1258 if (event->orientation() == Qt::Vertical) {
1259 if (event->delta() > 0) {
1260 angleSwitchCCW();
1261 } else {
1262 angleSwitchCW();
1263 }
1264 }
1265
1266 event->accept();
1267}
1268
1269
1270#include "boardwidget.moc"
1271