summaryrefslogtreecommitdiffstats
path: root/kmahjongg
diff options
context:
space:
mode:
Diffstat (limited to 'kmahjongg')
-rw-r--r--kmahjongg/Background.cpp115
-rw-r--r--kmahjongg/Background.h36
-rw-r--r--kmahjongg/BoardLayout.cpp300
-rw-r--r--kmahjongg/BoardLayout.h62
-rw-r--r--kmahjongg/ChangeLog97
-rw-r--r--kmahjongg/Editor.cpp675
-rw-r--r--kmahjongg/Editor.h68
-rw-r--r--kmahjongg/GameTimer.cpp94
-rw-r--r--kmahjongg/GameTimer.h53
-rw-r--r--kmahjongg/HighScore.cpp541
-rw-r--r--kmahjongg/HighScore.h77
-rw-r--r--kmahjongg/KmTypes.h27
-rw-r--r--kmahjongg/Makefile.am25
-rw-r--r--kmahjongg/Preview.cpp513
-rw-r--r--kmahjongg/Preview.h104
-rw-r--r--kmahjongg/Tileset.cpp281
-rw-r--r--kmahjongg/Tileset.h107
-rw-r--r--kmahjongg/boardwidget.cpp2023
-rw-r--r--kmahjongg/boardwidget.h244
-rw-r--r--kmahjongg/hi128-app-kmahjongg.pngbin0 -> 9847 bytes
-rw-r--r--kmahjongg/hi16-app-kmahjongg.pngbin0 -> 679 bytes
-rw-r--r--kmahjongg/hi22-app-kmahjongg.pngbin0 -> 3688 bytes
-rw-r--r--kmahjongg/hi32-app-kmahjongg.pngbin0 -> 1642 bytes
-rw-r--r--kmahjongg/hi48-app-kmahjongg.pngbin0 -> 2825 bytes
-rw-r--r--kmahjongg/hi64-app-kmahjongg.pngbin0 -> 3952 bytes
-rw-r--r--kmahjongg/kmahjongg.cpp562
-rw-r--r--kmahjongg/kmahjongg.desktop72
-rw-r--r--kmahjongg/kmahjongg.h119
-rw-r--r--kmahjongg/kmahjongg.kcfg46
-rw-r--r--kmahjongg/kmahjonggui.rc102
-rw-r--r--kmahjongg/main.cpp38
-rw-r--r--kmahjongg/pics/Makefile.am19
-rw-r--r--kmahjongg/pics/cross.layout81
-rw-r--r--kmahjongg/pics/default.bgndbin0 -> 41434 bytes
-rw-r--r--kmahjongg/pics/default.layout86
-rw-r--r--kmahjongg/pics/default.theme4
-rw-r--r--kmahjongg/pics/default.tilesetbin0 -> 45748 bytes
-rw-r--r--kmahjongg/pics/haze.bgndbin0 -> 14708 bytes
-rw-r--r--kmahjongg/pics/kmahjongg.pngbin0 -> 9711 bytes
-rw-r--r--kmahjongg/pics/kmahjongg_bgnd.pngbin0 -> 27876 bytes
-rw-r--r--kmahjongg/pics/newnum.xpm30
-rw-r--r--kmahjongg/pics/pirates.bgndbin0 -> 286354 bytes
-rw-r--r--kmahjongg/pics/pirates.layout81
-rw-r--r--kmahjongg/pics/pirates.theme5
-rw-r--r--kmahjongg/pics/pirates.tilesetbin0 -> 38860 bytes
-rw-r--r--kmahjongg/pics/pyramid.layout86
-rw-r--r--kmahjongg/pics/runes.tilesetbin0 -> 302454 bytes
-rw-r--r--kmahjongg/pics/slate.bgndbin0 -> 18270 bytes
-rw-r--r--kmahjongg/pics/splash.pngbin0 -> 19311 bytes
-rw-r--r--kmahjongg/pics/stax.layout81
-rw-r--r--kmahjongg/pics/test.layout81
-rw-r--r--kmahjongg/pics/test2.layout81
-rw-r--r--kmahjongg/pics/tower.layout86
-rw-r--r--kmahjongg/pics/traditional.tilesetbin0 -> 47578 bytes
-rw-r--r--kmahjongg/pics/triangle.layout86
-rw-r--r--kmahjongg/pics/wood.bgndbin0 -> 18534 bytes
-rw-r--r--kmahjongg/prefs.kcfgc7
-rw-r--r--kmahjongg/settings.ui171
-rw-r--r--kmahjongg/version.h1
59 files changed, 7367 insertions, 0 deletions
diff --git a/kmahjongg/Background.cpp b/kmahjongg/Background.cpp
new file mode 100644
index 00000000..749607b3
--- /dev/null
+++ b/kmahjongg/Background.cpp
@@ -0,0 +1,115 @@
+
+#include "Background.h"
+#include <qimage.h>
+
+Background::Background(): tile(true) {
+
+ sourceImage = 0;
+ backgroundImage = 0;
+ backgroundPixmap = 0;
+ backgroundShadowPixmap = 0;
+}
+
+Background::~Background() {
+ delete sourceImage;
+ delete backgroundImage;
+ delete backgroundPixmap;
+ delete backgroundShadowPixmap;
+}
+
+bool Background::load(const QString &file, short width, short height) {
+ w=width;
+ h=height;
+
+ if (file == filename) {
+ return true;
+ }
+ sourceImage = new QImage();
+ backgroundImage = new QImage();
+ backgroundPixmap = new QPixmap();
+ backgroundShadowPixmap = new QPixmap();
+
+ // try to load the image, return on failure
+ if(!sourceImage->load(file ))
+ return false;
+
+ // Just in case the image is loaded 8 bit
+ if (sourceImage->depth() != 32)
+ *sourceImage = sourceImage->convertDepth(32);
+
+ // call out to scale/tile the source image into the background image
+ sourceToBackground();
+ filename = file;
+
+ return true;
+}
+
+// slot used when tile/scale option changes
+void Background::scaleModeChanged() {
+ sourceToBackground();
+}
+
+void Background::sizeChanged(int newW, int newH) {
+ if (newW == w && newH == h)
+ return;
+ w = newW;
+ h = newH;
+ sourceToBackground();
+}
+
+void Background::sourceToBackground() {
+
+ // Deallocate the old image and create the new one
+ if (!backgroundImage->isNull())
+ backgroundImage->reset();
+
+ // the new version of kmahjongg uses true color images
+ // to avoid the old color limitation.
+ backgroundImage->create(w, h, 32);
+
+ // Now we decide if we should scale the incoming image
+ // or if we tile. First we check for an exact match which
+ // should be true for all images created specifically for us.
+ if ((sourceImage->width() == w) && (sourceImage->height() == h)) {
+ *backgroundImage = *sourceImage;
+ return;
+ }
+
+ if (tile) {
+ // copy new to background wrapping on w and height
+ for (int y=0; y<backgroundImage->height(); y++) {
+ QRgb *dest = (QRgb *) backgroundImage->scanLine(y);
+ QRgb *src = (QRgb *) sourceImage->scanLine(y % sourceImage->height());
+ for (int x=0; x< backgroundImage->width(); x++) {
+ *dest = *(src + (x % sourceImage->width()));
+ dest++;
+ }
+ }
+ } else {
+ *backgroundImage = sourceImage->smoothScale(w, h);
+ // Just incase the image is loaded 8 bit
+ if (backgroundImage->depth() != 32)
+ *backgroundImage = backgroundImage->convertDepth(32);
+ }
+
+ // Save a copy of the background as a pixmap for easy and quick
+ // blitting.
+ backgroundPixmap->convertFromImage(*backgroundImage);
+
+ QImage tmp;
+ tmp.create(backgroundImage->width(), backgroundImage->height(), 32);
+ for (int ys=0; ys < tmp.height(); ys++) {
+ QRgb *src = (QRgb *) backgroundImage->scanLine(ys);
+ QRgb *dst = (QRgb *) tmp.scanLine(ys);
+ for (int xs=0; xs < tmp.width(); xs++) {
+ *dst=QColor(*src).dark(133).rgb();
+ src++;
+ dst++;
+ }
+ }
+
+ backgroundShadowPixmap->convertFromImage(tmp);
+
+ return;
+}
+
diff --git a/kmahjongg/Background.h b/kmahjongg/Background.h
new file mode 100644
index 00000000..095f0f6e
--- /dev/null
+++ b/kmahjongg/Background.h
@@ -0,0 +1,36 @@
+#ifndef _BACKGROUND_H
+#define _BACKGROUND_H
+#include <qstring.h>
+
+class QPixmap;
+class QImage;
+
+
+class Background
+{
+
+
+ public:
+ Background();
+ ~Background();
+ bool tile;
+
+ bool load(const QString &file, short width, short height);
+ void sizeChanged(int newW, int newH);
+ void scaleModeChanged();
+ QPixmap *getBackground() {return backgroundPixmap;}
+ QPixmap *getShadowBackground() {return backgroundShadowPixmap;}
+ private:
+ void sourceToBackground();
+
+ int tileMode; // scale background = 0, tile = 1
+ QImage *backgroundImage;
+ QImage *sourceImage;
+ QPixmap *backgroundPixmap;
+ QPixmap *backgroundShadowPixmap;
+ QString filename;
+ short w;
+ short h;
+};
+
+#endif
diff --git a/kmahjongg/BoardLayout.cpp b/kmahjongg/BoardLayout.cpp
new file mode 100644
index 00000000..56659611
--- /dev/null
+++ b/kmahjongg/BoardLayout.cpp
@@ -0,0 +1,300 @@
+
+#include "BoardLayout.h"
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qtextcodec.h>
+
+
+
+
+
+BoardLayout::BoardLayout()
+{
+ filename="";
+ clearBoardLayout();
+}
+
+BoardLayout::~BoardLayout()
+{
+}
+
+void BoardLayout::clearBoardLayout() {
+ loadedBoard="";
+ initialiseBoard();
+}
+
+bool BoardLayout::saveBoardLayout(const QString where) {
+ QFile f(where);
+ if (!f.open(IO_ReadWrite)) {
+ return false;
+ }
+
+ QCString tmp = layoutMagic1_0.utf8();
+ if (f.writeBlock(tmp, tmp.length()) == -1) {
+ return(false);
+ }
+
+ for (int z=0; z<depth; z++) {
+ for(int y=0; y<height; y++) {
+ if(f.putch('\n')==-1)
+ return(false);
+ for (int x=0; x<width; x++) {
+ if (board[z][y][x]) {
+ if(f.putch(board[z][y][x])==-1)
+ return false;
+ } else {
+ if(f.putch('.')==-1)
+ return false;
+ }
+ }
+ }
+ }
+ if(f.putch('\n')!=-1)
+ return true;
+ else
+ return false;
+}
+
+
+bool BoardLayout::loadBoardLayout(const QString from)
+{
+
+ if (from == filename) {
+ return true;
+ }
+
+ QFile f(from);
+ QString all = "";
+
+ if ( f.open(IO_ReadOnly) ) {
+ QTextStream t( &f );
+ t.setCodec(QTextCodec::codecForName("UTF-8"));
+ QString s;
+ s = t.readLine();
+ if (s != layoutMagic1_0) {
+ f.close();
+ return(false);
+ }
+ int lines = 0;
+ while ( !t.eof() ) {
+ s = t.readLine();
+ if (s[0] == '#')
+ continue;
+ all += s;
+ lines++;
+ }
+ f.close();
+ if (lines == height*depth) {
+ loadedBoard = all;
+ initialiseBoard();
+ filename = from;
+ return(true);
+ } else {
+ return(false);
+ }
+ return(true);
+ } else {
+ return(false);
+ }
+}
+
+void BoardLayout::initialiseBoard() {
+
+ short z=0;
+ short x=0; // Rand lassen.
+ short y=0;
+ maxTileNum = 0;
+
+ const char *pos = (const char *) loadedBoard.ascii();
+
+ memset( &board, 0, sizeof( board) );
+
+ if (loadedBoard.isEmpty())
+ return;
+
+ // loop will be left by break or return
+ while( true )
+ {
+ BYTE c = *pos++;
+
+ switch( c )
+ {
+ case (UCHAR)'1': maxTileNum++;
+ case (UCHAR)'3':
+ case (UCHAR)'2':
+ case (UCHAR)'4': board[z][y][x] = c;
+ break;
+
+ default: break;
+ }
+ if( ++x == width)
+ {
+ x=0;
+ if( ++y == height)
+ {
+ y=0;
+ if( ++z == depth)
+ {
+ // number of tiles have to be even
+ if( maxTileNum & 1 ) break;
+ return;
+ }
+ }
+ }
+ }
+}
+
+void BoardLayout::copyBoardLayout(UCHAR *to , unsigned short &n){
+
+ memcpy(to, board, sizeof(board));
+ n = maxTileNum;
+}
+
+const char* BoardLayout::getBoardLayout()
+{
+ return loadedBoard.ascii();
+}
+
+
+
+void BoardLayout::shiftLeft() {
+ for (int z=0; z<depth; z++) {
+ for (int y=0; y<height; y++) {
+ UCHAR keep=board[z][y][0];
+ if (keep == '1') {
+ // tile going off board, delete it
+ board[z][y][0]=0;
+ board[z][y][1]=0;
+ board[z][y+1][0]=0;
+ board[z][y+1][1]=0;
+ }
+ for (int x=0; x<width-1; x++) {
+ board[z][y][x] = board[z][y][x+1];
+ }
+ board[z][y][width-1] = 0;
+ }
+ }
+}
+
+
+void BoardLayout::shiftRight() {
+ for (int z=0; z<depth; z++) {
+ for (int y=0; y<height; y++) {
+ UCHAR keep=board[z][y][width-2];
+ if (keep == '1') {
+ // tile going off board, delete it
+ board[z][y][width-2]=0;
+ board[z][y][width-1]=0;
+ board[z][y+1][width-2]=0;
+ board[z][y+1][width-1]=0;
+ }
+ for (int x=width-1; x>0; x--) {
+ board[z][y][x] = board[z][y][x-1];
+ }
+ board[z][y][0] = 0;
+ }
+ }
+}
+void BoardLayout::shiftUp() {
+ for (int z=0; z<depth; z++) {
+ // remove tiles going off the top
+ for (int x=0; x<width; x++) {
+ if (board[z][0][x] == '1') {
+ board[z][0][x] = 0;
+ board[z][0][x+1] = 0;
+ board[z][1][x] = 0;
+ board[z][1][x+1] = 0;
+ }
+ }
+ }
+ for (int z=0; z<depth;z++) {
+ for (int y=0; y<height-1; y++) {
+ for (int x=0; x<width; x++) {
+ board[z][y][x]=board[z][y+1][x];
+ if (y == height-2)
+ board[z][y+1][x]=0;
+ }
+ }
+ }
+}
+
+
+void BoardLayout::shiftDown() {
+ for (int z=0; z<depth; z++) {
+ // remove tiles going off the top
+ for (int x=0; x<width; x++) {
+ if (board[z][height-2][x] == '1') {
+ board[z][height-2][x] = 0;
+ board[z][height-2][x+1] = 0;
+ board[z][height-1][x] = 0;
+ board[z][height-1][x+1] = 0;
+ }
+ }
+ }
+ for (int z=0; z<depth;z++) {
+ for (int y=height-1; y>0; y--) {
+ for (int x=0; x<width; x++) {
+ board[z][y][x]=board[z][y-1][x];
+ if (y == 1)
+ board[z][y-1][x]=0;
+ }
+ }
+ }
+}
+
+
+
+
+
+// is there a tile anywhere above here (top left to bot right quarter)
+bool BoardLayout::tileAbove(short z, short y, short x) {
+ if (z >= depth -1)
+ return false;
+ if( board[z+1][y][x] || board[z+1][y+1][x] || board[z+1][y][x+1] || board[z+1][y+1][x+1] ) {
+ return true;
+ }
+ return false;
+}
+
+
+bool BoardLayout::blockedLeftOrRight(short z, short y, short x) {
+ return( (board[z][y][x-1] || board[z][y+1][x-1]) &&
+ (board[z][y][x+2] || board[z][y+1][x+2]) );
+}
+
+void BoardLayout::deleteTile(POSITION &p) {
+ if ( p.e <depth && board[p.e][p.y][p.x] == '1') {
+ board[p.e][p.y][p.x]=0;
+ board[p.e][p.y][p.x+1]=0;
+ board[p.e][p.y+1][p.x]=0;
+ board[p.e][p.y+1][p.x+1]=0;
+ maxTileNum--;
+ }
+}
+
+bool BoardLayout::anyFilled(POSITION &p) {
+ return(board[p.e][ p.y][ p.x] != 0 ||
+ board[p.e][ p.y][ p.x+1] != 0 ||
+ board[p.e][ p.y+1][ p.x] != 0 ||
+ board[p.e][ p.y+1][ p.x+1] != 0);
+}
+bool BoardLayout::allFilled(POSITION &p) {
+ return(board[p.e][ p.y][ p.x] != 0 &&
+ board[p.e][p.y][ p.x+1] != 0 &&
+ board[p.e][p.y+1][ p.x] != 0 &&
+ board[p.e][p.y+1][ p.x+1] != 0);
+}
+void BoardLayout::insertTile(POSITION &p) {
+ board[p.e][p.y][p.x] = '1';
+ board[p.e][p.y][p.x+1] = '2';
+ board[p.e][p.y+1][p.x+1] = '3';
+ board[p.e][p.y+1][p.x] = '4';
+}
+
+
+
+
+
+
+
+
diff --git a/kmahjongg/BoardLayout.h b/kmahjongg/BoardLayout.h
new file mode 100644
index 00000000..60048bc9
--- /dev/null
+++ b/kmahjongg/BoardLayout.h
@@ -0,0 +1,62 @@
+#ifndef __BOARD__LAYOUT_
+#define __BOARD__LAYOUT_
+
+#include <qstring.h>
+#include "KmTypes.h"
+
+const QString layoutMagic1_0 = "kmahjongg-layout-v1.0";
+
+class BoardLayout {
+
+public:
+ BoardLayout();
+ ~BoardLayout();
+
+ bool loadBoardLayout(const QString from);
+ bool saveBoardLayout(const QString where);
+ UCHAR getBoardData(short z, short y, short x) {return board[z][y][x];}
+
+ // is there a tile anywhere above here (top left to bot right quarter)
+ bool tileAbove(short z, short y, short x);
+ bool tileAbove(POSITION &p) { return(tileAbove(p.e, p.y, p.x)); }
+
+ // is this tile blocked to the left or right
+ bool blockedLeftOrRight(short z, short y, short x);
+
+ void deleteTile(POSITION &p);
+
+ bool anyFilled(POSITION &p);
+ bool allFilled(POSITION &p);
+ void insertTile(POSITION &p);
+ bool isTileAt(POSITION &p) { return board[p.e][p.y][p.x] == '1'; }
+
+
+
+ const char *getBoardLayout();
+ void copyBoardLayout(UCHAR *to , unsigned short &numTiles);
+ void clearBoardLayout();
+ void shiftLeft();
+ void shiftRight();
+ void shiftUp();
+ void shiftDown();
+
+
+ enum { width = 32,
+ height = 16,
+ depth = 5 };
+ enum { maxTiles = (depth*width*height)/4 };
+
+ QString &getFilename() {return filename;}
+
+protected:
+
+ void initialiseBoard();
+
+private:
+ QString filename;
+ QString loadedBoard;
+ UCHAR board[depth][height][width];
+ unsigned short maxTileNum;
+};
+
+#endif
diff --git a/kmahjongg/ChangeLog b/kmahjongg/ChangeLog
new file mode 100644
index 00000000..dae64050
--- /dev/null
+++ b/kmahjongg/ChangeLog
@@ -0,0 +1,97 @@
+
+This is the change log for kmahjongg.
+
+Personel
+ Albert Astals Cid: Some bug fixes and wishlists
+ Michael Haertjens: Solvable game generation
+ David Black: Fold, spindle and mutilate 0.4.1 to 0.5.0
+ Osvaldo Stark: Tileset creator, doc guinepig and tester 0.5.0
+ Robert Williams: Bug fixes 0.4.0 to 0.4.1
+ Mathias Muller: Implementor of the original kmahjongg.
+
+0.7.4 to 0.7.6
++ Fix bug #73944
++ Implement wishlist #63171
++ Implement wishlist #56607
++ Fix bug #26595
+
+0.7.3 to 0.7.4
++ Fixed bug #31639 Kmahjongg flashes wrong tiles.
++ Fixed bug #26872 Kmahjongg timer not reset.
+
+0.7.2 to 0.7.3
++ Added ability to generate solvable games.
+ This includes a fair amount of code in kmahjongg.cpp, plus support
+ for a new entry in the Preferences dialog.
++ Place checkmark on Game menu when Demo Mode is active.
++ Change "Show Hiscores" to "Show High Scores" in Game Menu.
+
+0.7.0 to 0.7.2
+
++ Fixed bug in show matching tiles so that tiles on the top row and
+ the left -most column are detected in matched (the dreaded off by
+ one error. Fix provided by Robert Schroeter.
++ Fixed bug in board generation where tiles went missing. Fix
+ provided by Robert Schroeter.
++ Fixed bug where the default tileset was listed twice in the
+ preview dialog
++ Fixed bug in the board editor, which stopped you putting
+ tiles in completely legal places.
+
+Version 0.4.1 to 0.7.0 (beta)
+
++ Took over sources from Mathias Muller.
++ Changed tileset highlighting which previousely used a swap of two
+ fixed pallete colours, to a new tileset format where the designer
+ supplies the selected and unselected tile backgrounds.
++ Changed the main game engine to a 24 bit colour system. This removed
+ the old problem where the tileset and background combined could
+ only use a maximum of 128 colours. No limitations now exist. For
+ low color depths the game screen is dithered.
++ converted the rendering method to use pixmaps and blitting.
++ added a tool bar for commonly used features.
++ Added a tileset load feature (Yeah!!).
++ Added preferences dialog
++ Added preference to disable shadow generation (after complaints
+ about 3D visualisation.
++ Configuration and preferences now persist across sessions.
++ Tidied up transparency for tilesets. Now the top left colour of
+ the background tile determines the transparency colour, not a
+ fixed value of 0.
++ Added a hiscore system based on time taken, tiles removed and
+ penalties for using _cheats_
++ Added a game timer to the tool bar.
++ added a hiscore dialog.
++ Added a pause mode for hiscore play (blanks the screen because lets face it
+ people cheat).
++ Background images may be tiled or scalled. Saved as a preference
++ The main rendering functions are now independant of the tileset
+ metric. In future this will allow for variable size tiles.
++ Added preview dialogs for load background, tileset and game board,
+ now you can see what you will get.
++ Added a redo to compliment the undo.
++ Fixed up the menu system and added more accelerators.
++ Added a play with mini-tiles option. Not necessarily for everyone,
+ but some people (well at least one, and thats me, so there) use
+ kmahjongg on an 800x600 lcd laptop display. So this helps!
++ Added a show removed tiles option. This allows you to determine if
+ it is safe to remove a pair etc. Nice aid to game play.
++ Moved the file selectors over to the kde style. This should
+ allow urls to be supplied for tilesets, backgrounds and boards etc. In
+ future I hope to maintain a web page with new tilesets and layout etc.
+ Ultimately it would be nice to have a per-boardlayout internet hiscore
+ table (mayhaps in version 0.6)
++ Started documenting how to design tilesets etc.
++ Fixed a few bugs and introduced many (probably)
++ Added an embryonic board editor
++ Rewrote game generation for new board layout possibilities
++ Speed improvements for tile set load using fixed masks
++ High score now displays time as well as score (requested feature)
++ Plus lots of bits I forgot :-)
+
+
+
+Version original to 0.4.1
+* [Robert Williams] New games now start at 10
+* [Robert Williams] Added kapp->getHelpMenu()
+* [Robert Williams] Added -caption "%c" to kmahjongg.kdelnk
diff --git a/kmahjongg/Editor.cpp b/kmahjongg/Editor.cpp
new file mode 100644
index 00000000..4e141259
--- /dev/null
+++ b/kmahjongg/Editor.cpp
@@ -0,0 +1,675 @@
+
+#include <stdlib.h>
+#include <kapplication.h>
+#include <qlayout.h>
+#include <qpainter.h>
+
+#include "Editor.h"
+#include "prefs.h"
+
+#include <kmessagebox.h>
+#include <klocale.h> // Needed to use KLocale
+#include <kiconloader.h> //
+#include <kstandarddirs.h>
+#include <ktoolbarradiogroup.h>
+
+#define ID_TOOL_NEW 100
+#define ID_TOOL_LOAD 101
+#define ID_TOOL_SAVE 102
+#define ID_TOOL_ADD 103
+#define ID_TOOL_DEL 104
+#define ID_TOOL_MOVE 105
+#define ID_TOOL_SELECT 106
+#define ID_TOOL_CUT 107
+#define ID_TOOL_COPY 108
+#define ID_TOOL_PASTE 109
+#define ID_TOOL_LEFT 110
+#define ID_TOOL_RIGHT 111
+#define ID_TOOL_UP 112
+#define ID_TOOL_DOWN 113
+
+#define ID_TOOL_STATUS 199
+
+#define ID_META_EXIT 201
+
+
+
+// When we assign a tile to draw in a slot we do it in order from te following
+// table, wrapping on the tile number. It makes the tile layout look more
+// random.
+
+
+Editor::Editor
+(
+ QWidget* parent,
+ const char* name
+)
+ :
+ QDialog( parent, name, true, 0 ), tiles(false)
+{
+
+ clean= true;
+ numTiles=0;
+ mode = insert;
+
+ int sWidth = (BoardLayout::width+2)*(tiles.qWidth());
+ int sHeight =( BoardLayout::height+2)*tiles.qHeight();
+
+ sWidth += 4*tiles.shadowSize();
+
+ drawFrame = new FrameImage( this, "drawFrame" );
+ drawFrame->setGeometry( 10, 40 ,sWidth ,sHeight);
+ drawFrame->setMinimumSize( 0, 0 );
+ drawFrame->setMaximumSize( 32767, 32767 );
+ drawFrame->setFocusPolicy( QWidget::NoFocus );
+ drawFrame->setBackgroundMode( QWidget::PaletteBackground );
+ drawFrame->setFrameStyle( 49 );
+ drawFrame->setMouseTracking(true);
+
+ // setup the tool bar
+ setupToolbar();
+
+ QVBoxLayout *layout = new QVBoxLayout(this, 1);
+ layout->addWidget(topToolbar,0);
+ layout->addWidget(drawFrame,1);
+ layout->activate();
+
+ resize( sWidth+60, sHeight+60);
+ setMinimumSize( sWidth+60, sHeight+60);
+ setMaximumSize( sWidth+60, sHeight+60);
+
+ QString tile = Prefs::tileSet();
+ tiles.loadTileset(tile);
+
+ // tell the user what we do
+ setCaption(kapp->makeStdCaption(i18n("Edit Board Layout")));
+
+
+ connect( drawFrame, SIGNAL(mousePressed(QMouseEvent *) ),
+ SLOT(drawFrameMousePressEvent(QMouseEvent *)));
+ connect( drawFrame, SIGNAL(mouseMoved(QMouseEvent *) ),
+ SLOT(drawFrameMouseMovedEvent(QMouseEvent *)));
+
+ statusChanged();
+
+ update();
+}
+
+
+
+Editor::~Editor()
+{
+}
+
+// ---------------------------------------------------------
+void Editor::setupToolbar()
+{
+
+ KIconLoader *loader = KGlobal::iconLoader();
+ topToolbar = new KToolBar( this, "editToolBar" );
+ KToolBarRadioGroup *radio = new KToolBarRadioGroup(topToolbar);
+
+ // new game
+ topToolbar->insertButton(loader->loadIcon("filenew", KIcon::Toolbar),
+ ID_TOOL_NEW, true, i18n("New board"));
+ // open game
+ topToolbar->insertButton(loader->loadIcon("fileopen", KIcon::Toolbar),
+ ID_TOOL_LOAD, true, i18n("Open board"));
+ // save game
+ topToolbar->insertButton(loader->loadIcon("filesave", KIcon::Toolbar),
+ ID_TOOL_SAVE, true, i18n("Save board"));
+ topToolbar->setButtonIconSet(ID_TOOL_SAVE,loader->loadIconSet("filesave", KIcon::Toolbar));
+
+#ifdef FUTURE_OPTIONS
+ // Select
+ topToolbar->insertSeparator();
+ topToolbar->insertButton(loader->loadIcon("rectangle_select", KIcon::Toolbar),
+ ID_TOOL_SELECT, true, i18n("Select"));
+ topToolbar->insertButton(loader->loadIcon("editcut", KIcon::Toolbar),
+ ID_TOOL_CUT, true, i18n("Cut"));
+ topToolbar->insertButton(loader->loadIcon("editcopy", KIcon::Toolbar),
+ ID_TOOL_COPY, true, i18n("Copy"));
+ topToolbar->insertButton(loader->loadIcon("editpaste", KIcon::Toolbar),
+ ID_TOOL_PASTE, true, i18n("Paste"));
+
+ topToolbar->insertSeparator();
+ topToolbar->insertButton(loader->loadIcon("move", KIcon::Toolbar),
+ ID_TOOL_MOVE, true, i18n("Move tiles"));
+#endif
+ topToolbar->insertButton(loader->loadIcon("pencil", KIcon::Toolbar),
+ ID_TOOL_ADD, true, i18n("Add tiles"));
+ topToolbar->insertButton(loader->loadIcon("editdelete", KIcon::Toolbar),
+ ID_TOOL_DEL, true, i18n("Remove tiles"));
+
+ topToolbar->setToggle(ID_TOOL_ADD);
+ topToolbar->setToggle(ID_TOOL_MOVE);
+ topToolbar->setToggle(ID_TOOL_DEL);
+ topToolbar->toggleButton(ID_TOOL_ADD);
+ radio->addButton(ID_TOOL_ADD);
+#ifdef FUTURE_OPTIONS
+ radio->addButton(ID_TOOL_MOVE);
+#endif
+ radio->addButton(ID_TOOL_DEL);
+
+ // board shift
+
+ topToolbar->insertSeparator();
+ topToolbar->insertButton(loader->loadIcon("back", KIcon::Toolbar),
+ ID_TOOL_LEFT, true, i18n("Shift left"));
+ topToolbar->insertButton(loader->loadIcon("up", KIcon::Toolbar),
+ ID_TOOL_UP, true, i18n("Shift up"));
+ topToolbar->insertButton(loader->loadIcon("down", KIcon::Toolbar),
+ ID_TOOL_DOWN, true, i18n("Shift down"));
+ topToolbar->insertButton(loader->loadIcon("forward", KIcon::Toolbar),
+ ID_TOOL_RIGHT, true, i18n("Shift right"));
+
+ topToolbar->insertSeparator();
+ topToolbar->insertButton(loader->loadIcon("exit", KIcon::Toolbar),
+ ID_META_EXIT, true, i18n("Exit"));
+
+ // status in the toolbar for now (ick)
+
+ theLabel = new QLabel(statusText(), topToolbar);
+ int lWidth = theLabel->sizeHint().width();
+
+ topToolbar->insertWidget(ID_TOOL_STATUS,lWidth, theLabel );
+ topToolbar->alignItemRight( ID_TOOL_STATUS, true );
+
+ //addToolBar(topToolbar);
+ connect( topToolbar, SIGNAL(clicked(int) ), SLOT( topToolbarOption(int) ) );
+
+ topToolbar->updateRects(0);
+ topToolbar->setFullSize(true);
+ topToolbar->setBarPos(KToolBar::Top);
+// topToolbar->enableMoving(false);
+ topToolbar->adjustSize();
+ setMinimumWidth(topToolbar->width());
+
+
+}
+
+void Editor::statusChanged() {
+ bool canSave = ((numTiles !=0) && ((numTiles & 1) == 0));
+ theLabel->setText(statusText());
+ topToolbar->setItemEnabled( ID_TOOL_SAVE, canSave);
+}
+
+
+void Editor::topToolbarOption(int option) {
+
+ switch(option) {
+ case ID_TOOL_NEW:
+ newBoard();
+ break;
+ case ID_TOOL_LOAD:
+ loadBoard();
+ break;
+ case ID_TOOL_SAVE:
+ saveBoard();
+ break;
+ case ID_TOOL_LEFT:
+ theBoard.shiftLeft();
+ repaint(false);
+ break;
+ case ID_TOOL_RIGHT:
+ theBoard.shiftRight();
+ repaint(false);
+ break;
+ case ID_TOOL_UP:
+ theBoard.shiftUp();
+ repaint(false);
+ break;
+ case ID_TOOL_DOWN:
+ theBoard.shiftDown();
+ repaint(false);
+ break;
+ case ID_TOOL_DEL:
+ mode=remove;
+ break;
+
+ case ID_TOOL_MOVE:
+ mode=move;
+ break;
+
+ case ID_TOOL_ADD:
+ mode = insert;
+ break;
+ case ID_META_EXIT:
+ close();
+ break;
+
+ default:
+
+ break;
+ }
+
+}
+
+QString Editor::statusText() {
+ QString buf;
+
+ int x=currPos.x;
+ int y=currPos.y;
+ int z= currPos.e;
+
+ if (z == 100)
+ z = 0;
+ else
+ z=z+1;
+
+ if (x >=BoardLayout::width || x <0 || y >=BoardLayout::height || y <0)
+ x = y = z = 0;
+
+ buf = i18n("Tiles: %1 Pos: %2,%3,%4").arg(numTiles).arg(x).arg(y).arg(z);
+ return buf;
+}
+
+
+void Editor::loadBoard() {
+
+ if ( !testSave() )
+ return;
+
+ KURL url = KFileDialog::getOpenURL(
+ NULL,
+ i18n("*.layout|Board Layout (*.layout)\n"
+ "*|All Files"),
+ this,
+ i18n("Open Board Layout" ));
+
+ if ( url.isEmpty() )
+ return;
+
+
+ theBoard.loadBoardLayout( url.path() );
+
+ repaint(false);
+}
+
+
+// Clear out the contents of the board. Repaint the screen
+// set values to their defaults.
+void Editor::newBoard() {
+ if (!testSave())
+ return;
+
+
+
+ theBoard.clearBoardLayout();
+
+
+
+ clean=true;
+ numTiles=0;
+ statusChanged();
+ repaint(false);
+}
+
+bool Editor::saveBoard() {
+ // get a save file name
+ KURL url = KFileDialog::getSaveURL(
+ NULL,
+ i18n("*.layout|Board Layout (*.layout)\n"
+ "*|All Files"),
+ this,
+ i18n("Save Board Layout" ));
+ if( url.isEmpty() ) return false;
+ if( !url.isLocalFile() )
+ {
+ KMessageBox::sorry( this, i18n( "Only saving to local files currently supported." ) );
+ return false;
+ }
+
+ if ( url.isEmpty() )
+ return false;
+
+ QFileInfo f( url.path() );
+ if ( f.exists() ) {
+ // if it already exists, querie the user for replacement
+ int res=KMessageBox::warningContinueCancel(this,
+ i18n("A file with that name "
+ "already exists. Do you "
+ "wish to overwrite it?"),
+ i18n("Save Board Layout" ), KStdGuiItem::save());
+ if (res != KMessageBox::Continue)
+ return false;
+ }
+
+ bool result = theBoard.saveBoardLayout( url.path() );
+ if (result==true){
+ clean = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+// test if a save is required and return true if the app is to continue
+// false if cancel is selected. (if ok then call out to save the board
+bool Editor::testSave()
+{
+
+ if (clean)
+ return(true);
+
+ int res;
+ res=KMessageBox::warningYesNoCancel(this,
+ i18n("The board has been modified. Would you "
+ "like to save the changes?"),QString::null,KStdGuiItem::save(),KStdGuiItem::dontSave());
+
+ if (res == KMessageBox::Yes) {
+ // yes to save
+ if (saveBoard()) {
+ return true;
+ } else {
+ KMessageBox::sorry(this, i18n("Save failed. Aborting operation."));
+ }
+ } else {
+ return (res != KMessageBox::Cancel);
+ }
+ return(true);
+}
+
+
+// The main paint event, draw in the grid and blit in
+// the tiles as specified by the layout.
+
+void Editor::paintEvent( QPaintEvent* ) {
+
+
+ // first we layer on a background grid
+ QPixmap buff;
+ QPixmap *dest=drawFrame->getPreviewPixmap();
+ buff.resize(dest->width(), dest->height());
+ drawBackground(&buff);
+ drawTiles(&buff);
+ bitBlt(dest, 0,0,&buff, 0,0,buff.width(), buff.height(), CopyROP);
+
+ drawFrame->repaint(false);
+}
+
+void Editor::drawBackground(QPixmap *pixmap) {
+
+ QPainter p(pixmap);
+
+ // blast in a white background
+ p.fillRect(0,0,pixmap->width(), pixmap->height(), QColor(white));
+
+
+ // now put in a grid of tile quater width squares
+ int sy = (tiles.height()/2)+tiles.shadowSize();
+ int sx = (tiles.width()/2);
+
+ for (int y=0; y<=BoardLayout::height; y++) {
+ int nextY=sy+(y*tiles.qHeight());
+ p.drawLine(sx, nextY,sx+(BoardLayout::width*tiles.qWidth()), nextY);
+ }
+
+ for (int x=0; x<=BoardLayout::width; x++) {
+ int nextX=sx+(x*tiles.qWidth());
+ p.drawLine(nextX, sy, nextX, sy+BoardLayout::height*tiles.qHeight());
+ }
+}
+
+void Editor::drawTiles(QPixmap *dest) {
+
+ QPainter p(dest);
+
+ QString tile1 = Prefs::tileSet();
+ tiles.loadTileset(tile1);
+
+
+ int xOffset = tiles.width()/2;
+ int yOffset = tiles.height()/2;
+ short tile = 0;
+
+ // we iterate over the depth stacking order. Each successive level is
+ // drawn one indent up and to the right. The indent is the width
+ // of the 3d relief on the tile left (tile shadow width)
+ for (int z=0; z<BoardLayout::depth; z++) {
+ // we draw down the board so the tile below over rights our border
+ for (int y = 0; y < BoardLayout::height; y++) {
+ // drawing right to left to prevent border overwrite
+ for (int x=BoardLayout::width-1; x>=0; x--) {
+ int sx = x*(tiles.qWidth() )+xOffset;
+ int sy = y*(tiles.qHeight() )+yOffset;
+ if (theBoard.getBoardData(z, y, x) != '1') {
+ continue;
+ }
+ QPixmap *t;
+ tile=(z*BoardLayout::depth)+
+ (y*BoardLayout::height)+
+ (x*BoardLayout::width);
+// if (mode==remove && currPos.x==x && currPos.y==y && currPos.e==z) {
+// t = tiles.selectedPixmaps(44));
+// } else {
+ t = tiles.unselectedPixmaps(43);
+// }
+
+ // Only one compilcation. Since we render top to bottom , left
+ // to right situations arise where...:
+ // there exists a tile one q height above and to the left
+ // in this situation we would draw our top left border over it
+ // we simply split the tile draw so the top half is drawn
+ // minus border
+ if ((x>1) && (y>0) && theBoard.getBoardData(z,y-1,x-2)=='1'){
+
+ bitBlt( dest,
+ sx+tiles.shadowSize(), sy,
+ t, tiles.shadowSize() ,0,
+ t->width()-tiles.shadowSize(),
+ t->height()/2, CopyROP );
+
+
+ bitBlt( dest, sx, sy+t->height()/2,
+ t, 0,t->height()/2,t->width(),t->height()/2,CopyROP);
+ } else {
+
+ bitBlt( dest, sx, sy,
+ t, 0,0, t->width(), t->height(), CopyROP );
+ }
+
+
+ tile++;
+ tile = tile % 143;
+ }
+ }
+ xOffset +=tiles.shadowSize();
+ yOffset -=tiles.shadowSize();
+ }
+}
+
+
+// convert mouse position on screen to a tile z y x coord
+// different to the one in kmahjongg.cpp since if we hit ground
+// we return a result too.
+
+void Editor::transformPointToPosition(
+ const QPoint& point,
+ POSITION& MouseClickPos,
+ bool align)
+{
+
+ short z = 0; // shut the compiler up about maybe uninitialised errors
+ short y = 0;
+ short x = 0;
+ MouseClickPos.e = 100;
+
+ // iterate over z coordinate from top to bottom
+ for( z=BoardLayout::depth-1; z>=0; z-- )
+ {
+ // calculate mouse coordiantes --> position in game board
+ // the factor -theTiles.width()/2 must keep track with the
+ // offset for blitting in the print zvent (FIX ME)
+ x = ((point.x()-tiles.width()/2)-(z+1)*tiles.shadowSize())/ tiles.qWidth();
+ y = ((point.y()-tiles.height()/2)+ z*tiles.shadowSize()) / tiles.qHeight();
+
+
+ // skip when position is illegal
+ if (x<0 || x>=BoardLayout::width || y<0 || y>=BoardLayout::height)
+ continue;
+
+ //
+ switch( theBoard.getBoardData(z,y,x) )
+ {
+ case (UCHAR)'3': if (align) {
+ x--;
+ y--;
+ }
+ break;
+
+ case (UCHAR)'2': if (align)
+ x--;
+ break;
+
+ case (UCHAR)'4': if (align)
+ y--;
+ break;
+
+ case (UCHAR)'1': break;
+
+ default : continue;
+ }
+ // if gameboard is empty, skip
+ if ( ! theBoard.getBoardData(z,y,x) )
+ continue;
+
+ // here, position is legal
+ MouseClickPos.e = z;
+ MouseClickPos.y = y;
+ MouseClickPos.x = x;
+ MouseClickPos.f = theBoard.getBoardData(z,y,x);
+ break;
+ }
+ if (MouseClickPos.e == 100) {
+ MouseClickPos.x = x;
+ MouseClickPos.y = y;
+ MouseClickPos.f=0;
+ }
+}
+
+
+// we swallow the draw frames mouse clicks and process here
+void Editor::drawFrameMousePressEvent( QMouseEvent* e )
+{
+
+ POSITION mPos;
+ transformPointToPosition(e->pos(), mPos, (mode == remove));
+
+ switch (mode) {
+ case remove:
+ if (!theBoard.tileAbove(mPos) && mPos.e < BoardLayout::depth && theBoard.isTileAt(mPos) ) {
+ theBoard.deleteTile(mPos);
+ numTiles--;
+ statusChanged();
+ drawFrameMouseMovedEvent(e);
+ repaint(false);
+ }
+ break;
+ case insert: {
+ POSITION n = mPos;
+ if (n.e == 100)
+ n.e = 0;
+ else
+ n.e += 1;
+ if (canInsert(n)) {
+ theBoard.insertTile(n);
+ numTiles++;
+ statusChanged();
+ repaint(false);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+}
+
+
+void Editor::drawCursor(POSITION &p, bool visible)
+{
+ int x = (tiles.width()/2)+(p.e*tiles.shadowSize())+(p.x * tiles.qWidth());
+ int y = (tiles.height()/2)-(p.e*tiles.shadowSize())+(p.y * tiles.qHeight());
+ int w = tiles.width();
+ int h = tiles.height();
+
+
+ if (p.e==100 || !visible)
+ x = -1;
+ drawFrame->setRect(x,y,w,h, tiles.shadowSize(), mode-remove);
+ drawFrame->repaint(false);
+
+
+
+}
+
+
+
+// we swallow the draw frames mouse moves and process here
+void Editor::drawFrameMouseMovedEvent( QMouseEvent* e ){
+
+
+ POSITION mPos;
+ transformPointToPosition(e->pos(), mPos, (mode == remove));
+
+ if ((mPos.x==currPos.x) && (mPos.y==currPos.y) && (mPos.e==currPos.e))
+ return;
+ currPos = mPos;
+
+ statusChanged();
+
+ switch(mode) {
+ case insert: {
+ POSITION next;
+ next = currPos;
+ if (next.e == 100)
+ next.e = 0;
+ else
+ next.e += 1;
+
+ drawCursor(next, canInsert(next));
+ break;
+ }
+ case remove:
+ drawCursor(currPos, 1);
+ break;
+
+ case move:
+
+ break;
+
+ }
+
+}
+
+// can we inser a tile here. We can iff
+// there are tiles in all positions below us (or we are a ground level)
+// there are no tiles intersecting with us on this level
+
+bool Editor::canInsert(POSITION &p) {
+
+
+ if (p.e >= BoardLayout::depth)
+ return (false);
+ if (p.y >BoardLayout::height-2)
+ return false;
+ if (p.x >BoardLayout::width-2)
+ return false;
+
+ POSITION n = p;
+ if (p.e != 0) {
+ n.e -= 1;
+ if (!theBoard.allFilled(n)) {
+ return(false);
+ }
+ }
+ int any = theBoard.anyFilled(p);
+ return(!any);
+
+}
+
+
+
+#include "Editor.moc"
diff --git a/kmahjongg/Editor.h b/kmahjongg/Editor.h
new file mode 100644
index 00000000..5ced0daf
--- /dev/null
+++ b/kmahjongg/Editor.h
@@ -0,0 +1,68 @@
+#ifndef _EditorLoadBase_H
+#define _EditorLoadBase_H
+
+#include <qdialog.h>
+#include <qframe.h>
+#include <ktoolbar.h>
+#include <kstatusbar.h>
+#include <kfiledialog.h>
+
+#include "Tileset.h"
+#include "BoardLayout.h"
+#include "Background.h"
+
+#include "Preview.h"
+
+class Editor: public QDialog
+{
+ Q_OBJECT
+
+public:
+
+
+ Editor
+ (
+ QWidget* parent = NULL,
+ const char* name = NULL
+ );
+
+ virtual ~Editor();
+
+
+
+protected slots:
+ void topToolbarOption(int w);
+ void drawFrameMousePressEvent ( QMouseEvent* );
+ void drawFrameMouseMovedEvent ( QMouseEvent *);
+
+
+protected:
+ enum {remove=98, insert=99, move=100};
+ void paintEvent( QPaintEvent* pa );
+ void setupToolbar();
+ void loadBoard();
+ bool saveBoard();
+ void newBoard();
+ void drawBackground(QPixmap *to);
+ void drawTiles(QPixmap *to);
+ bool testSave();
+ void transformPointToPosition(const QPoint &, POSITION &, bool align);
+ void drawCursor(POSITION &p, bool visible);
+ bool canInsert(POSITION &p);
+ void statusChanged();
+ QString statusText();
+private:
+ int mode;
+ int numTiles;
+ KToolBar *topToolbar;
+ FrameImage * drawFrame;
+ Tileset tiles;
+ BoardLayout theBoard;
+ bool clean;
+ POSITION currPos;
+ QLabel *theLabel;
+private:
+
+};
+
+#endif
diff --git a/kmahjongg/GameTimer.cpp b/kmahjongg/GameTimer.cpp
new file mode 100644
index 00000000..5eb827d9
--- /dev/null
+++ b/kmahjongg/GameTimer.cpp
@@ -0,0 +1,94 @@
+/*
+ * Based upon the QT example dclock
+ */
+
+#include <stdio.h>
+#include "GameTimer.h"
+#include "GameTimer.moc"
+
+
+//
+// Constructs a GameTimer widget with a parent and a name.
+//
+
+GameTimer::GameTimer( QWidget *parent, const char *name )
+ : QLCDNumber( parent, name )
+{
+ showingColon = false;
+ setNumDigits(7);
+ setFrameStyle(QFrame::Panel | QFrame::Sunken);
+ setFrameStyle(QFrame::NoFrame);
+ timerMode = stopped;
+ showTime(); // display the current time1
+ startTimer( 500 ); // 1/2 second timer events
+}
+
+// QObject timer call back implementation
+void GameTimer::timerEvent( QTimerEvent * )
+{
+ if (timerMode == running)
+ theTimer=theTimer.addMSecs(500);
+ showTime();
+}
+
+
+//
+// Shows the current time in the internal lcd widget.
+//
+
+void GameTimer::showTime()
+{
+ QString s;
+ showingColon = !showingColon; // toggle/blink colon
+
+ switch(timerMode) {
+ case paused:
+ case running:
+ s = theTimer.toString();
+ break;
+ case stopped:
+ s = "00:00:00";
+ break;
+ }
+
+ if ( !showingColon )
+ s[2] = s[5] = ' ';
+ display( s ); // set LCD number/text
+}
+
+void GameTimer::start() {
+ theTimer.setHMS(0,0,0);
+ timerMode = running;
+}
+
+
+void GameTimer::fromString(const char*tim) {
+ int h,m,s;
+ sscanf(tim, "%2d:%2d:%2d\n", &h, &m, &s);
+ theTimer.setHMS(h,m,s);
+ timerMode = running;
+}
+
+
+void GameTimer::stop() {
+ timerMode = stopped;
+}
+
+void GameTimer::pause() {
+
+ if (timerMode == stopped)
+ return;
+
+ if (timerMode == paused) {
+ timerMode = running;
+ } else {
+ timerMode = paused;
+ }
+}
+
+int GameTimer::toInt() {
+
+ return (theTimer.second()+
+ theTimer.minute()*60+
+ theTimer.hour()*360);
+}
diff --git a/kmahjongg/GameTimer.h b/kmahjongg/GameTimer.h
new file mode 100644
index 00000000..6cde5d11
--- /dev/null
+++ b/kmahjongg/GameTimer.h
@@ -0,0 +1,53 @@
+/* -------------------------------------------------------------------------
+ -- kmahjongg timer. Based on a slightly modified verion of the QT demo --
+ -- program dclock. Copyright as shown below. --
+ ------------------------------------------------------------------------- */
+
+/****************************************************************************
+** $Id$
+**
+** Copyright (C) 1992-1998 Troll Tech AS. All rights reserved.
+**
+** This file is part of an example program for Qt. This example
+** program may be used, distributed and modified without limitation.
+**
+*****************************************************************************/
+
+#ifndef KM_GAME_TIMER
+#define KM_GAME_TIMER
+
+#include <qlcdnumber.h>
+#include <qdatetime.h>
+
+enum TimerMode {running = -53 , stopped= -54 , paused = -55};
+
+class GameTimer: public QLCDNumber
+{
+ Q_OBJECT
+public:
+ GameTimer( QWidget *parent=0, const char *name=0 );
+
+ int toInt();
+ QString toString() {return theTimer.toString();}
+ void fromString(const char *);
+
+protected: // event handlers
+ void timerEvent( QTimerEvent * );
+
+public slots:
+ void start();
+ void stop();
+ void pause();
+
+
+private slots: // internal slots
+ void showTime();
+
+private: // internal data
+ bool showingColon;
+ QTime theTimer;
+ TimerMode timerMode;
+};
+
+
+#endif
diff --git a/kmahjongg/HighScore.cpp b/kmahjongg/HighScore.cpp
new file mode 100644
index 00000000..2e55c15a
--- /dev/null
+++ b/kmahjongg/HighScore.cpp
@@ -0,0 +1,541 @@
+#include <unistd.h>
+#include "HighScore.h"
+#include "HighScore.moc"
+
+
+#include <qlabel.h>
+#include <qfileinfo.h>
+#include <kmessagebox.h>
+#include "klocale.h"
+#include <kstandarddirs.h>
+#include <kiconloader.h>
+#include <qlineedit.h>
+#include <qcombobox.h>
+#include <kapplication.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+
+static const QString highScoreMagic1_0 = "kmahjongg-scores-v1.0";
+static const QString highScoreMagic1_1 = "kmahjongg-scores-v1.1";
+
+static const char * highScoreFilename = "/kmahjonggHiscores";
+
+const char * defNames[numScores] = {
+ "David Black",
+ "Mathias Mueller",
+ "Osvaldo Stark",
+ "Steve Taylor",
+ "Clare Brizzolara",
+ "Angela Simpson",
+ "Michael O'Brien",
+ "Kelvin Bell",
+ "Jenifferi O'Keeffe",
+ "Phil Lamdin"
+};
+
+int defScores[numScores] =
+ {400, 350, 300, 250, 200, 150, 100, 50, 20, 10};
+
+const int ages = 59+(59*60)+(2*60*60);
+int defTimes[numScores] = {ages, ages-1, ages-2, ages-3,
+ ages-4, ages-5, ages-6, ages-7, ages-8, ages-9};
+
+
+HighScore::HighScore
+(
+ QWidget* parent,
+ const char* name
+)
+ :
+ QDialog( parent, name, true, 0 )
+{
+
+ // form the target name
+
+
+ filename = locateLocal("appdata", highScoreFilename);
+
+ QFont fnt;
+ // Number
+ QLabel* qtarch_Label_3;
+ qtarch_Label_3 = new QLabel( this, "Label_3" );
+ qtarch_Label_3->setGeometry( 10, 45, 30, 30 );
+ qtarch_Label_3->setFrameStyle( 50 );
+ qtarch_Label_3->setText( i18n("Pos") );
+ qtarch_Label_3->setAlignment( AlignCenter );
+ fnt = qtarch_Label_3->font();
+ fnt.setBold(true);
+ qtarch_Label_3->setFont(fnt);
+
+
+ // name
+
+ QLabel* qtarch_Label_4;
+ qtarch_Label_4 = new QLabel( this, "Label_4" );
+ qtarch_Label_4->setGeometry( 40, 45, 150, 30 );
+ qtarch_Label_4->setFrameStyle( 50 );
+ qtarch_Label_4->setText( i18n("Name") );
+ qtarch_Label_4->setFont(fnt);
+
+
+ // board number
+ QLabel* boardTitle;
+ boardTitle= new QLabel( this, "" );
+ boardTitle->setGeometry( 190, 45, 80, 30 );
+ boardTitle->setFrameStyle( 50 );
+ boardTitle->setText( i18n("Board") );
+ boardTitle->setFont(fnt);
+
+ // score
+ QLabel* qtarch_Label_5;
+ qtarch_Label_5 = new QLabel( this, "Label_5" );
+ qtarch_Label_5->setGeometry( 270, 45, 70, 30 );
+ qtarch_Label_5->setFrameStyle( 50 );
+ qtarch_Label_5->setText( i18n("Score") );
+ qtarch_Label_5->setFont(fnt);
+
+ // time
+ QLabel* qtarch_Label_6;
+ qtarch_Label_6 = new QLabel( this, "Label_6" );
+ qtarch_Label_6->setGeometry( 340, 45, 70, 30 );
+ qtarch_Label_6->setFrameStyle( 50 );
+ qtarch_Label_6->setText( i18n("Time") );
+ qtarch_Label_6->setFont(fnt);
+
+
+
+ for (int row=0; row<numScores; row++)
+ addRow(row);
+
+ QPushButton* qtarch_PushButton_1;
+ qtarch_PushButton_1 = new KPushButton( KStdGuiItem::ok(), this, "PushButton_1" );
+ qtarch_PushButton_1->setGeometry( 110+35, 340+50, 100, 30 );
+ qtarch_PushButton_1->setMinimumSize( 0, 0 );
+ qtarch_PushButton_1->setMaximumSize( 32767, 32767 );
+ qtarch_PushButton_1->setFocusPolicy( QWidget::TabFocus );
+ qtarch_PushButton_1->setAutoRepeat( false );
+ qtarch_PushButton_1->setAutoResize( false );
+ qtarch_PushButton_1->setDefault(true);
+
+ QPushButton* resetBtn;
+ resetBtn= new QPushButton( this, "resetBtn" );
+ resetBtn->setGeometry( 10, 5, 25, 25);
+ resetBtn->setMinimumSize( 0, 0 );
+ resetBtn->setMaximumSize( 32767, 32767 );
+ resetBtn->setFocusPolicy( QWidget::TabFocus );
+ //resetBtn->setText(i18n( "Reset" ));
+ resetBtn->setAutoRepeat( false );
+ resetBtn->setAutoResize( false );
+
+ KIconLoader *loader = KGlobal::iconLoader();
+ resetBtn->setPixmap(loader->loadIcon("editdelete", KIcon::Toolbar));
+
+
+
+ /* We create the ediat area for the hi score name entry and move it */
+ /* off screen. it is moved over and placed in position when a */
+ /* new name is added */
+
+ lineEdit = new QLineEdit(this, "");
+ lineEdit->setGeometry( 50, 40+(20*30), 190, 30 );
+ lineEdit->setFocusPolicy(QWidget::StrongFocus);
+ lineEdit->setFrame(true);
+ lineEdit->setEchoMode(QLineEdit::Normal);
+ lineEdit->setText("");
+
+ // the drop down for the board names
+
+ combo = new QComboBox( false, this, "combo" );
+ combo->setGeometry( 65, 5, 220, 25 );
+ combo->setMinimumSize( 0, 0 );
+ combo->setMaximumSize( 32767, 32767 );
+ combo->setFocusPolicy( QWidget::StrongFocus );
+ combo->setSizeLimit( 10 );
+ combo->setAutoResize( false );
+ connect( combo, SIGNAL(activated(int)), SLOT(selectionChanged(int)) );
+
+
+ resize( 350+70,390+45 );
+ setFixedSize(350+70,390+45);
+
+ tables = NULL;
+ loadTables();
+ currTable = tables;
+
+ setCaption(kapp->makeStdCaption(i18n("Scores")));
+
+ selectedLine = -1;
+
+ connect(lineEdit, SIGNAL( textChanged(const QString &)),
+ SLOT( nameChanged(const QString &)));
+
+
+ connect(qtarch_PushButton_1, SIGNAL(clicked()), SLOT(reject()));
+ connect(resetBtn, SIGNAL(clicked()), SLOT(reset()));
+}
+
+// free up the table structures
+
+HighScore::~HighScore()
+{
+ TableInstance *t, *t1;
+
+ if (tables != NULL) {
+ t = tables;
+ while(t != NULL)
+ {
+ t1 = t;
+ t = t -> next;
+ delete t1;
+ }
+ }
+ tables = NULL;
+}
+
+
+
+// return a pointer to a linked list of table entries from the
+// saved hi score tables file. If no such file exists then
+// return a single default hiscore table.
+
+void HighScore::loadTables() {
+ char buff[1024];
+
+ // open the file, on error set up the default table
+ FILE *fp = fopen( QFile::encodeName(highScoreFile()), "r");
+ if (fp == NULL)
+ goto error;
+
+ // check magic
+ fscanf(fp, "%1023s\n", buff);
+ if (highScoreMagic1_1 != buff) {
+ goto error;
+ }
+
+ int num;
+ fscanf(fp, "%d\n", &num);
+
+ for (int p=0; p<num; p++) {
+ TableInstance *t = new TableInstance;
+ t->next = tables;
+
+
+ tables = t;
+
+ fgets(buff, sizeof(buff), fp);
+ if (buff[strlen(buff)-1] == '\n')
+ buff[strlen(buff)-1] = '\0';
+ t->name = buff;
+ combo->insertItem(t->name);
+ setComboTo(t->name);
+ for (int e=0; e<numScores; e++) {
+ fscanf(fp, "%ld\n", &(t->entries[e].score));
+ fscanf(fp, "%ld\n", &(t->entries[e].elapsed));
+ fscanf(fp, "%ld\n", &(t->entries[e].board));
+ fgets(buff, sizeof(buff), fp);
+ if (buff[strlen(buff)-1] == '\n')
+ buff[strlen(buff)-1] = '\0';
+ t->entries[e].name=QString::fromUtf8(buff,-1);
+ }
+ }
+
+
+ fclose(fp);
+ return;
+
+
+error:
+ selectTable("default");
+ saveTables();
+ return;
+}
+
+void HighScore::saveTables() {
+
+ TableInstance *p;
+ int num = 0;
+
+ // Nothing to do
+ if (tables == NULL)
+ return;
+
+
+ // open the outrput file, with naff error handling
+ FILE *fp = fopen( QFile::encodeName(highScoreFile()), "w");
+ if (fp == NULL)
+ return;
+
+ // count up the number of tables to save
+ for (p=tables; p != NULL; p = p->next)
+ num++;
+
+ // output the file magic
+ fprintf(fp,"%s\n", highScoreMagic1_1.utf8().data());
+
+ // output the count of tables to process
+ fprintf(fp, "%d\n", num);
+
+ // output each table
+ for (p=tables; p != NULL; p = p->next) {
+ fprintf(fp, "%s\n", p->name.utf8().data());
+ for (int e=0; e<numScores; e++) {
+ fprintf(fp,"%ld\n%ld\n%ld\n%s\n",
+ p->entries[e].score,
+ p->entries[e].elapsed,
+ p->entries[e].board,
+ p->entries[e].name.utf8().data());
+ }
+ }
+ fclose(fp);
+
+}
+
+// traverse the list of hi score tables and set the
+// current table to the specified board. Create it if it does not
+// exist.
+
+void HighScore::selectTable(const QString &board) {
+
+ TableInstance *pos = tables;
+
+
+ while (pos != NULL) {
+ if (pos->name == board)
+ break;
+ pos = pos->next;
+ }
+
+
+
+ if (pos == NULL) {
+ // not found, add new board to the front of the list
+ TableInstance *n = new TableInstance;
+ n->next = tables;
+ n->name = board;
+
+
+
+ for (int p =0; p < numScores; p ++) {
+ n->entries[p].name = defNames[p];
+ n->entries[p].score = defScores[p];
+ n->entries[p].board = 928364243l+(p *3);
+ n->entries[p].elapsed = defTimes[p];
+ }
+ tables = n;
+ currTable = n;
+ combo->insertItem(board);
+ setComboTo(board);
+ return;
+ }
+ currTable = pos;
+ setComboTo(board);
+ return;
+}
+
+
+
+void HighScore::addRow(int num) {
+ QFont tmp;
+
+ // game number
+ numbersWidgets[num] = new QLabel( this);
+ numbersWidgets[num]->setGeometry( 10, 75+(num*30), 30, 30 );
+ numbersWidgets[num]->setFrameStyle( 50 );
+ numbersWidgets[num]->setAlignment( AlignRight | AlignVCenter );
+ numbersWidgets[num]->setNum(num+1);
+
+
+ // name
+ namesWidgets[num] = new QLabel( this);
+ namesWidgets[num]->setGeometry( 40, 75+(num*30), 150, 30 );
+ namesWidgets[num]->setFrameStyle( 50 );
+ namesWidgets[num]->setAlignment( 289 );
+
+ // board
+ boardWidgets[num] = new QLabel( this);
+ boardWidgets[num]->setGeometry( 190, 75+(num*30), 80, 30 );
+ boardWidgets[num]->setFrameStyle( 50 );
+ boardWidgets[num]->setAlignment( 289 );
+
+ // score
+ scoresWidgets[num] = new QLabel( this);
+ scoresWidgets[num]->setGeometry( 270, 75+(num*30), 70, 30 );
+ scoresWidgets[num]->setFrameStyle( 50 );
+ tmp = scoresWidgets[num]->font();
+ tmp.setItalic(true);
+ scoresWidgets[num]->setFont(tmp);
+
+ // elapsed time
+ elapsedWidgets[num] = new QLabel( this);
+ elapsedWidgets[num]->setGeometry( 270+70, 75+(num*30), 70, 30 );
+ elapsedWidgets[num]->setFrameStyle( 50 );
+ tmp = elapsedWidgets[num]->font();
+ tmp.setItalic(true);
+ elapsedWidgets[num]->setFont(tmp);
+
+
+}
+
+void HighScore::copyTableToScreen(const QString &name) {
+ char buff[256];
+ QString base;
+ getBoardName(name, base);
+ selectTable(base);
+ for (int p=0; p<numScores;p++) {
+ scoresWidgets[p]->setNum((int)currTable->entries[p].score);
+ namesWidgets[p]->setText(currTable->entries[p].name);
+ boardWidgets[p]->setNum((int)currTable->entries[p].board);
+
+ int e = currTable->entries[p].elapsed;
+ int s = e % 60;
+ e = e-s;
+ int m = (e % (60*60)) / 60;
+ e = e - (e % (60*60));
+ int h = (e % (60*60*60)) / (60*60);
+ sprintf(buff, "%2.2d:%2.2d:%2.2d", h, m , s);
+ elapsedWidgets[p]->setText(buff);
+
+ }
+ repaint(false);
+}
+
+int HighScore::exec(QString &layout) {
+ copyTableToScreen(layout);
+ return(QDialog::exec());
+}
+
+void HighScore::checkHighScore(int s, int e, long gameNum, QString &name) {
+ int pos;
+
+ QString board;
+ getBoardName(name, board);
+
+ // make this board name the current one!
+ // creates it if it does not exist
+ selectTable(board);
+
+
+ for (pos=0; pos <numScores; pos++) {
+ if (s > currTable->entries[pos].score) {
+ break;
+ }
+ }
+ if (pos >= numScores) {
+ return;
+ }
+ for (int move= numScores-1; move >pos; move--) {
+ currTable->entries[move].score = currTable->entries[move-1].score;
+ currTable->entries[move].name = currTable->entries[move-1].name;
+ currTable->entries[move].board = currTable->entries[move-1].board;
+ currTable->entries[move].elapsed = currTable->entries[move-1].elapsed;
+ }
+
+ currTable->entries[pos].score = s;
+ currTable->entries[pos].board = gameNum;
+ currTable->entries[pos].elapsed = e;
+
+ lineEdit->setEnabled(true);
+ lineEdit->setGeometry( 40, 75+(pos*30), 150, 30 );
+ lineEdit->setFocus();
+ lineEdit->setText("");
+ selectedLine = pos;
+ nameChanged("");
+
+ // no board change when entering data
+ combo->setEnabled(false);
+ exec(board);
+ combo->setEnabled(true);
+
+ selectedLine = -1;
+ lineEdit->setGeometry( 40, 75+(20*30), 150, 30);
+ lineEdit->setEnabled(false);
+
+ // sync the hiscore table to disk now
+ saveTables();
+
+}
+
+void HighScore::nameChanged(const QString &s) {
+
+ if (selectedLine == -1)
+ return;
+
+ if (s.isEmpty())
+ currTable->entries[selectedLine].name =
+ i18n("Anonymous");
+ else
+ currTable->entries[selectedLine].name = s;
+}
+
+
+void HighScore::getBoardName(QString in, QString &out) {
+
+ QFileInfo fi( in );
+ out = fi.baseName();
+}
+
+void HighScore::setComboTo(const QString &to) {
+ for (int p=0; p<combo->count(); p++) {
+ if (combo->text(p) == to)
+ combo->setCurrentItem(p);
+ }
+}
+
+
+void HighScore::selectionChanged(int ) {
+ copyTableToScreen(combo->currentText());
+
+}
+
+
+// reset the high score table. Caution the user
+// before acting
+
+
+void HighScore::reset() {
+
+ int res=KMessageBox::warningContinueCancel(this,
+ i18n("Resetting the high scores will "
+ "remove all high score entries "
+ "both in memory and on disk. Do "
+ "you wish to proceed?"),
+ i18n("Reset High Scores"),i18n("Reset"));
+ if (res != KMessageBox::Continue)
+ return ;
+
+ // delete the file
+ res = unlink( QFile::encodeName(highScoreFile()));
+
+ // wipe ou the in memory list of tables
+ TableInstance *t, *d;
+
+ if (tables != NULL) {
+ t = tables;
+ while (t != NULL) {
+ d = t;
+ t = t->next;
+ d->next=0;
+ delete d;
+ }
+
+ }
+
+ // set the list empty
+ tables = NULL;
+ currTable=NULL;
+
+ // clear out the combobox
+ combo->clear();
+
+ // stick in a default
+ selectTable("default");
+
+ // make sure tha the on screen data does not
+ // point to deleted data
+ copyTableToScreen("default");
+}
+
+QString &HighScore::highScoreFile() {
+ return filename;
+
+}
diff --git a/kmahjongg/HighScore.h b/kmahjongg/HighScore.h
new file mode 100644
index 00000000..5db36819
--- /dev/null
+++ b/kmahjongg/HighScore.h
@@ -0,0 +1,77 @@
+
+#ifndef HighScore_included
+#define HighScore_included
+
+#include <qdialog.h>
+
+
+class QLineEdit;
+class QComboBox;
+class QLabel;
+
+const int numScores = 10;
+
+typedef struct HiScoreEntry {
+ QString name;
+ long board;
+ long score;
+ long elapsed;
+
+};
+
+typedef struct TableInstance {
+ QString name;
+ HiScoreEntry entries[numScores];
+ TableInstance *next;
+};
+
+
+class HighScore : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ HighScore
+ (
+ QWidget* parent = NULL,
+ const char* name = NULL
+ );
+
+ virtual ~HighScore();
+
+ int exec(QString &layout);
+
+
+ void checkHighScore(int score, int elapsed, long game, QString &board);
+public slots:
+ void selectionChanged(int);
+
+protected slots:
+ void nameChanged(const QString &s);
+ void reset();
+private:
+ void addRow(int num); // generate one table row
+ void loadTables(); // initialise from saved
+ void saveTables(); // save to disc.
+ void getBoardName(QString in, QString &out);
+ void selectTable(const QString &name);
+ void setComboTo(const QString &to);
+ void copyTableToScreen(const QString &name);
+ QString &highScoreFile();
+
+ int selectedLine;
+ QLineEdit *lineEdit;
+ QLabel* numbersWidgets[numScores];
+ QLabel* boardWidgets[numScores];
+ QLabel* namesWidgets[numScores];
+ QLabel* scoresWidgets[numScores];
+ QLabel* elapsedWidgets[numScores];
+ QComboBox* combo;
+ QString filename;
+
+ TableInstance *tables;
+ TableInstance *currTable;
+};
+
+#endif // HighScore_included
diff --git a/kmahjongg/KmTypes.h b/kmahjongg/KmTypes.h
new file mode 100644
index 00000000..8f85978a
--- /dev/null
+++ b/kmahjongg/KmTypes.h
@@ -0,0 +1,27 @@
+#ifndef _KM_TYPES_
+#define _KM_TYPES_
+
+//----------------------------------------------------------
+// TYPEDEFS
+//----------------------------------------------------------
+typedef unsigned char UCHAR;
+typedef unsigned char BYTE;
+typedef unsigned short USHORT;
+typedef unsigned long ULONG;
+
+
+typedef struct pos {
+ pos() : e(0), y(0), x(0), f(0) { }
+ USHORT e,y,x,f;
+} POSITION;
+
+typedef struct dep {
+ int turn_dep[4]; // Turn dependencies
+ int place_dep[4]; // Placing dependencies
+ int lhs_dep[2]; // Left side dependencies, same level
+ int rhs_dep[2]; // Right side dependencies, same level
+ bool filled; // True if this tile has been placed.
+ bool free; // True if this tile can be removed?
+} DEPENDENCY;
+
+#endif
diff --git a/kmahjongg/Makefile.am b/kmahjongg/Makefile.am
new file mode 100644
index 00000000..7eba27b1
--- /dev/null
+++ b/kmahjongg/Makefile.am
@@ -0,0 +1,25 @@
+SUBDIRS = pics
+
+INCLUDES = -I$(top_srcdir)/libkdegames $(all_includes)
+KDE_ICON = kmahjongg
+
+bin_PROGRAMS = kmahjongg
+kmahjongg_SOURCES = main.cpp kmahjongg.cpp boardwidget.cpp \
+ Tileset.cpp BoardLayout.cpp GameTimer.cpp \
+ Background.cpp Preview.cpp prefs.kcfgc \
+ Editor.cpp HighScore.cpp settings.ui
+kmahjongg_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+kmahjongg_LDADD = $(LIB_KDEGAMES) $(LIB_KFILE)
+kmahjongg_DEPENDENCIES = $(LIB_KDEGAMES_DEP)
+
+METASOURCES = AUTO
+
+xdg_apps_DATA = kmahjongg.desktop
+kde_kcfg_DATA = kmahjongg.kcfg
+
+rcdir = $(kde_datadir)/kmahjongg
+rc_DATA = kmahjonggui.rc
+
+messages: rc.cpp
+ $(XGETTEXT) *.cpp -o $(podir)/kmahjongg.pot
+
diff --git a/kmahjongg/Preview.cpp b/kmahjongg/Preview.cpp
new file mode 100644
index 00000000..edd8ef79
--- /dev/null
+++ b/kmahjongg/Preview.cpp
@@ -0,0 +1,513 @@
+#include <kapplication.h>
+#include <kfiledialog.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kpushbutton.h>
+#include <kstandarddirs.h>
+#include <kstdguiitem.h>
+#include <kimageio.h>
+
+#include <qcombobox.h>
+#include <qhgroupbox.h>
+#include <qimage.h>
+#include <qregexp.h>
+#include <qpainter.h>
+#include <qvbox.h>
+
+#include "prefs.h"
+#include "Preview.h"
+
+static const char * themeMagicV1_0= "kmahjongg-theme-v1.0";
+
+Preview::Preview(QWidget* parent) : KDialogBase(parent), m_tiles(true)
+{
+ KPushButton *loadButton;
+ QGroupBox *group;
+ QVBox *page;
+
+ page = new QVBox(this);
+
+ group = new QHGroupBox(page);
+
+ m_combo = new QComboBox(false, group);
+ connect(m_combo, SIGNAL(activated(int)), SLOT(selectionChanged(int)));
+
+ loadButton = new KPushButton(i18n("Load..."), group);
+ connect( loadButton, SIGNAL(clicked()), SLOT(load()) );
+
+ m_drawFrame = new FrameImage(page);
+ m_drawFrame->setFixedSize(310, 236);
+
+ m_changed = false;
+
+ setMainWidget(page);
+ setFixedSize(sizeHint());
+}
+
+Preview::~Preview()
+{
+}
+
+void Preview::selectionChanged(int which)
+{
+ m_selectedFile = m_fileList[which];
+ drawPreview();
+ m_drawFrame->repaint(0,0,-1,-1,false);
+ markChanged();
+}
+
+bool Preview::isChanged()
+{
+ return m_changed;
+}
+
+void Preview::markChanged()
+{
+ m_changed = true;
+}
+
+void Preview::markUnchanged()
+{
+ m_changed = false;
+}
+
+void Preview::initialise(const PreviewType type)
+{
+ QString extension;
+ QString tile = Prefs::tileSet();
+ QString back = Prefs::background();
+ QString layout = Prefs::layout();
+
+ // set up the concept of the current file. Initialised to the preferences
+ // value initially. Set the caption to indicate what we are doing
+ switch (type)
+ {
+ case background:
+ setCaption(i18n("Change Background Image"));
+ m_selectedFile = back;
+ m_fileSelector = i18n("*.bgnd|Background Image (*.bgnd)\n");
+ m_fileSelector += KImageIO::pattern()+"\n";
+ extension = "*.bgnd";
+ break;
+
+ case tileset:
+ setCaption(i18n("Change Tile Set"));
+ m_fileSelector = i18n("*.tileset|Tile Set File (*.tileset)\n");
+ m_selectedFile = tile;
+ extension = "*.tileset";
+ break;
+
+ case board:
+ m_fileSelector = i18n("*.layout|Board Layout File (*.layout)\n");
+ setCaption(i18n("Change Board Layout"));
+ m_selectedFile = layout;
+ extension = "*.layout";
+ break;
+
+ case theme:
+ m_fileSelector = i18n("*.theme|KMahjongg Theme File (*.theme)\n");
+ setCaption(i18n("Choose Theme"));
+ m_selectedFile="";
+ extension = "*.theme";
+
+ m_themeLayout="";
+ m_themeBack="";
+ m_themeTileset="";
+
+ default:
+ break;
+ }
+
+ m_fileSelector += i18n("*|All Files");
+ enableButtonApply(type != board);
+
+ m_previewType = type;
+ // we start with no change indicated
+ markUnchanged();
+
+ m_fileList = kapp->dirs()->findAllResources("appdata", "pics/*"+extension, false, true);
+
+ // get rid of files from the last invocation
+ m_combo->clear();
+
+ QStringList names;
+ QStringList::const_iterator it, itEnd;
+ it = m_fileList.begin();
+ itEnd = m_fileList.end();
+ for ( ; it != itEnd; ++it)
+ {
+ QFileInfo fi(*it);
+ names << fi.baseName();
+ }
+
+ m_combo->insertStringList(names);
+ m_combo->setEnabled(m_fileList.count());
+ drawPreview();
+}
+
+void Preview::slotApply() {
+ if (isChanged()) {
+ applyChange();
+ markUnchanged();
+ }
+}
+
+void Preview::slotOk() {
+ slotApply();
+ accept();
+}
+
+void Preview::load() {
+ KURL url = KFileDialog::getOpenURL(QString::null, m_fileSelector, this, i18n("Open Board Layout" ));
+ if ( !url.isEmpty() ) {
+ m_selectedFile = url.path();
+ drawPreview();
+ m_drawFrame->repaint(0,0,-1,-1,false);
+ markChanged();
+ }
+}
+
+// Top level preview drawing method. Background, tileset and layout
+// are initialised from the preferences. Depending on the type
+// of preview dialog we pick up the selected file for one of these
+// chaps.
+
+void Preview::drawPreview()
+{
+ QString tile = Prefs::tileSet();
+ QString back = Prefs::background();
+ QString layout = Prefs::layout();
+
+ switch (m_previewType)
+ {
+ case background:
+ back = m_selectedFile;
+ break;
+
+ case tileset:
+ tile = m_selectedFile;
+ break;
+
+ case board:
+ layout = m_selectedFile;
+ break;
+
+ case theme:
+ // a theme is quite a bit of work. We load the
+ // specified bits in (layout, background and tileset
+ if (!m_selectedFile.isEmpty())
+ {
+ QString backRaw, layoutRaw, tilesetRaw, magic;
+
+ QFile in(m_selectedFile);
+ if (in.open(IO_ReadOnly))
+ {
+ QTextStream stream(&in);
+ magic = stream.readLine();
+ if (magic != themeMagicV1_0)
+ {
+ in.close();
+ KMessageBox::sorry(this, i18n("That is not a valid theme file."));
+ break;
+ }
+ tilesetRaw = stream.readLine();
+ backRaw = stream.readLine();
+ layoutRaw = stream.readLine();
+ in.close();
+
+ tile = tilesetRaw;
+ tile.replace(":", "/kmahjongg/pics/");
+ if (!QFile::exists(tile))
+ {
+ tile = tilesetRaw;
+ tile = "pics/" + tile.right(tile.length() - tile.find(":") - 1 );
+ tile = locate("appdata", tile);
+ }
+
+ back = backRaw;
+ back.replace(":", "/kmahjongg/pics/");
+ if (!QFile::exists(back))
+ {
+ back = backRaw;
+ back = "pics/" + back.right(back.length() - back.find(":") - 1);
+ back = locate("appdata", back);
+ }
+
+ layout = layoutRaw;
+ layout.replace(":", "/kmahjongg/pics/");
+ if (!QFile::exists(layout))
+ {
+ layout = layoutRaw;
+ layout = "pics/" + layout.right(layout.length() - layout.find(":") - 1);
+ layout = locate("appdata", layout);
+ }
+
+ m_themeBack=back;
+ m_themeLayout=layout;
+ m_themeTileset=tile;
+ }
+ }
+ break;
+ }
+
+ renderBackground(back);
+ renderTiles(tile, layout);
+}
+
+void Preview::paintEvent( QPaintEvent* ){
+ m_drawFrame->repaint(false);
+}
+
+// the user selected ok, or apply. This method passes the changes
+// across to the game widget and if necessary forces a board redraw
+// (unnecessary on layout changes since it only effects the next game)
+void Preview::applyChange()
+{
+ switch (m_previewType)
+ {
+ case background:
+ loadBackground(m_selectedFile, false);
+ break;
+
+ case tileset:
+ loadTileset(m_selectedFile);
+ break;
+
+ case board:
+ loadBoard(m_selectedFile);
+ break;
+
+ case theme:
+ if (!m_themeLayout.isEmpty() && !m_themeBack.isEmpty() && !m_themeTileset.isEmpty())
+ {
+ loadBackground(m_themeBack, false);
+ loadTileset(m_themeTileset);
+ loadBoard(m_themeLayout);
+ }
+ break;
+ }
+
+ // don't redraw for a layout change
+ if (m_previewType == board || m_previewType == theme) layoutChange();
+ else boardRedraw(true);
+
+ // either way we mark the current value as unchanged
+ markUnchanged();
+}
+
+// Render the background to the pixmap.
+void Preview::renderBackground(const QString &bg) {
+ QImage img;
+ QImage tmp;
+ QPixmap *p;
+ QPixmap *b;
+ p = m_drawFrame->getPreviewPixmap();
+ m_back.load(bg, p->width(), p->height());
+ b = m_back.getBackground();
+ bitBlt( p, 0,0,
+ b,0,0, b->width(), b->height(), CopyROP );
+}
+
+// This method draws a mini-tiled board with no tiles missing.
+
+void Preview::renderTiles(const QString &file, const QString &layout) {
+ m_tiles.loadTileset(file, true);
+ m_boardLayout.loadBoardLayout(layout);
+
+ QPixmap *dest = m_drawFrame->getPreviewPixmap();
+ int xOffset = m_tiles.width()/2;
+ int yOffset = m_tiles.height()/2;
+ short tile = 0;
+
+ // we iterate over the depth stacking order. Each successive level is
+ // drawn one indent up and to the right. The indent is the width
+ // of the 3d relief on the tile left (tile shadow width)
+ for (int z=0; z<BoardLayout::depth; z++) {
+ // we draw down the board so the tile below over rights our border
+ for (int y = 0; y < BoardLayout::height; y++) {
+ // drawing right to left to prevent border overwrite
+ for (int x=BoardLayout::width-1; x>=0; x--) {
+ int sx = x*(m_tiles.qWidth() )+xOffset;
+ int sy = y*(m_tiles.qHeight() )+yOffset;
+ if (m_boardLayout.getBoardData(z, y, x) != '1') {
+ continue;
+ }
+ QPixmap *t = m_tiles.unselectedPixmaps(tile);
+
+ // Only one compilcation. Since we render top to bottom , left
+ // to right situations arise where...:
+ // there exists a tile one q height above and to the left
+ // in this situation we would draw our top left border over it
+ // we simply split the tile draw so the top half is drawn
+ // minus border
+
+ if ((x>1) && (y>0) && m_boardLayout.getBoardData(z,y-1,x-2)=='1'){
+ bitBlt( dest, sx+2, sy,
+ t, 2,0, t->width(), t->height()/2, CopyROP );
+ bitBlt( dest, sx, sy+t->height()/2,
+ t, 0,t->height()/2,t->width(),t->height()/2,CopyROP);
+ } else {
+
+ bitBlt( dest, sx, sy,
+ t, 0,0, t->width(), t->height(), CopyROP );
+ }
+ tile++;
+ if (tile == 35)
+ tile++;
+ tile = tile % 43;
+ }
+ }
+ xOffset +=m_tiles.shadowSize();
+ yOffset -=m_tiles.shadowSize();
+ }
+}
+
+// this really does not belong here. It will be fixed in v1.1 onwards
+void Preview::saveTheme() {
+ QString tile = Prefs::tileSet();
+ QString back = Prefs::background();
+ QString layout = Prefs::layout();
+
+ QString with = ":";
+ // we want to replace any path in the default store
+ // with a +
+ QRegExp p(locate("data_dir", "/kmahjongg/pics/"));
+
+ back.replace(p,with);
+ tile.replace(p,with);
+ layout.replace(p,with);
+
+
+ // Get the name of the file to save
+ KURL url = KFileDialog::getSaveURL(
+ NULL,
+ "*.theme",
+ parentWidget(),
+ i18n("Save Theme" ));
+ if ( url.isEmpty() )
+ return;
+
+ if( !url.isLocalFile() )
+ {
+ KMessageBox::sorry( this, i18n( "Only saving to local files currently supported." ) );
+ return;
+ }
+
+ // Are we over writing an existin file, or was a directory selected?
+ QFileInfo f( url.path() );
+ if( f.isDir() )
+ return;
+ if (f.exists()) {
+ // if it already exists, querie the user for replacement
+ int res=KMessageBox::warningContinueCancel(this,
+ i18n("A file with that name "
+ "already exists. Do you "
+ "wish to overwrite it?"),QString::null,i18n("Overwrite"));
+ if (res != KMessageBox::Continue)
+ return ;
+ }
+ FILE *outFile = fopen( QFile::encodeName(url.path()), "w" );
+ if (outFile == NULL) {
+ KMessageBox::sorry(this,
+ i18n("Could not write to file. Aborting."));
+ return;
+ }
+
+ fprintf(outFile,"%s\n%s\n%s\n%s\n",
+ themeMagicV1_0,
+ tile.utf8().data(),
+ back.utf8().data(),
+ layout.utf8().data());
+ fclose(outFile);
+}
+
+FrameImage::FrameImage (QWidget *parent, const char *name)
+ : QFrame(parent, name)
+{
+ rx = -1;
+ thePixmap = new QPixmap();
+}
+
+FrameImage::~FrameImage()
+{
+ delete thePixmap;
+}
+
+void FrameImage::setGeometry(int x, int y, int w, int h) {
+ QFrame::setGeometry(x,y,w,h);
+
+ thePixmap->resize(size());
+
+}
+
+void FrameImage::paintEvent( QPaintEvent* pa )
+{
+ QFrame::paintEvent(pa);
+
+ QPainter p(this);
+
+
+ QPen line;
+ line.setStyle(DotLine);
+ line.setWidth(2);
+ line.setColor(yellow);
+ p.setPen(line);
+ p.setBackgroundMode(OpaqueMode);
+ p.setBackgroundColor(black);
+
+ int x = pa->rect().left();
+ int y = pa->rect().top();
+ int h = pa->rect().height();
+ int w = pa->rect().width();
+
+ p.drawPixmap(x+frameWidth(),y+frameWidth(),*thePixmap,x+frameWidth(),y+frameWidth(),w-(2*frameWidth()),h-(2*frameWidth()));
+ if (rx >=0) {
+
+ p.drawRect(rx, ry, rw, rh);
+ p.drawRect(rx+rs, ry, rw-rs, rh-rs);
+ p.drawLine(rx, ry+rh, rx+rs, ry+rh-rs);
+
+ int midX = rx+rs+((rw-rs)/2);
+ int midY = ry+((rh-rs)/2);
+ switch (rt) {
+ case 0: // delete mode cursor
+ p.drawLine(rx+rs, ry, rx+rw, ry+rh-rs);
+ p.drawLine(rx+rw, ry, rx+rs, ry+rh-rs);
+ break;
+ case 1: // insert cursor
+ p.drawLine(midX, ry, midX, ry+rh-rs);
+ p.drawLine(rx+rs, midY, rx+rw, midY);
+ break;
+ case 2: // move mode cursor
+ p.drawLine(midX, ry, rx+rw, midY);
+ p.drawLine(rx+rw, midY, midX, ry+rh-rs);
+ p.drawLine(midX, ry+rh-rs, rx+rs, midY);
+ p.drawLine(rx+rs, midY, midX, ry);
+
+ break;
+ }
+
+ }
+}
+
+void FrameImage::setRect(int x,int y,int w,int h, int s, int t)
+{
+ rx = x;
+ ry = y;
+ rw = w;
+ rh = h;
+ rt = t;
+ rs = s;
+}
+
+// Pass on the mouse presed event to our owner
+
+void FrameImage::mousePressEvent(QMouseEvent *m) {
+ mousePressed(m);
+}
+
+void FrameImage::mouseMoveEvent(QMouseEvent *e) {
+ mouseMoved(e);
+}
+
+#include "Preview.moc"
diff --git a/kmahjongg/Preview.h b/kmahjongg/Preview.h
new file mode 100644
index 00000000..4f58e2cd
--- /dev/null
+++ b/kmahjongg/Preview.h
@@ -0,0 +1,104 @@
+#ifndef _PreviewLoadBase_H
+#define _PreviewLoadBase_H
+
+#include <kdialogbase.h>
+
+#include <qframe.h>
+
+#include "Tileset.h"
+#include "BoardLayout.h"
+#include "Background.h"
+
+class QComboBox;
+class QPixmap;
+
+class FrameImage: public QFrame
+{
+ Q_OBJECT
+public:
+ FrameImage(QWidget *parent=NULL, const char *name = NULL);
+ ~FrameImage();
+ void setGeometry(int x, int y, int w, int h);
+ QPixmap *getPreviewPixmap() {return thePixmap;}
+ void setRect(int x, int y, int w, int h, int ss, int type);
+signals:
+ void mousePressed(QMouseEvent *e);
+ void mouseMoved(QMouseEvent *e);
+protected:
+ void mousePressEvent(QMouseEvent *e);
+ void mouseMoveEvent(QMouseEvent *e);
+ void paintEvent( QPaintEvent* pa );
+private:
+ QPixmap *thePixmap;
+ int rx;
+ int ry;
+ int rw;
+ int rh;
+ int rs;
+ int rt;
+};
+
+
+
+class Preview: public KDialogBase
+{
+ Q_OBJECT
+
+public:
+ enum PreviewType {background, tileset, board, theme};
+
+ Preview(QWidget* parent);
+ ~Preview();
+
+ void initialise(const PreviewType type);
+ void saveTheme();
+
+protected:
+ void markUnchanged();
+ void markChanged();
+ bool isChanged();
+ QPixmap *getPreviewPixmap() {return m_drawFrame->getPreviewPixmap(); }
+ virtual void drawPreview();
+ void applyChange() ;
+ void renderBackground(const QString &bg);
+ void renderTiles(const QString &file, const QString &layout);
+ void paintEvent( QPaintEvent* pa );
+
+signals:
+ void boardRedraw(bool);
+ void loadTileset(const QString &);
+ void loadBackground(const QString &, bool);
+ void loadBoard(const QString &);
+ void layoutChange();
+
+public slots:
+ void selectionChanged(int which);
+
+protected slots:
+ void slotApply();
+ void slotOk();
+
+private slots:
+ void load();
+
+protected:
+ FrameImage *m_drawFrame;
+ QComboBox *m_combo;
+
+ QString m_selectedFile;
+ Tileset m_tiles;
+ BoardLayout m_boardLayout;
+ Background m_back;
+
+private:
+ QString m_fileSelector;
+ bool m_changed;
+ QStringList m_fileList;
+ PreviewType m_previewType;
+
+ QString m_themeBack;
+ QString m_themeLayout;
+ QString m_themeTileset;
+};
+
+#endif
diff --git a/kmahjongg/Tileset.cpp b/kmahjongg/Tileset.cpp
new file mode 100644
index 00000000..c29f9701
--- /dev/null
+++ b/kmahjongg/Tileset.cpp
@@ -0,0 +1,281 @@
+
+#include <stdlib.h>
+#include "Tileset.h"
+#include <qimage.h>
+
+
+#define mini_width 20
+#define mini_height 28
+static unsigned char mini_bits[] = {
+ 0xfc, 0xff, 0x0f, 0xfe, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f,
+ 0xff, 0xff, 0x0f, 0xff, 0xff, 0x0f, 0xff, 0xff, 0x07, 0xff, 0xff, 0x03,
+ };
+
+#define mask_width 40
+#define mask_height 56
+static unsigned char mask_bits[] = {
+ 0xf0, 0xff, 0xff, 0xff, 0xff, 0xf8, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xff,
+ 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0x7f, 0xff, 0xff, 0xff, 0xff, 0x3f, 0xff, 0xff, 0xff, 0xff, 0x1f, 0xff,
+ 0xff, 0xff, 0xff, 0x0f, };
+
+
+// ---------------------------------------------------------
+
+Tileset::Tileset(bool scale):
+ maskBits(mask_width, mask_height, mask_bits, true),
+ maskBitsMini(mini_width, mini_height, mini_bits, true)
+{
+ isScaled = scale;
+ divisor = (isScaled) ? 2 : 1;
+
+ // set up tile metrics (fixed for now)
+ ss = 4; // left/bottom shadow width
+ bs = 1; // tile boarder width
+ w = 40; // tile width (inc boarder & shadow)
+ h = 56; // tile height (inc boarder and shadow)
+ s = w*h; // RGBA's required per tile
+
+ // Allocate memory for the 9*5 tile arrays
+ tiles = new QRgb [9*5*s];
+ selectedTiles = new QRgb [9*5*s];
+
+ // allocate memory for single tile storage
+ selectedFace = new QRgb [s];
+ unselectedFace = new QRgb [s];
+
+ // quarter widths are used as an offset when
+ // overlaying tiles in 3 dimensions.
+ qw = ((w-ss)/2) ;
+ qh = ((h-ss)/2) ;
+
+ filename = "";
+}
+
+
+// ---------------------------------------------------------
+
+Tileset::~Tileset() {
+
+ // deallocate all memory
+ delete [] tiles;
+ delete [] selectedTiles;
+ delete [] selectedFace;
+ delete [] unselectedFace;
+}
+
+// ---------------------------------------------------------
+// copy a tile from a qimage into a linear array of bytes. This
+// method returns the address of the byte after the copied image
+// and can be used to fill a larger array of tiles.
+
+QRgb *Tileset::copyTileImage(short tileX, short tileY, QRgb *to, QImage &from) {
+ QRgb *dest = to;
+ QRgb *src;
+
+ src = (QRgb *) from.scanLine(tileY * h)
+ +(tileX * w);
+ for (short pos=0; pos < h; pos++) {
+ memcpy(dest, src, w*sizeof(QRgb));
+ dest+=w;
+ src += from.width();
+ }
+ return(dest);
+}
+
+// ----------------------------------------------------------
+// Create a tile. Take a specified tile background a tile face
+// (specified as an x,y coord) and a destination buffer (location
+// in which is calculated from the x,y) and copy in the
+// tile background, overlaying the foreground with transparency
+// (the foregrounds top/left pixel is taken as the transparent
+// color).
+
+
+QRgb *Tileset::createTile(short x, short y,
+ QRgb *det, QImage &allTiles , QRgb *face) {
+ QRgb *image ;
+ QRgb *to = det;
+
+ // Alloc the space
+ image = new QRgb[s];
+
+ // copy in the background
+ memcpy(to, face, s*sizeof(QRgb));
+
+ // get the tile gylph
+ copyTileImage(x, y , image, allTiles);
+
+ // copy the image over the background using the
+ // top left colour as the transparency. We step over
+ // the shadow and the boarder
+
+ QRgb* src = image+ // image
+ ss+ // past the left shadow
+ bs+ // then the tile border
+ (bs * w); // then step past the top border
+
+
+ to += (((ss+bs))+(bs*w));
+
+
+ QRgb trans = *src;
+
+ // start after the top border rows and go through all rows
+ for( short YP=0; YP < h-ss - (2*bs); YP++) {
+ // start after the shadow and border and iterate over x
+ for (short xpos=0; xpos < w-ss -(2*bs) ; xpos++) {
+ // skip transparent pixels
+ if (*src != trans)
+ *to = *src;
+ src++;
+ to++;
+ }
+ // step over the border to get to the next row
+ src += ss + (2 * bs);
+ to += ss + (2 * bs);
+ }
+
+ // Free allocated space
+ delete [] image;
+
+ // calculate the address of the next tile
+ return(det+s);
+}
+
+// --------------------------------------------------------
+// create a pixmap for a tile. Optionally create a scalled
+// version, which can be used for mini tile requirements.
+// this gives us a small tile for previews and showing
+// removed tiles.
+void Tileset::createPixmap(QRgb *src, QPixmap &dest, bool scale, bool shadow)
+{
+
+ QImage buff;
+ QRgb *line;
+
+ buff.create(w, h, 32);
+
+ for (int y=0; y<h; y++) {
+ line = (QRgb *) buff.scanLine(y);
+ memcpy( line, src, w*sizeof(QRgb));
+
+ if (shadow) {
+ for (int spos=0; spos <w; spos++) {
+ line[spos] = QColor(line[spos]).dark(133).rgb();
+ }
+ }
+
+ src += w;
+ }
+
+
+ // create the pixmap and initialise the drawing mask
+ if (!scale) {
+ dest.convertFromImage(buff);
+ dest.setMask(maskBits);
+ } else {
+ dest.convertFromImage(buff.smoothScale(w/2, h/2));
+ dest.setMask(maskBitsMini);
+ }
+
+}
+
+
+// ---------------------------------------------------------
+bool Tileset::loadTileset( const QString& tilesetPath, const bool isPreview)
+{
+
+ QImage qiTiles;
+ QRgb *unsel;
+ QRgb *sel;
+ QRgb *nextSel=0;
+ QRgb *nextUnsel=0;
+
+ if (filename == tilesetPath) {
+ return true;
+ }
+
+
+ // try to load it
+ if( ! qiTiles.load( tilesetPath) )
+ return( false );
+
+
+
+ // we deal only with 32 bit images
+ if (qiTiles.depth() != 32)
+ qiTiles = qiTiles.convertDepth(32);
+
+
+
+ // Read in the unselected and selected tile backgrounds
+ copyTileImage(7, 4 , unselectedFace, qiTiles);
+ copyTileImage(8, 4, selectedFace, qiTiles);
+
+
+
+ // Read in the 9*5 tiles. Each tile is overlayed onto
+ // the selected and unselected tile backgrounds and
+ // stored.
+ sel = selectedTiles;
+ unsel = tiles;
+ for (short atY=0; atY<5; atY++) {
+ for (short atX=0; atX<9; atX++) {
+
+
+ nextUnsel = createTile(atX, atY, unsel , qiTiles, unselectedFace);
+
+ // for the preview dialog we do not create selected tiles
+ if (!isPreview)
+ nextSel = createTile(atX, atY, sel , qiTiles, selectedFace);
+ int pixNo = atX+(atY*9);
+
+ // for the preview dialog we only create the unselected mini pix
+ if (!isPreview) {
+ createPixmap(sel, selectedPix[pixNo], false, false);
+ createPixmap(unsel, unselectedPix[pixNo], false, false);
+ createPixmap(sel, selectedMiniPix[pixNo], true, false);
+
+ createPixmap(sel, selectedShadowPix[pixNo], false, true);
+ createPixmap(unsel, unselectedShadowPix[pixNo], false, true);
+ createPixmap(sel, selectedShadowMiniPix[pixNo], true, true);
+ createPixmap(unsel, unselectedShadowMiniPix[pixNo], true, true);
+ }
+
+ createPixmap(unsel, unselectedMiniPix[pixNo], true, false);
+ sel = nextSel;
+ unsel= nextUnsel;
+ }
+ }
+
+ filename = tilesetPath;
+ return( true );
+}
+
+
diff --git a/kmahjongg/Tileset.h b/kmahjongg/Tileset.h
new file mode 100644
index 00000000..45bc9281
--- /dev/null
+++ b/kmahjongg/Tileset.h
@@ -0,0 +1,107 @@
+#ifndef _TILE_SET_H_
+#define _TILE_SET_H_
+
+
+#include <qbitmap.h>
+
+class Tileset {
+ public:
+ Tileset(bool scaled=false);
+ ~Tileset();
+
+ bool loadTileset(const QString &filesetPath, const bool isPreview = false);
+ QRgb *createTile(short x, short y, QRgb *dst, QImage &src , QRgb *face);
+ QRgb *copyTileImage(short tileX, short tileY, QRgb *to, QImage &from);
+
+ void setScaled(bool sc) {isScaled = sc; divisor = (sc) ? 2 : 1;}
+
+
+ QRgb *tile(short tnum);
+ QRgb *selectedTile(short tnum);
+ short width() {return w/divisor;}
+ short height() {return h/divisor;}
+ short shadowSize() {return ss/divisor;}
+ short size() {return s;}
+ short qWidth() {return qw/divisor;}
+ short qHeight() {return qh/divisor;}
+
+
+ QPixmap *selectedPixmaps(int num) {
+ if (!isScaled)
+ return &(selectedPix[num]);
+ else
+ return &(selectedMiniPix[num]);
+ }
+
+ QPixmap *unselectedPixmaps(int num) {
+ if (!isScaled)
+ return &(unselectedPix[num]);
+ else
+ return &(unselectedMiniPix[num]);
+ }
+
+ QPixmap *selectedShadowPixmaps(int num) {
+ if (!isScaled)
+ return &(selectedShadowPix[num]);
+ else
+ return &(selectedShadowMiniPix[num]);
+ }
+
+ QPixmap *unselectedShadowPixmaps(int num) {
+ if (!isScaled)
+ return &(unselectedShadowPix[num]);
+ else
+ return &(unselectedShadowMiniPix[num]);
+ }
+
+ protected:
+
+ enum { maxTiles=45 };
+ void createPixmap(QRgb *src, QPixmap &dest, bool scale, bool shadow);
+
+
+ private:
+ QBitmap maskBits; // xbm mask for the tile
+ QBitmap maskBitsMini; // xbm mask for the tile
+ QRgb* tiles; // Buffer containing all tiles (unselected glyphs)
+ QRgb* selectedTiles; // Buffer containing all tiles (selected glyphs)
+
+
+ // in version 0.5 we have moved ftom using images and calculating
+ // masks etc, to using pixmaps and letting the blt do the hard work,
+ // in hardware.
+ QPixmap selectedPix[maxTiles]; // selected tiles
+ QPixmap unselectedPix[maxTiles]; // unselected tiles
+ QPixmap selectedMiniPix[maxTiles]; // selected tiles
+ QPixmap unselectedMiniPix[maxTiles]; // unselected tiles
+
+ QPixmap selectedShadowPix[maxTiles]; // selected tiles as above in shadow
+ QPixmap unselectedShadowPix[maxTiles]; // unselected tiles
+ QPixmap selectedShadowMiniPix[maxTiles]; // selected tiles
+ QPixmap unselectedShadowMiniPix[maxTiles]; // unselected tiles
+
+
+
+
+ QRgb* selectedFace; // The tile background face for a selected tile
+ QRgb* unselectedFace;// The tile background face for an unselected tile
+
+ QRgb tr; // transparenct color for tile bg
+
+ short ss; // left/bottom shadow width
+ short bs; // width of the border around a tile
+ short w; // tile width ( +border +shadow)
+ short h; // tile height ( +border +shadow)
+ short qw; // quarter tile width used in 3d rendering
+ short qh; // quarter tile height used in 3d rendering
+ short s; // buffer size for tile (width*height)
+
+ QString filename; // cache the last file loaded to save reloading it
+ bool isScaled;
+ int divisor;
+};
+
+
+#endif
+
+
diff --git a/kmahjongg/boardwidget.cpp b/kmahjongg/boardwidget.cpp
new file mode 100644
index 00000000..9c3355ea
--- /dev/null
+++ b/kmahjongg/boardwidget.cpp
@@ -0,0 +1,2023 @@
+#include "boardwidget.h"
+#include "prefs.h"
+
+#include <kmessagebox.h>
+#include <kapplication.h>
+#include <qtimer.h>
+#include <qpainter.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <qfile.h>
+#include <kconfig.h>
+
+/**
+ * Constructor.
+ * Loads tileset and background bitmaps.
+ */
+BoardWidget::BoardWidget( QWidget* parent, const char *name )
+ : QWidget( parent, name ), theTiles(false)
+{
+ setBackgroundColor( QColor( 0,0,0 ) );
+
+ timer = new QTimer(this);
+ connect( timer, SIGNAL(timeout()),
+ this, SLOT(helpMoveTimeout()) );
+
+ TimerState = Stop;
+ gamePaused = false;
+ iTimerStep = 0;
+ matchCount = 0;
+ showMatch = false;
+ showHelp = false;
+ MouseClickPos1.e = BoardLayout::depth; // mark tile position as invalid
+ MouseClickPos2.e = BoardLayout::depth;
+ memset( &Game.Mask, 0, sizeof( Game.Mask ) );
+ Game.MaxTileNum = 0;
+ gameGenerationNum = 0;
+
+ // initially we force a redraw
+ updateBackBuffer=true;
+
+ // Load tileset. First try to load the last use tileset
+ QString tFile;
+ getFileOrDefault(Prefs::tileSet(), "tileset", tFile);
+
+ if (!loadTileset(tFile)){
+ KMessageBox::error(this,
+ i18n("An error occurred when loading the tileset file %1\n"
+ "KMahjongg will now terminate.").arg(tFile));
+ kapp->quit();
+ }
+
+ getFileOrDefault(Prefs::background(), "bgnd", tFile);
+
+ // Load background
+ if( ! loadBackground(tFile, false ) )
+ {
+ KMessageBox::error(this,
+ i18n("An error occurred when loading the background image\n%1").arg(tFile)+
+ i18n("KMahjongg will now terminate."));
+ kapp->quit();
+ }
+
+ getFileOrDefault(Prefs::layout(), "layout", tFile);
+ if( ! loadBoardLayout(tFile) )
+ {
+ KMessageBox::error(this,
+ i18n("An error occurred when loading the board layout %1\n"
+ "KMahjongg will now terminate.").arg(tFile));
+ kapp->quit();
+ }
+ setDisplayedWidth();
+ loadSettings();
+}
+
+BoardWidget::~BoardWidget(){
+ saveSettings();
+}
+
+void BoardWidget::loadSettings(){
+ theBackground.tile = Prefs::tiledBackground();
+
+ setDisplayedWidth();
+ tileSizeChanged();
+ updateScaleMode();
+ drawBoard(true);
+}
+
+void BoardWidget::saveSettings(){
+ // Preview can't handle this. TODO
+ //KConfig *config=kapp->config();
+ //config->setGroup("General");
+
+ //config->writePathEntry("Tileset_file", tileFile);
+ //config->writePathEntry("Background_file", backgroundFile);
+ //config->writePathEntry("Layout_file", layout);
+}
+
+void BoardWidget::getFileOrDefault(QString filename, QString type, QString &res)
+{
+ QString picsPos = "pics/";
+ picsPos += "default.";
+ picsPos += type;
+
+ if (QFile::exists(filename)) {
+ res = filename;
+ }
+ else {
+ res = locate("appdata", picsPos);
+ }
+
+ if (res.isEmpty()) {
+ KMessageBox::error(this, i18n("KMahjongg could not locate the file: %1\n"
+ "or the default file of type: %2\n"
+ "KMahjongg will now terminate").arg(filename).arg(type) );
+ kapp->quit();
+ }
+}
+
+void BoardWidget::setDisplayedWidth() {
+ if (Prefs::showRemoved())
+ setFixedSize( requiredWidth() , requiredHeight());
+ else
+ setFixedSize( requiredWidth() - ((theTiles.width())*4)
+ , requiredHeight());
+}
+
+// for a given cell x y calc how that cell is shadowed
+// returnd left = width of left hand side shadow
+// t = height of top shadow
+// c = width and height of corner shadow
+
+void BoardWidget::calcShadow(int e, int y, int x, int &l, int &t, int &c) {
+
+ l = t = c = 0;
+ if ((Game.shadowHeight(e,y,x) != 0) ||
+ (Game.shadowHeight(e,y-1,x) != 0) ||
+ (Game.shadowHeight(e,y,x-1) != 0)) {
+ return;
+ }
+ int a,b;
+
+ a=Game.shadowHeight(e,y,x-2);
+ b=Game.shadowHeight(e,y-1,x-2);
+ if (a != 0 || b != 0)
+ l = (a>b) ? a : b;
+ a=Game.shadowHeight(e,y-2,x);
+ b=Game.shadowHeight(e,y-2,x-1);
+ if (a != 0 || b != 0)
+ t = (a>b) ? a : b;
+
+ c = Game.shadowHeight(e, y-2, x-2);
+}
+
+// draw a triangular shadow from the top right to the bottom left.
+// one such shadow is a right hand edge of a shadow line.
+// if a second shadow botton left to top right is rendered over it
+// then the shadow becomes a box (ie in the middle of the run)
+
+void BoardWidget::shadowTopLeft(int depth, int sx, int sy, int rx, int ry, QPixmap *src, bool flag) {
+ if (depth) {
+ int shadowPixels= (depth+1) * theTiles.shadowSize();
+ int xOffset=theTiles.qWidth()-shadowPixels;
+ for (int p=0; p<shadowPixels; p++) {
+ bitBlt( &backBuffer,
+ sx+xOffset, sy+p,
+ src,
+ rx+xOffset, ry+p,
+ shadowPixels-p,
+ 1, CopyROP );
+ }
+ // Now aafter rendering the triangle, fill in the rest of
+ // the quater width
+ if (flag && ((theTiles.qWidth() - shadowPixels) > 0))
+ bitBlt( &backBuffer,
+ sx, sy,
+ src,
+ rx, ry,
+ theTiles.qWidth() - shadowPixels,
+ shadowPixels, CopyROP );
+ }
+}
+
+// Second triangular shadow generator see above
+void BoardWidget::shadowBotRight(int depth, int sx, int sy, int rx, int ry, QPixmap *src, bool flag) {
+ if (depth) {
+ int shadowPixels= (depth+1) * theTiles.shadowSize();
+ int xOffset=theTiles.qWidth();
+ for (int p=0; p<shadowPixels; p++) {
+ bitBlt( &backBuffer,
+ sx+xOffset-p, /* step to shadow right start */
+ sy+p, /* down for each line */
+ src,
+ rx+xOffset-p, /* step to shadow right start */
+ ry+p,
+ p, /* increace width each line down */
+ 1, CopyROP );
+ }
+ if (flag && ((theTiles.qHeight() - shadowPixels) >0))
+ bitBlt( &backBuffer,
+ sx+xOffset-shadowPixels,
+ sy+shadowPixels,
+ src,
+ rx+xOffset-shadowPixels,
+ ry+shadowPixels,
+ shadowPixels,
+ theTiles.qHeight()-shadowPixels, CopyROP );
+
+ }
+}
+
+
+
+
+void BoardWidget::shadowArea(int z, int y, int x, int sx, int sy,int rx, int ry, QPixmap *src)
+{
+ // quick check to see if we are obscured
+ if (z < BoardLayout::depth-1) {
+ if ((x >= 0) && (y<BoardLayout::height)) {
+ if (Game.Mask[z+1][y][x] && Game.Board[z+1][y][x]) {
+ return;
+ }
+ }
+ }
+
+
+
+
+ // offset to pass tile depth indicator
+ sx+=theTiles.shadowSize();
+ rx+=theTiles.shadowSize();
+
+
+
+ // We shadow the top right hand edge of the tile with a
+ // triangular border. If the top shadow covers it all
+ // well and good, otherwise if its smaller, part of the
+ // triangle will show through.
+
+ shadowTopLeft(Game.shadowHeight(z+1, y-1, x), sx, sy, rx,ry,src, true);
+ shadowBotRight(Game.shadowHeight(z+1, y, x+1), sx, sy, rx, ry, src, true);
+ shadowTopLeft(Game.shadowHeight(z+1, y-1, x+1), sx, sy, rx,ry,src, false);
+ shadowBotRight(Game.shadowHeight(z+1, y-1, x+1), sx, sy, rx, ry, src, false);
+
+ return;
+
+}
+
+// ---------------------------------------------------------
+void BoardWidget::paintEvent( QPaintEvent* pa )
+{
+ QPixmap *back;
+
+ int xx = pa->rect().left();
+ int xheight = pa->rect().height();
+ int xwidth = pa->rect().width();
+
+ back = theBackground.getBackground();
+
+ if (gamePaused) {
+ // If the game is paused, then blank out the board.
+ // We tolerate no cheats around here folks..
+ bitBlt( this, xx, pa->rect().top(),
+ back, xx, pa->rect().top(), xwidth, xheight, CopyROP );
+ return;
+ }
+
+ // if the repaint is because of a window redraw after a move
+ // or a menu roll up, then just blit in the last rendered image
+ if (!updateBackBuffer) {
+ bitBlt(this, xx,pa->rect().top(),
+ &backBuffer, xx, pa->rect().top(), xwidth, xheight, CopyROP);
+ return;
+ }
+
+ // update the complete drawArea
+
+ backBuffer.resize(back->width(), back->height());
+
+ // erase out with the background
+ bitBlt( &backBuffer, xx, pa->rect().top(),
+ back, xx,pa->rect().top(), back->width(), back->height(), CopyROP );
+
+ // initial offset on the screen of tile 0,0
+ int xOffset = theTiles.width()/2;
+ int yOffset = theTiles.height()/2;
+ //short tile = 0;
+
+ // shadow the background first
+ if (Prefs::showShadows()) {
+ for (int by=0; by <BoardLayout::height+1; by++)
+ for (int bx=-1; bx < BoardLayout::width+1; bx++)
+ shadowArea(-1, by, bx,
+ bx*theTiles.qWidth()+xOffset-theTiles.shadowSize(),
+ by*theTiles.qHeight()+yOffset+theTiles.shadowSize(),
+ bx*theTiles.qWidth()+xOffset-theTiles.shadowSize(),
+ by*theTiles.qHeight()+yOffset+theTiles.shadowSize(),
+ theBackground.getShadowBackground());
+ }
+
+
+
+
+ // we iterate over the depth stacking order. Each successive level is
+ // drawn one indent up and to the right. The indent is the width
+ // of the 3d relief on the tile left (tile shadow width)
+ for (int z=0; z<BoardLayout::depth; z++) {
+ // we draw down the board so the tile below over rights our border
+ for (int y = 0; y < BoardLayout::height; y++) {
+ // drawing right to left to prevent border overwrite
+ for (int x=BoardLayout::width-1; x>=0; x--) {
+ int sx = x*(theTiles.qWidth() )+xOffset;
+ int sy = y*(theTiles.qHeight() )+yOffset;
+
+
+
+ // skip if no tile to display
+ if (!Game.tilePresent(z,y,x))
+ continue;
+
+ QPixmap *t;
+ QPixmap *s;
+ if (Game.hilighted[z][y][x]) {
+ t= theTiles.selectedPixmaps(
+ Game.Board[z][y][x]-TILE_OFFSET);
+ s= theTiles.selectedShadowPixmaps(
+ Game.Board[z][y][x]-TILE_OFFSET);
+ } else {
+ t= theTiles.unselectedPixmaps(
+ Game.Board[z][y][x]-TILE_OFFSET);
+ s= theTiles.unselectedShadowPixmaps(
+ Game.Board[z][y][x]-TILE_OFFSET);
+ }
+
+ // Only one compilcation. Since we render top to bottom , left
+ // to right situations arise where...:
+ // there exists a tile one q height above and to the left
+ // in this situation we would draw our top left border over it
+ // we simply split the tile draw so the top half is drawn
+ // minus border
+
+ if (x > 1 && y > 0 && Game.tilePresent(z, y-1, x-2)){
+ bitBlt( &backBuffer,
+ sx+theTiles.shadowSize(), sy,
+ t, theTiles.shadowSize() ,0,
+ t->width()-theTiles.shadowSize(),
+ t->height()/2, CopyROP );
+ bitBlt( &backBuffer, sx, sy+t->height()/2,
+ t, 0,t->height()/2,t->width(),t->height()/2,CopyROP);
+ } else {
+
+ bitBlt( &backBuffer, sx, sy,
+ t, 0,0, t->width(), t->height(), CopyROP );
+ }
+
+
+ if (Prefs::showShadows() && z<BoardLayout::depth - 1) {
+ for (int xp = 0; xp <= 1; xp++) {
+ for (int yp=0; yp <= 1; yp++) {
+ shadowArea(z, y+yp, x+xp,
+ sx+(xp*theTiles.qWidth()),
+ sy+(yp*theTiles.qHeight()),
+ xp*theTiles.qWidth(),
+ yp*theTiles.qHeight(),
+ s);
+ }
+ }
+
+ }
+
+
+
+ }
+ }
+ xOffset +=theTiles.shadowSize();
+ yOffset -=theTiles.shadowSize();
+ }
+
+
+ // Now we add the list of cancelled tiles
+
+ // we start blitting as usuall right to left, top to bottom, first
+ // we calculate the start pos of the first tile, allowing space for
+ // the upwards at rightwards creep when stacking in 3d
+ unsigned short xPos = backBuffer.width()-(3*theTiles.shadowSize())-theTiles.width();
+ unsigned short yPos = (3*theTiles.shadowSize());
+
+ for (int pos=0; pos < 9; pos++) {
+ int last = 0;
+ int tile=0;
+ // dragon?
+ if (pos >= 0 && pos < 3) {
+ last = removedDragon[pos];
+ tile = TILE_DRAGON+pos;
+ } else {
+ //Wind?
+ if (pos >= 3 && pos < 7) {
+ last = removedWind[pos-3];
+ tile = TILE_WIND+pos-3;
+ } else {
+ if (pos == 7) {
+ for (int t=0; t<4;t++) {
+ if (removedFlower[t]) {
+ last++;
+ tile=TILE_FLOWER+t;
+ }
+ }
+ } else {
+ for (int t=0; t<4;t++) {
+ if (removedSeason[t]) {
+ last++;
+ tile=TILE_SEASON+t;
+ }
+ }
+ }
+ }
+ }
+
+ stackTiles(tile, last, xPos, yPos);
+ stackTiles(TILE_ROD+pos, removedRod[pos],
+ xPos - (1*(theTiles.width() - theTiles.shadowSize())) , yPos);
+ stackTiles(TILE_BAMBOO+pos, removedBamboo[pos],
+ xPos - (2*(theTiles.width() - theTiles.shadowSize())) , yPos);
+ stackTiles(TILE_CHARACTER+pos, removedCharacter[pos],
+ xPos - (3*(theTiles.width() - theTiles.shadowSize())) , yPos);
+
+
+
+ yPos += theTiles.height()-theTiles.shadowSize();
+ }
+
+ updateBackBuffer=false;
+ bitBlt(this, xx,pa->rect().top(), &backBuffer, xx, pa->rect().top(), xwidth, xheight, CopyROP);
+
+
+}
+
+void BoardWidget::stackTiles(unsigned char t, unsigned short h, unsigned short x,unsigned short y)
+{
+
+ int ss = theTiles.shadowSize();
+ QPainter p(&backBuffer);
+ QPen line;
+ p.setBackgroundMode(OpaqueMode);
+ p.setBackgroundColor(black);
+
+
+
+
+ line.setWidth(1);
+ line.setColor(white);
+ p.setPen(line);
+ int x2 = x+theTiles.width()-ss-1;
+ int y2 = y+theTiles.height()-1;
+ p.drawLine(x, y+ss, x2, y+ss);
+ p.drawLine(x, y+ss, x, y2);
+ p.drawLine(x2, y+ss, x2, y2);
+ p.drawLine(x+1, y2, x2, y2);
+
+ // p.fillRect(x+1, y+ss+1, theTiles.width()-ss-2, theTiles.height()-ss-2, QBrush(lightGray));
+
+ for (unsigned short pos=0; pos < h; pos++) {
+ QPixmap *p = theTiles.unselectedPixmaps(t-TILE_OFFSET);
+ bitBlt( &backBuffer, x+(pos*ss), y-(pos*ss),
+ p, 0,0, p->width(), p->height(), CopyROP );
+ }
+
+}
+
+
+void BoardWidget::pause() {
+ gamePaused = !gamePaused;
+ drawBoard(true);
+}
+
+void BoardWidget::gameLoaded()
+{
+ int i;
+ initialiseRemovedTiles();
+ i = Game.TileNum;
+ // use the history of moves to put in the removed tiles area the correct tiles
+ while (i < Game.MaxTileNum )
+ {
+ setRemovedTilePair(Game.MoveList[i], Game.MoveList[i+1]);
+ i +=2;
+ }
+ drawBoard();
+}
+
+// ---------------------------------------------------------
+int BoardWidget::undoMove()
+{
+ cancelUserSelectedTiles();
+
+ if( Game.TileNum < Game.MaxTileNum )
+ {
+
+ clearRemovedTilePair(Game.MoveList[Game.TileNum], Game.MoveList[Game.TileNum+1]);
+ putTile( Game.MoveList[Game.TileNum], false );
+ Game.TileNum++;
+ putTile( Game.MoveList[Game.TileNum] );
+ Game.TileNum++;
+ drawTileNumber();
+ setStatusText( i18n("Undo operation done successfully.") );
+ return 1;
+ }
+ else {
+ setStatusText(i18n("What do you want to undo? You have done nothing!"));
+ return 0;
+ }
+}
+
+// ---------------------------------------------------------
+void BoardWidget::helpMove()
+{
+ cancelUserSelectedTiles();
+ if (showHelp) helpMoveStop();
+
+ if( findMove( TimerPos1, TimerPos2 ) )
+ {
+ cheatsUsed++;
+ iTimerStep = 1;
+ showHelp = true;
+ helpMoveTimeout();
+ }
+ else
+ setStatusText( i18n("Sorry, you have lost the game.") );
+}
+// ---------------------------------------------------------
+void BoardWidget::helpMoveTimeout()
+{
+ if( iTimerStep & 1 )
+ {
+ hilightTile( TimerPos1, true, false );
+ hilightTile( TimerPos2, true );
+ }
+ else
+ {
+ hilightTile( TimerPos1, false, false );
+ hilightTile( TimerPos2, false );
+ }
+ // restart timer
+ if( iTimerStep++ < 8 )
+ timer->start( ANIMSPEED , true );
+ else
+ showHelp = false;
+}
+// ---------------------------------------------------------
+
+void BoardWidget::helpMoveStop()
+{
+ timer->stop();
+ iTimerStep = 8;
+ hilightTile( TimerPos1, false, false );
+ hilightTile( TimerPos2, false );
+ showHelp = false;
+}
+
+// ---------------------------------------------------------
+void BoardWidget::startDemoMode()
+{
+ calculateNewGame();
+
+ if( TimerState == Stop )
+ {
+ TimerState = Demo;
+ iTimerStep = 0;
+ emit demoModeChanged( true );
+ setStatusText( i18n("Demo mode. Click mousebutton to stop.") );
+ demoMoveTimeout();
+ }
+}
+// ---------------------------------------------------------
+void BoardWidget::stopDemoMode()
+{
+ TimerState = Stop; // stop demo
+ calculateNewGame();
+ setStatusText( i18n("Now it's you again.") );
+ emit demoModeChanged( false );
+ emit gameCalculated();
+}
+// ---------------------------------------------------------
+void BoardWidget::demoMoveTimeout()
+{
+ if( TimerState == Demo )
+ {
+ switch( iTimerStep++ % 6 )
+ {
+ // at firts, find new matching tiles
+ case 0:
+ if( ! findMove( TimerPos1, TimerPos2 ) )
+ {
+ // if computer has won
+ if( Game.TileNum == 0 )
+ {
+ animateMoveList();
+ }
+ // else computer has lost
+ else
+ {
+ setStatusText( i18n("Your computer has lost the game.") );
+ while( Game.TileNum < Game.MaxTileNum )
+ {
+ putTile( Game.MoveList[Game.TileNum], false );
+ Game.TileNum++;
+ putTile( Game.MoveList[Game.TileNum] );
+ Game.TileNum++;
+ drawTileNumber();
+ }
+ }
+ TimerState = Stop;
+ startDemoMode();
+ return;
+ }
+ break;
+ // hilight matching tiles two times
+ case 1:
+ case 3:
+ hilightTile( TimerPos1, true, false );
+ hilightTile( TimerPos2, true );
+ break;
+
+ case 2:
+ case 4:
+ hilightTile( TimerPos1, false, false );
+ hilightTile( TimerPos2, false );
+ break;
+ // remove matching tiles from game board
+ case 5:
+ setRemovedTilePair(TimerPos1, TimerPos2);
+ removeTile( TimerPos1, false );
+ removeTile( TimerPos2 );
+ drawTileNumber();
+ break;
+ }
+ // restart timer
+ QTimer::singleShot( ANIMSPEED, this, SLOT( demoMoveTimeout() ) );
+ }
+}
+
+// ---------------------------------------------------------
+void BoardWidget::setShowMatch( bool show )
+{
+ if( showMatch )
+ stopMatchAnimation();
+ showMatch = show;
+}
+// ---------------------------------------------------------
+void BoardWidget::matchAnimationTimeout()
+{
+ if (matchCount == 0)
+ return;
+
+ if( iTimerStep++ & 1 )
+ {
+ for(short Pos = 0; Pos < matchCount; Pos++)
+ {
+
+
+ hilightTile(PosTable[Pos], true);
+ }
+ }
+ else
+ {
+ for(short Pos = 0; Pos < matchCount; Pos++)
+ {
+ hilightTile(PosTable[Pos], false);
+ }
+ }
+ if( TimerState == Match )
+ QTimer::singleShot( ANIMSPEED, this, SLOT( matchAnimationTimeout() ) );
+}
+// ---------------------------------------------------------
+void BoardWidget::stopMatchAnimation()
+{
+ for(short Pos = 0; Pos < matchCount; Pos++)
+ {
+ hilightTile(PosTable[Pos], false);
+ }
+ TimerState = Stop;
+ matchCount = 0;
+}
+
+void BoardWidget::redoMove()
+{
+
+ setRemovedTilePair(Game.MoveList[Game.TileNum-1],Game.MoveList[Game.TileNum-2]);
+ removeTile(Game.MoveList[Game.TileNum-1], false);
+ removeTile(Game.MoveList[Game.TileNum-1]);
+ drawTileNumber();
+}
+
+// ---------------------------------------------------------
+void BoardWidget::animateMoveList()
+{
+ setStatusText( i18n("Congratulations. You have won!") );
+
+ if (Prefs::playAnimation())
+ {
+ while( Game.TileNum < Game.MaxTileNum )
+ {
+ // put back all tiles
+ putTile(Game.MoveList[Game.TileNum]);
+ Game.TileNum++;
+ putTile(Game.MoveList[Game.TileNum], false);
+ Game.TileNum++;
+ drawTileNumber();
+ }
+ while( Game.TileNum > 0 )
+ {
+ // remove all tiles
+ removeTile(Game.MoveList[Game.TileNum-1], false);
+ removeTile(Game.MoveList[Game.TileNum-1]);
+ drawTileNumber();
+ }
+ }
+
+ calculateNewGame();
+}
+
+// ---------------------------------------------------------
+void BoardWidget::calculateNewGame( int gNumber)
+{
+ cancelUserSelectedTiles();
+ stopMatchAnimation();
+ initialiseRemovedTiles();
+ setStatusText( i18n("Calculating new game...") );
+
+
+ if( !loadBoard())
+ {
+ setStatusText( i18n("Error converting board information!") );
+ return;
+ }
+
+ if (gNumber == -1) {
+ gameGenerationNum = kapp->random();
+ } else {
+ gameGenerationNum = gNumber;
+ }
+
+ random.setSeed(gameGenerationNum);
+
+ // Translate Game.Map to an array of POSITION data. We only need to
+ // do this once for each new game.
+ memset(tilePositions, 0, sizeof(tilePositions));
+ generateTilePositions();
+
+ // Now use the tile position data to generate tile dependency data.
+ // We only need to do this once for each new game.
+ generatePositionDepends();
+
+ // Now try to position tiles on the board, 64 tries max.
+ for( short nr=0; nr<64; nr++ )
+ {
+ if( generateStartPosition2() )
+ {
+ drawBoard();
+ setStatusText( i18n("Ready. Now it is your turn.") );
+ cheatsUsed=0;
+ return;
+ }
+ }
+
+ drawBoard();
+ setStatusText( i18n("Error generating new game!") );
+}
+
+// ---------------------------------------------------------
+// Generate the position data for the layout from contents of Game.Map.
+void BoardWidget::generateTilePositions() {
+
+ numTiles = 0;
+
+ for (int z=0; z< BoardLayout::depth; z++) {
+ for (int y=0; y<BoardLayout::height; y++) {
+ for (int x=0; x<BoardLayout::width; x++) {
+ Game.Board[z][y][x] = 0;
+ if (Game.Mask[z][y][x] == '1') {
+ tilePositions[numTiles].x = x;
+ tilePositions[numTiles].y = y;
+ tilePositions[numTiles].e = z;
+ tilePositions[numTiles].f = 254;
+ numTiles++;
+ }
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------
+// Generate the dependency data for the layout from the position data.
+// Note that the coordinates of each tile in tilePositions are those of
+// the upper left quarter of the tile.
+void BoardWidget::generatePositionDepends() {
+
+ // For each tile,
+ for (int i = 0; i < numTiles; i++) {
+
+ // Get its basic position data
+ int x = tilePositions[i].x;
+ int y = tilePositions[i].y;
+ int z = tilePositions[i].e;
+
+ // LHS dependencies
+ positionDepends[i].lhs_dep[0] = tileAt(x-1, y, z);
+ positionDepends[i].lhs_dep[1] = tileAt(x-1, y+1, z);
+
+ // Make them unique
+ if (positionDepends[i].lhs_dep[1] == positionDepends[i].lhs_dep[0]) {
+ positionDepends[i].lhs_dep[1] = -1;
+ }
+
+ // RHS dependencies
+ positionDepends[i].rhs_dep[0] = tileAt(x+2, y, z);
+ positionDepends[i].rhs_dep[1] = tileAt(x+2, y+1, z);
+
+ // Make them unique
+ if (positionDepends[i].rhs_dep[1] == positionDepends[i].rhs_dep[0]) {
+ positionDepends[i].rhs_dep[1] = -1;
+ }
+
+ // Turn dependencies
+ positionDepends[i].turn_dep[0] = tileAt(x, y, z+1);
+ positionDepends[i].turn_dep[1] = tileAt(x+1, y, z+1);
+ positionDepends[i].turn_dep[2] = tileAt(x+1, y+1, z+1);
+ positionDepends[i].turn_dep[3] = tileAt(x, y+1, z+1);
+
+ // Make them unique
+ for (int j = 0; j < 3; j++) {
+ for (int k = j+1; k < 4; k++) {
+ if (positionDepends[i].turn_dep[j] ==
+ positionDepends[i].turn_dep[k]) {
+ positionDepends[i].turn_dep[k] = -1;
+ }
+ }
+ }
+
+ // Placement dependencies
+ positionDepends[i].place_dep[0] = tileAt(x, y, z-1);
+ positionDepends[i].place_dep[1] = tileAt(x+1, y, z-1);
+ positionDepends[i].place_dep[2] = tileAt(x+1, y+1, z-1);
+ positionDepends[i].place_dep[3] = tileAt(x, y+1, z-1);
+
+ // Make them unique
+ for (int j = 0; j < 3; j++) {
+ for (int k = j+1; k < 4; k++) {
+ if (positionDepends[i].place_dep[j] ==
+ positionDepends[i].place_dep[k]) {
+ positionDepends[i].place_dep[k] = -1;
+ }
+ }
+ }
+
+ // Filled and free indicators.
+ positionDepends[i].filled = false;
+ positionDepends[i].free = false;
+ }
+}
+
+// ---------------------------------------------------------
+// x, y, z are the coordinates of a *quarter* tile. This returns the
+// index (in positions) of the tile at those coordinates or -1 if there
+// is no tile at those coordinates. Note that the coordinates of each
+// tile in positions are those of the upper left quarter of the tile.
+int BoardWidget::tileAt(int x, int y, int z) {
+
+ for (int i = 0; i < numTiles; i++) {
+ if (tilePositions[i].e == z) {
+ if ((tilePositions[i].x == x && tilePositions[i].y == y) ||
+ (tilePositions[i].x == x-1 && tilePositions[i].y == y) ||
+ (tilePositions[i].x == x-1 && tilePositions[i].y == y-1) ||
+ (tilePositions[i].x == x && tilePositions[i].y == y-1)) {
+
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+// ---------------------------------------------------------
+bool BoardWidget::generateSolvableGame() {
+
+ // Initially we want to mark positions on layer 0 so that we have only
+ // one free position per apparent horizontal line.
+ for (int i = 0; i < numTiles; i++) {
+
+ // Pick a random tile on layer 0
+ int position, cnt = 0;
+ do {
+ position = (int) random.getLong(numTiles);
+ if (cnt++ > (numTiles*numTiles)) {
+ return false; // bail
+ }
+ } while (tilePositions[position].e != 0);
+
+ // If there are no other free positions on the same apparent
+ // horizontal line, we can mark that position as free.
+ if (onlyFreeInLine(position)) {
+ positionDepends[position].free = true;
+ }
+ }
+
+ // Check to make sure we really got them all. Very important for
+ // this algorithm.
+ for (int i = 0; i < numTiles; i++) {
+ if (tilePositions[i].e == 0 && onlyFreeInLine(i)) {
+ positionDepends[i].free = true;
+ }
+ }
+
+ // Get ready to place the tiles
+ int lastPosition = -1;
+ int position = -1;
+ int position2 = -1;
+
+ // For each position,
+ for (int i = 0; i < numTiles; i++) {
+
+ // If this is the first tile in a 144 tile set,
+ if ((i % 144) == 0) {
+
+ // Initialise the faces to allocate. For the classic
+ // dragon board there are 144 tiles. So we allocate and
+ // randomise the assignment of 144 tiles. If there are > 144
+ // tiles we will reallocate and re-randomise as we run out.
+ // One advantage of this method is that the pairs to assign are
+ // non-linear. In kmahjongg 0.4, If there were > 144 the same
+ // allocation series was followed. So 154 = 144 + 10 rods.
+ // 184 = 144 + 40 rods (20 pairs) which overwhemed the board
+ // with rods and made deadlock games more likely.
+ randomiseFaces();
+ }
+
+ // If this is the first half of a pair, there is no previous
+ // position for the pair.
+ if ((i & 1) == 0) {
+ lastPosition = -1;
+ }
+
+ // Select a position for the tile, relative to the position of
+ // the last tile placed.
+ if ((position = selectPosition(lastPosition)) < 0) {
+ return false; // bail
+ }
+ if (i < numTiles-1) {
+ if ((position2 = selectPosition(lastPosition)) < 0) {
+ return false; // bail
+ }
+ if (tilePositions[position2].e > tilePositions[position].e) {
+ position = position2; // higher is better
+ }
+ }
+
+ // Place the tile.
+ placeTile(position, tilePair[i % 144]);
+
+ // Remember the position
+ lastPosition = position;
+ }
+
+ // The game is solvable.
+ return true;
+}
+
+// ---------------------------------------------------------
+// Determines whether it is ok to mark this position as "free" because
+// there are no other positions marked "free" in its apparent horizontal
+// line.
+bool BoardWidget::onlyFreeInLine(int position) {
+
+ int i, i0, w;
+ int lin, rin, out;
+ static int nextLeft[BoardLayout::maxTiles];
+ static int nextRight[BoardLayout::maxTiles];
+
+ /* Check left, starting at position */
+ lin = 0;
+ out = 0;
+ nextLeft[lin++] = position;
+ do {
+ w = nextLeft[out++];
+ if (positionDepends[w].free || positionDepends[w].filled) {
+ return false;
+ }
+ if ((i = positionDepends[w].lhs_dep[0]) != -1) {
+ nextLeft[lin++] = i;
+ }
+ i0 = i;
+ if ((i = positionDepends[w].lhs_dep[1]) != -1 && i0 != i) {
+ nextLeft[lin++] = i;
+ }
+ }
+ while (lin > out) ;
+
+ /* Check right, starting at position */
+ rin = 0;
+ out = 0;
+ nextRight[rin++] = position;
+ do {
+ w = nextRight[out++];
+ if (positionDepends[w].free || positionDepends[w].filled) {
+ return false;
+ }
+ if ((i = positionDepends[w].rhs_dep[0]) != -1) {
+ nextRight[rin++] = i;
+ }
+ i0 = i;
+ if ((i = positionDepends[w].rhs_dep[1]) != -1 && i0 != i) {
+ nextRight[rin++] = i;
+ }
+ }
+ while (rin > out) ;
+
+ // Here, the position can be marked "free"
+ return true;
+}
+
+// ---------------------------------------------------------
+int BoardWidget::selectPosition(int lastPosition) {
+
+ int position, cnt = 0;
+ bool goodPosition = false;
+
+ // while a good position has not been found,
+ while (!goodPosition) {
+
+ // Select a random, but free, position.
+ do {
+ position = random.getLong(numTiles);
+ if (cnt++ > (numTiles*numTiles)) {
+ return -1; // bail
+ }
+ } while (!positionDepends[position].free);
+
+ // Found one.
+ goodPosition = true;
+
+ // If there is a previous position to take into account,
+ if (lastPosition != -1) {
+
+ // Check the new position against the last one.
+ for (int i = 0; i < 4; i++) {
+ if (positionDepends[position].place_dep[i] == lastPosition) {
+ goodPosition = false; // not such a good position
+ }
+ }
+ for (int i = 0; i < 2; i++) {
+ if ((positionDepends[position].lhs_dep[i] == lastPosition) ||
+ (positionDepends[position].rhs_dep[i] == lastPosition)) {
+ goodPosition = false; // not such a good position
+ }
+ }
+ }
+ }
+
+ return position;
+}
+
+// ---------------------------------------------------------
+void BoardWidget::placeTile(int position, int tile) {
+
+ // Install the tile in the specified position
+ tilePositions[position].f = tile;
+ Game.putTile(tilePositions[position]);
+
+ // Update position dependency data
+ positionDepends[position].filled = true;
+ positionDepends[position].free = false;
+
+ // Now examine the tiles near this to see if this makes them "free".
+ int depend;
+ for (int i = 0; i < 4; i++) {
+ if ((depend = positionDepends[position].turn_dep[i]) != -1) {
+ updateDepend(depend);
+ }
+ }
+ for (int i = 0; i < 2; i++) {
+ if ((depend = positionDepends[position].lhs_dep[i]) != -1) {
+ updateDepend(depend);
+ }
+ if ((depend = positionDepends[position].rhs_dep[i]) != -1) {
+ updateDepend(depend);
+ }
+ }
+}
+
+// ---------------------------------------------------------
+// Updates the free indicator in the dependency data for a position
+// based on whether the positions on which it depends are filled.
+void BoardWidget::updateDepend(int position) {
+
+ // If the position is valid and not filled
+ if (position >= 0 && !positionDepends[position].filled) {
+
+ // Check placement depends. If they are not filled, the
+ // position cannot become free.
+ int depend;
+ for (int i = 0; i < 4; i++) {
+ if ((depend = positionDepends[position].place_dep[i]) != -1) {
+ if (!positionDepends[depend].filled) {
+ return ;
+ }
+ }
+ }
+
+ // If position is first free on apparent horizontal, it is
+ // now free to be filled.
+ if (onlyFreeInLine(position)) {
+ positionDepends[position].free = true;
+ return;
+ }
+
+ // Assume no LHS positions to fill
+ bool lfilled = false;
+
+ // If positions to LHS
+ if ((positionDepends[position].lhs_dep[0] != -1) ||
+ (positionDepends[position].lhs_dep[1] != -1)) {
+
+ // Assume LHS positions filled
+ lfilled = true;
+
+ for (int i = 0; i < 2; i++) {
+ if ((depend = positionDepends[position].lhs_dep[i]) != -1) {
+ if (!positionDepends[depend].filled) {
+ lfilled = false;
+ }
+ }
+ }
+ }
+
+ // Assume no RHS positions to fill
+ bool rfilled = false;
+
+ // If positions to RHS
+ if ((positionDepends[position].rhs_dep[0] != -1) ||
+ (positionDepends[position].rhs_dep[1] != -1)) {
+
+ // Assume LHS positions filled
+ rfilled = true;
+
+ for (int i = 0; i < 2; i++) {
+ if ((depend = positionDepends[position].rhs_dep[i]) != -1) {
+ if (!positionDepends[depend].filled) {
+ rfilled = false;
+ }
+ }
+ }
+ }
+
+ // If positions to left or right are filled, this position
+ // is now free to be filled.
+ positionDepends[position].free = (lfilled || rfilled);
+ }
+}
+
+// ---------------------------------------------------------
+bool BoardWidget::generateStartPosition2() {
+
+ // For each tile,
+ for (int i = 0; i < numTiles; i++) {
+
+ // Get its basic position data
+ int x = tilePositions[i].x;
+ int y = tilePositions[i].y;
+ int z = tilePositions[i].e;
+
+ // Clear Game.Board at that position
+ Game.Board[z][y][x] = 0;
+
+ // Clear tile placed/free indicator(s).
+ positionDepends[i].filled = false;
+ positionDepends[i].free = false;
+
+ // Set tile face blank
+ tilePositions[i].f = 254;
+ }
+
+ // If solvable games should be generated,
+ if (Prefs::solvableGames()) {
+
+ if (generateSolvableGame()) {
+ Game.TileNum = Game.MaxTileNum;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ // Initialise the faces to allocate. For the classic
+ // dragon board there are 144 tiles. So we allocate and
+ // randomise the assignment of 144 tiles. If there are > 144
+ // tiles we will reallocate and re-randomise as we run out.
+ // One advantage of this method is that the pairs to assign are
+ // non-linear. In kmahjongg 0.4, If there were > 144 the same
+ // allocation series was followed. So 154 = 144 + 10 rods.
+ // 184 = 144 + 40 rods (20 pairs) which overwhemed the board
+ // with rods and made deadlock games more likely.
+
+ int remaining = numTiles;
+ randomiseFaces();
+
+ for (int tile=0; tile <numTiles; tile+=2) {
+ int p1;
+ int p2;
+
+ if (remaining > 2) {
+ p2 = p1 = random.getLong(remaining-2);
+ int bail = 0;
+ while (p1 == p2) {
+ p2 = random.getLong(remaining-2);
+
+ if (bail >= 100) {
+ if (p1 != p2) {
+ break;
+ }
+ }
+ if ((tilePositions[p1].y == tilePositions[p2].y) &&
+ (tilePositions[p1].e == tilePositions[p2].e)) {
+ // skip if on same y line
+ bail++;
+ p2=p1;
+ continue;
+ }
+ }
+ } else {
+ p1 = 0;
+ p2 = 1;
+ }
+ POSITION a, b;
+ a = tilePositions[p1];
+ b = tilePositions[p2];
+ tilePositions[p1] = tilePositions[remaining - 1];
+ tilePositions[p2] = tilePositions[remaining - 2];
+ remaining -= 2;
+
+ getFaces(a, b);
+ Game.putTile(a);
+ Game.putTile(b);
+ }
+
+ Game.TileNum = Game.MaxTileNum;
+ return 1;
+}
+
+void BoardWidget::getFaces(POSITION &a, POSITION &b) {
+ a.f = tilePair[tilesUsed];
+ b.f = tilePair[tilesUsed+1];
+ tilesUsed += 2;
+
+ if (tilesUsed >= 144) {
+ randomiseFaces();
+ }
+}
+
+void BoardWidget::randomiseFaces() {
+ int nr;
+ int numAlloced=0;
+ // stick in 144 tiles in pairsa.
+
+ for( nr=0; nr<9*4; nr++)
+ tilePair[numAlloced++] = TILE_CHARACTER+(nr/4); // 4*9 Tiles
+ for( nr=0; nr<9*4; nr++)
+ tilePair[numAlloced++] = TILE_BAMBOO+(nr/4); // 4*9 Tiles
+ for( nr=0; nr<9*4; nr++)
+ tilePair[numAlloced++] = TILE_ROD+(nr/4); // 4*9 Tiles
+ for( nr=0; nr<4; nr++)
+ tilePair[numAlloced++] = TILE_FLOWER+nr; // 4 Tiles
+ for( nr=0; nr<4; nr++)
+ tilePair[numAlloced++] = TILE_SEASON+nr; // 4 Tiles
+ for( nr=0; nr<4*4; nr++)
+ tilePair[numAlloced++] = TILE_WIND+(nr/4); // 4*4 Tiles
+ for( nr=0; nr<3*4; nr++)
+ tilePair[numAlloced++] = TILE_DRAGON+(nr/4); // 3*4 Tiles
+
+
+ //randomise. Keep pairs together. Ie take two random
+ //odd numbers (n,x) and swap n, n+1 with x, x+1
+
+ int at=0;
+ int to=0;
+ for (int r=0; r<200; r++) {
+
+
+ to=at;
+ while (to==at) {
+ to = random.getLong(144);
+
+ if ((to & 1) != 0)
+ to--;
+
+ }
+ UCHAR tmp = tilePair[at];
+ tilePair[at] = tilePair[to];
+ tilePair[to] = tmp;
+ tmp = tilePair[at+1];
+ tilePair[at+1] = tilePair[to+1];
+ tilePair[to+1] = tmp;
+
+
+ at+=2;
+ if (at >= 144)
+ at =0;
+ }
+
+ tilesAllocated = numAlloced;
+ tilesUsed = 0;
+}
+
+
+// ---------------------------------------------------------
+bool isFlower( UCHAR Tile )
+{
+ return( Tile >= TILE_FLOWER && Tile <=TILE_FLOWER+3 );
+}
+bool isSeason( UCHAR Tile )
+{
+ return( Tile >= TILE_SEASON && Tile <=TILE_SEASON+3 );
+}
+bool isBamboo(UCHAR t) {
+ return( t >= TILE_BAMBOO && t <TILE_BAMBOO+9);
+}
+bool isCharacter(UCHAR t) {
+ return( t >= TILE_CHARACTER && t <TILE_CHARACTER + 9);
+}
+bool isRod(UCHAR t) {
+ return( t >= TILE_ROD && t <TILE_ROD + 9);
+}
+bool isDragon(UCHAR t) {
+ return( t >= TILE_DRAGON && t < TILE_DRAGON +3);
+}
+bool isWind(UCHAR t) {
+ return( t >= TILE_WIND && t < TILE_WIND +4);
+}
+
+
+bool BoardWidget::isMatchingTile( POSITION& Pos1, POSITION& Pos2 )
+{
+ // don't compare 'equal' positions
+ if( memcmp( &Pos1, &Pos2, sizeof(POSITION) ) )
+ {
+ UCHAR FA = Pos1.f;
+ UCHAR FB = Pos2.f;
+
+ if( (FA == FB)
+ || ( isFlower( FA ) && isFlower( FB ) )
+ || ( isSeason( FA ) && isSeason( FB ) ) )
+ return( true );
+ }
+ return( false );
+}
+
+// ---------------------------------------------------------
+bool BoardWidget::findMove( POSITION& posA, POSITION& posB )
+{
+ short Pos_Ende = Game.MaxTileNum; // Ende der PosTable
+
+ for( short E=0; E<BoardLayout::depth; E++ )
+ {
+ for( short Y=0; Y<BoardLayout::height-1; Y++ )
+ {
+ for( short X=0; X<BoardLayout::width-1; X++ )
+ {
+ if( Game.Mask[E][Y][X] != (UCHAR) '1' )
+ continue;
+ if( ! Game.Board[E][Y][X] )
+ continue;
+ if( E < 4 )
+ {
+ if( Game.Board[E+1][Y][X] || Game.Board[E+1][Y+1][X] ||
+ Game.Board[E+1][Y][X+1] || Game.Board[E+1][Y+1][X+1] )
+ continue;
+ }
+ if( (Game.Board[E][Y][X-1] || Game.Board[E][Y+1][X-1]) &&
+ (Game.Board[E][Y][X+2] || Game.Board[E][Y+1][X+2]) )
+ continue;
+
+ Pos_Ende--;
+ PosTable[Pos_Ende].e = E;
+ PosTable[Pos_Ende].y = Y;
+ PosTable[Pos_Ende].x = X;
+ PosTable[Pos_Ende].f = Game.Board[E][Y][X];
+
+
+
+
+ }
+ }
+ }
+
+ // PosTable[0].e = BoardLayout::depth; // 1. Paar noch nicht gefunden
+ iPosCount = 0; // Hier Anzahl der gefunden Paare merken
+
+
+ // The new tile layout with non-contiguos horizantle spans
+ // can lead to huge numbers of matching pairs being exposed.
+ // we alter the loop to bail out when BoardLayout::maxTiles/2 pairs are found
+ // (or less);
+ while( Pos_Ende < Game.MaxTileNum-1 && iPosCount <BoardLayout::maxTiles-2)
+ {
+ for( short Pos = Pos_Ende+1; Pos < Game.MaxTileNum; Pos++)
+ {
+ if( isMatchingTile(PosTable[Pos], PosTable[Pos_Ende]) )
+ {
+ if (iPosCount <BoardLayout::maxTiles-2) {
+ PosTable[iPosCount++] = PosTable[Pos_Ende];
+ PosTable[iPosCount++] = PosTable[Pos];
+ }
+ }
+ }
+ Pos_Ende++;
+ }
+
+ if( iPosCount>=2 )
+ {
+ random.setSeed(0); // WABA: Why is the seed reset?
+ short Pos = random.getLong(iPosCount) & -2; // Gerader Wert
+ posA = PosTable[Pos];
+ posB = PosTable[Pos+1];
+
+ return( true );
+ }
+ else
+ return( false );
+}
+
+int BoardWidget::moveCount( )
+{
+ short Pos_Ende = Game.MaxTileNum; // end of PosTable
+
+ for( short E=0; E<BoardLayout::depth; E++ )
+ {
+ for( short Y=0; Y<BoardLayout::height-1; Y++ )
+ {
+ for( short X=0; X<BoardLayout::width-1; X++ )
+ {
+ if( Game.Mask[E][Y][X] != (UCHAR) '1' )
+ continue;
+ if( ! Game.Board[E][Y][X] )
+ continue;
+ if( E < 4 )
+ {
+ if( Game.Board[E+1][Y][X] || Game.Board[E+1][Y+1][X] ||
+ Game.Board[E+1][Y][X+1] || Game.Board[E+1][Y+1][X+1] )
+ continue;
+ }
+ if( (Game.Board[E][Y][X-1] || Game.Board[E][Y+1][X-1]) &&
+ (Game.Board[E][Y][X+2] || Game.Board[E][Y+1][X+2]) )
+ continue;
+
+ Pos_Ende--;
+ PosTable[Pos_Ende].e = E;
+ PosTable[Pos_Ende].y = Y;
+ PosTable[Pos_Ende].x = X;
+ PosTable[Pos_Ende].f = Game.Board[E][Y][X];
+
+ }
+ }
+ }
+
+ iPosCount = 0; // store number of pairs found
+
+ while( Pos_Ende < Game.MaxTileNum-1 && iPosCount <BoardLayout::maxTiles-2)
+ {
+ for( short Pos = Pos_Ende+1; Pos < Game.MaxTileNum; Pos++)
+ {
+ if( isMatchingTile(PosTable[Pos], PosTable[Pos_Ende]) )
+ {
+ if (iPosCount <BoardLayout::maxTiles-2) {
+ PosTable[iPosCount++] = PosTable[Pos_Ende];
+ PosTable[iPosCount++] = PosTable[Pos];
+ }
+ }
+ }
+ Pos_Ende++;
+ }
+
+ return iPosCount/2;
+}
+
+
+
+
+// ---------------------------------------------------------
+short BoardWidget::findAllMatchingTiles( POSITION& posA )
+{
+ short Pos = 0;
+
+ for( short E=0; E<BoardLayout::depth; E++ )
+ {
+ for( short Y=0; Y<BoardLayout::height-1; Y++ )
+ {
+ for( short X=0; X<BoardLayout::width-1; X++ )
+ {
+ if( Game.Mask[E][Y][X] != (UCHAR) '1' )
+ continue;
+ if( ! Game.Board[E][Y][X] )
+ continue;
+ if( E < 4 )
+ {
+ if( Game.Board[E+1][Y][X] || Game.Board[E+1][Y+1][X] ||
+ Game.Board[E+1][Y][X+1] || Game.Board[E+1][Y+1][X+1] )
+ continue;
+ }
+ if( (Game.Board[E][Y][X-1] || Game.Board[E][Y+1][X-1]) &&
+ (Game.Board[E][Y][X+2] || Game.Board[E][Y+1][X+2]) )
+ continue;
+
+ PosTable[Pos].e = E;
+ PosTable[Pos].y = Y;
+ PosTable[Pos].x = X;
+ PosTable[Pos].f = Game.Board[E][Y][X];
+
+ if( isMatchingTile(posA, PosTable[Pos]) )
+ Pos++;
+ }
+ }
+ }
+ return Pos;
+}
+
+
+
+// ---------------------------------------------------------
+// This function replaces the old method of hilighting by
+// modifying color 21 to color 20. This was single tileset
+// specific. We now have two tile faces, one selected one not.
+
+void BoardWidget::hilightTile( POSITION& Pos, bool on, bool doRepaint )
+{
+
+ if (on) {
+ Game.hilighted[Pos.e][Pos.y][Pos.x]=1;
+ } else {
+ Game.hilighted[Pos.e][Pos.y][Pos.x]=0;
+ }
+ if (doRepaint) {
+ updateBackBuffer=true;
+ if (testWFlags(WNoAutoErase))
+ update();
+ else
+ {
+ setWFlags(getWFlags() | WNoAutoErase );
+ update();
+ setWFlags(getWFlags() & (~WNoAutoErase) );
+ }
+ }
+}
+
+
+
+// ---------------------------------------------------------
+void BoardWidget::drawBoard(bool )
+{
+ updateBackBuffer=true;
+ if (testWFlags(WNoAutoErase))
+ update();
+ else
+ {
+ setWFlags(getWFlags() | WNoAutoErase );
+ update();
+ setWFlags(getWFlags() & (~WNoAutoErase) );
+ }
+ drawTileNumber();
+}
+
+// ---------------------------------------------------------
+void BoardWidget::putTile( POSITION& Pos, bool doRepaint )
+{
+ short E=Pos.e;
+ short Y=Pos.y;
+ short X=Pos.x;
+
+ // we ensure that any tile we put on has highlighting off
+ Game.putTile( E, Y, X, Pos.f );
+ Game.hilighted[E][Y][X] = 0;
+ if (doRepaint) {
+ updateBackBuffer=true;
+ if (testWFlags(WNoAutoErase))
+ update();
+ else
+ {
+ setWFlags(getWFlags() | WNoAutoErase );
+ update();
+ setWFlags(getWFlags() & (~WNoAutoErase) );
+ }
+ }
+}
+
+
+// ---------------------------------------------------------
+void BoardWidget::removeTile( POSITION& Pos , bool doRepaint)
+{
+
+ short E = Pos.e;
+ short Y = Pos.y;
+ short X = Pos.x;
+
+ Game.TileNum--; // Eine Figur weniger
+ Game.MoveList[Game.TileNum] = Pos; // Position ins Protokoll eintragen
+
+
+
+ // remove tile from game board
+ Game.putTile( E, Y, X, 0 );
+ if (doRepaint) {
+ updateBackBuffer=true;
+ if (testWFlags(WNoAutoErase))
+ update();
+ else
+ {
+ setWFlags(getWFlags() | WNoAutoErase );
+ update();
+ setWFlags(getWFlags() & (~WNoAutoErase) );
+ }
+ }
+}
+
+// ---------------------------------------------------------
+void BoardWidget::mousePressEvent ( QMouseEvent* event )
+{
+ if (gamePaused)
+ return;
+
+ if( event->button() == LeftButton )
+ {
+ if( TimerState == Demo )
+ {
+ stopDemoMode();
+ }
+ else if( showMatch )
+ {
+ stopMatchAnimation();
+ }
+
+ if( showHelp ) // stop hilighting tiles
+ helpMoveStop();
+
+ if( MouseClickPos1.e == BoardLayout::depth ) // first tile
+ {
+ transformPointToPosition( event->pos(), MouseClickPos1 );
+
+ if( MouseClickPos1.e != BoardLayout::depth && showMatch )
+ {
+ matchCount = findAllMatchingTiles( MouseClickPos1 );
+ TimerState = Match;
+ iTimerStep = 1;
+ matchAnimationTimeout();
+ cheatsUsed++;
+ }
+ }
+ else // second tile
+ {
+ transformPointToPosition( event->pos(), MouseClickPos2 );
+ if( MouseClickPos2.e == BoardLayout::depth )
+ {
+ cancelUserSelectedTiles();
+ }
+ else
+ {
+ if( isMatchingTile( MouseClickPos1, MouseClickPos2 ) )
+ {
+ // update the removed tiles (we do this before the remove below
+ // so that we only require 1 screen paint for both actions)
+ setRemovedTilePair(MouseClickPos1, MouseClickPos2);
+
+ // now we remove the tiles from the board
+ removeTile(MouseClickPos1, false);
+ removeTile(MouseClickPos2);
+
+ // removing a tile means redo is impossible without
+ // a further undo.
+ Game.allow_redo=false;
+ demoModeChanged(false);
+ drawTileNumber();
+
+ // if no tiles are left, the player has `won`, so celebrate
+ if( Game.TileNum == 0 )
+ {
+ gameOver(Game.MaxTileNum,cheatsUsed);
+ }
+ // else if no more moves are possible, display the sour grapes dialog
+ else if( ! findMove( TimerPos1, TimerPos2 ) )
+ {
+ KMessageBox::information(this, i18n("Game over: You have no moves left."));
+ }
+ }
+ else
+ {
+ // redraw tiles in normal state
+ hilightTile( MouseClickPos1, false, false );
+ hilightTile( MouseClickPos2, false );
+ }
+ MouseClickPos1.e = BoardLayout::depth; // mark tile position as invalid
+ MouseClickPos2.e = BoardLayout::depth;
+ }
+ }
+ }
+}
+
+
+// ----------------------------------------------------------
+/**
+ Transform window point to board position.
+
+ @param point Input: Point in window coordinates
+ @param MouseClickPos Output: Position in game board
+*/
+void BoardWidget::transformPointToPosition(
+ const QPoint& point,
+ POSITION& MouseClickPos
+ )
+{
+ short E,X,Y;
+
+ // iterate over E coordinate from top to bottom
+ for( E=BoardLayout::depth-1; E>=0; E-- )
+ {
+ // calculate mouse coordiantes --> position in game board
+ // the factor -theTiles.width()/2 must keep track with the
+ // offset for blitting in the print Event (FIX ME)
+ X = ((point.x()-theTiles.width()/2)- (E+1)*theTiles.shadowSize()) / theTiles.qWidth();
+ Y = ((point.y()-theTiles.height()/2) + E*theTiles.shadowSize()) / theTiles.qHeight();
+
+
+ // changed to allow x == 0
+ // skip when position is illegal
+ if (X<0 || X>=BoardLayout::width || Y<0 || Y>=BoardLayout::height)
+ continue;
+
+ //
+ switch( Game.Mask[E][Y][X] )
+ {
+ case (UCHAR)'3': X--;Y--;
+ break;
+
+ case (UCHAR)'2': X--;
+ break;
+
+ case (UCHAR)'4': Y--;
+ break;
+
+ case (UCHAR)'1': break;
+
+ default : continue;
+ }
+ // if gameboard is empty, skip
+ if ( ! Game.Board[E][Y][X] ) continue;
+ // tile must be 'free' (nothing left, right or above it)
+ if( E < 4 )
+ {
+ if( Game.Board[E+1][Y][X] || Game.Board[E+1][Y+1][X] ||
+ (X<BoardLayout::width-2 && Game.Board[E+1][Y][X+1]) ||
+ (X<BoardLayout::width-2 && Game.Board[E+1][Y+1][X+1]) )
+ continue;
+ }
+
+ // No left test on left edge
+ if (( X > 0) && (Game.Board[E][Y][X-1] || Game.Board[E][Y+1][X-1])) {
+ if ((X<BoardLayout::width-2) && (Game.Board[E][Y][X+2] || Game.Board[E][Y+1][X+2])) {
+
+
+ continue;
+ }
+ }
+
+ // here, position is legal
+ MouseClickPos.e = E;
+ MouseClickPos.y = Y;
+ MouseClickPos.x = X;
+ MouseClickPos.f = Game.Board[E][Y][X];
+ // give visible feedback
+ hilightTile( MouseClickPos );
+ break;
+ }
+}
+
+// ---------------------------------------------------------
+bool BoardWidget::loadBoard( )
+{
+ GAMEDATA newGame;
+
+ memset( &newGame, 0, sizeof( newGame ) );
+ theBoardLayout.copyBoardLayout((UCHAR *) newGame.Mask, newGame.MaxTileNum);
+ Game = newGame;
+ return(true);
+}
+
+// ---------------------------------------------------------
+void BoardWidget::setStatusText( const QString & pszText )
+{
+ emit statusTextChanged( pszText, gameGenerationNum );
+}
+
+
+
+// ---------------------------------------------------------
+bool BoardWidget::loadBackground(
+ const QString& pszFileName,
+ bool bShowError
+ )
+{
+ if( ! theBackground.load( pszFileName, requiredWidth(), requiredHeight()) )
+ {
+ if( bShowError )
+ KMessageBox::sorry(this, i18n("Failed to load image:\n%1").arg(pszFileName) );
+ return( false );
+ }
+ Prefs::setBackground(pszFileName);
+ Prefs::writeConfig();
+ return true;
+}
+
+// ---------------------------------------------------------
+void BoardWidget::drawTileNumber()
+{
+ emit tileNumberChanged( Game.MaxTileNum, Game.TileNum, moveCount( ) );
+}
+
+// ---------------------------------------------------------
+void BoardWidget::cancelUserSelectedTiles()
+{
+ if( MouseClickPos1.e != BoardLayout::depth )
+ {
+ hilightTile( MouseClickPos1, false ); // redraw tile
+ MouseClickPos1.e = BoardLayout::depth; // mark tile invalid
+ }
+}
+
+// ---------------------------------------------------------
+void BoardWidget::setRemovedTilePair(POSITION &a, POSITION &b) {
+
+ if (isFlower(a.f)) {
+ removedFlower[a.f-TILE_FLOWER]++;
+ removedFlower[b.f-TILE_FLOWER]++;
+ return;
+ }
+
+ if (isSeason(a.f)) {
+ removedSeason[a.f-TILE_SEASON]++;
+ removedSeason[b.f-TILE_SEASON]++;
+ return;
+ }
+ if (isCharacter(a.f)) {
+ removedCharacter[a.f - TILE_CHARACTER]+=2;
+ return;
+ }
+
+ if (isBamboo(a.f)) {
+ removedBamboo[a.f - TILE_BAMBOO]+=2;
+ return;
+ }
+ if (isRod(a.f)) {
+ removedRod[a.f - TILE_ROD]+=2;
+ return;
+ }
+ if (isDragon(a.f)){
+ removedDragon[a.f - TILE_DRAGON]+=2;
+ return;
+ }
+ if (isWind(a.f)){
+ removedWind[a.f - TILE_WIND]+=2;
+ return;
+ }
+}
+
+// ---------------------------------------------------------
+void BoardWidget::clearRemovedTilePair(POSITION &a, POSITION &b) {
+
+ if (isFlower(a.f)) {
+ removedFlower[a.f-TILE_FLOWER]--;
+ removedFlower[b.f-TILE_FLOWER]--;
+ return;
+ }
+
+ if (isSeason(a.f)) {
+ removedSeason[a.f-TILE_SEASON]--;
+ removedSeason[b.f-TILE_SEASON]--;
+ return;
+ }
+ if (isCharacter(a.f)) {
+ removedCharacter[a.f - TILE_CHARACTER]-=2;
+ return;
+ }
+
+ if (isBamboo(a.f)) {
+ removedBamboo[a.f - TILE_BAMBOO]-=2;
+ return;
+ }
+ if (isRod(a.f)){
+ removedRod[a.f - TILE_ROD]-=2;
+ return;
+ }
+ if (isDragon(a.f)){
+ removedDragon[a.f - TILE_DRAGON]-=2;
+ return;
+ }
+ if (isWind(a.f)){
+ removedWind[a.f - TILE_WIND]-=2;
+ return;
+ }
+}
+
+
+// ---------------------------------------------------------
+void BoardWidget::initialiseRemovedTiles() {
+ for (int pos=0; pos<9; pos++) {
+ removedCharacter[pos]=0;
+ removedBamboo[pos]=0;
+ removedRod[pos]=0;
+ removedDragon[pos %3] = 0;
+ removedFlower[pos % 4] = 0;
+ removedWind[pos % 4] = 0;
+ removedSeason[pos % 4] = 0;
+
+ }
+
+}
+
+// ---------------------------------------------------------
+bool BoardWidget::loadTileset(const QString &path) {
+
+ if (theTiles.loadTileset(path)) {
+ Prefs::setTileSet(path);
+ Prefs::writeConfig();
+ return true;
+ } else {
+ return false;
+ }
+
+}
+
+bool BoardWidget::loadBoardLayout(const QString &file) {
+ if (theBoardLayout.loadBoardLayout(file)) {
+ Prefs::setLayout(file);
+ Prefs::writeConfig();
+ return true;
+ }
+ return false;
+}
+
+void BoardWidget::updateScaleMode() {
+
+
+ theBackground.scaleModeChanged();
+}
+
+
+
+// calculate the required window width (board + removed tiles)
+int BoardWidget::requiredWidth() {
+ int res = ((BoardLayout::width+12)* theTiles.qWidth());
+
+ return(res);
+}
+
+// calculate the required window height (board + removed tiles)
+int BoardWidget::requiredHeight() {
+
+ int res = ((BoardLayout::height+3)* theTiles.qHeight());
+ return(res);
+}
+
+void BoardWidget::tileSizeChanged() {
+ theTiles.setScaled(Prefs::miniTiles());
+ theBackground.sizeChanged(requiredWidth(), requiredHeight());
+
+}
+
+// shuffle the remaining tiles around, useful if a deadlock ocurrs
+// this is a big cheat so we penalise the user.
+void BoardWidget::shuffle() {
+ int count = 0;
+ // copy positions and faces of the remaining tiles into
+ // the pos table
+ for (int e=0; e<BoardLayout::depth; e++) {
+ for (int y=0; y<BoardLayout::height; y++) {
+ for (int x=0; x<BoardLayout::width; x++) {
+ if (Game.Board[e][y][x] && Game.Mask[e][y][x] == '1') {
+ PosTable[count].e = e;
+ PosTable[count].y = y;
+ PosTable[count].x = x;
+ PosTable[count].f = Game.Board[e][y][x];
+ count++;
+ }
+ }
+ }
+
+ }
+
+
+ // now lets randomise the faces, selecting 400 pairs at random and
+ // swapping the faces.
+ for (int ran=0; ran < 400; ran++) {
+ int pos1 = random.getLong(count);
+ int pos2 = random.getLong(count);
+ if (pos1 == pos2)
+ continue;
+ BYTE f = PosTable[pos1].f;
+ PosTable[pos1].f = PosTable[pos2].f;
+ PosTable[pos2].f = f;
+ }
+
+ // put the rearranged tiles back.
+ for (int p=0; p<count; p++)
+ Game.putTile(PosTable[p]);
+
+
+ // force a redraw
+
+ updateBackBuffer=true;
+ repaint(false);
+
+
+ // I consider this s very bad cheat so, I punish the user
+ // 300 points per use
+ cheatsUsed += 15;
+ drawTileNumber();
+}
+
+
+#include "boardwidget.moc"
diff --git a/kmahjongg/boardwidget.h b/kmahjongg/boardwidget.h
new file mode 100644
index 00000000..4c042c38
--- /dev/null
+++ b/kmahjongg/boardwidget.h
@@ -0,0 +1,244 @@
+#ifndef BOARDWIDGET_H
+#define BOARDWIDGET_H
+
+#include <qwidget.h>
+#include <qevent.h>
+#include <krandomsequence.h>
+
+
+#include "KmTypes.h"
+#include "Tileset.h"
+#include "Background.h"
+#include "BoardLayout.h"
+
+typedef struct gamedata {
+ int allow_undo;
+ int allow_redo;
+ UCHAR Board[BoardLayout::depth][BoardLayout::height][BoardLayout::width];
+ USHORT TileNum;
+ USHORT MaxTileNum;
+ UCHAR Mask[BoardLayout::depth][BoardLayout::height][BoardLayout::width];
+ UCHAR hilighted[BoardLayout::depth][BoardLayout::height][BoardLayout::width];
+ POSITION MoveList[BoardLayout::maxTiles];
+ void putTile( short e, short y, short x, UCHAR f )
+ {
+
+
+ Board[e][y][x] = Board[e][y+1][x] =
+ Board[e][y+1][x+1] = Board[e][y][x+1] = f;
+ }
+ void putTile( POSITION& pos )
+ {
+ putTile( pos.e, pos.y, pos.x, pos.f );
+ }
+
+
+ bool tilePresent(int z, int y, int x) {
+ return(Board[z][y][x]!=0 && Mask[z][y][x] == '1');
+ }
+
+ bool partTile(int z, int y, int x) {
+ return (Board[z][y][x] != 0);
+ }
+
+ int shadowHeight(int z, int y, int x) {
+
+
+ if ((z>=BoardLayout::depth||y>=BoardLayout::height||x>=BoardLayout::width))
+ return 0;
+
+
+ if ((y < 0) || (x < 0))
+ return 0;
+
+ int h=0;
+ for (int e=z; e<BoardLayout::depth; e++) {
+ if (Board[e][y][x] && Mask[e][y][x]) {
+
+
+ h++;
+ } else {
+ return h;
+ }
+
+ }
+ return h;
+ }
+
+
+} GAMEDATA;
+
+#define ANIMSPEED 200
+
+// tiles symbol names:
+#define TILE_OFFSET 2
+
+#define TILE_CHARACTER ( 0 + TILE_OFFSET)
+#define TILE_BAMBOO ( 9 + TILE_OFFSET)
+#define TILE_ROD (18 + TILE_OFFSET)
+#define TILE_SEASON (27 + TILE_OFFSET)
+#define TILE_WIND (31 + TILE_OFFSET)
+#define TILE_DRAGON (36 + TILE_OFFSET)
+#define TILE_FLOWER (39 + TILE_OFFSET)
+
+#define ID_GAME_TIMER 999
+
+/**
+ * @author Mathias Mueller
+ */
+class BoardWidget : public QWidget
+{
+ Q_OBJECT
+
+ public:
+ BoardWidget( QWidget* parent = 0, const char *name = 0 );
+ ~BoardWidget();
+
+ void calculateNewGame(int num = -1 );
+ int undoMove();
+ void redoMove();
+ void startDemoMode();
+ void stopDemoMode();
+
+ void pause();
+ void gameLoaded();
+
+ void animateMoveList();
+ void setShowMatch( bool );
+ void tileSizeChanged();
+ long getGameNum() {return gameGenerationNum;}
+ QString &getBoardName(){return theBoardLayout.getFilename();}
+ QString &getLayoutName() {return theBoardLayout.getFilename();}
+
+
+ public slots:
+ void loadSettings();
+ void saveSettings();
+
+ void shuffle();
+ void helpMove();
+ void helpMoveTimeout();
+ void helpMoveStop();
+ void demoMoveTimeout();
+ void matchAnimationTimeout();
+ void setDisplayedWidth();
+ bool loadTileset ( const QString & );
+ bool loadBoardLayout( const QString& );
+ bool loadBoard ( );
+ void updateScaleMode ();
+ void drawBoard(bool deferUpdate = true);
+ bool loadBackground ( const QString&, bool bShowError = true );
+ signals:
+ void statusTextChanged ( const QString&, long );
+ void tileNumberChanged ( int iMaximum, int iCurrent, int iLeft );
+ void demoModeChanged ( bool bActive );
+
+ void gameCalculated();
+
+ void gameOver(unsigned short removed, unsigned short cheats);
+ protected:
+ void getFileOrDefault(QString filename, QString type, QString &res);
+ void shadowArea(int z, int y, int x, int sx, int sy, int rx, int ry, QPixmap *src);
+ void shadowTopLeft(int depth, int sx, int sy, int rx, int ry,QPixmap *src, bool flag);
+ void shadowBotRight(int depth, int sx, int sy, int rx, int ry,QPixmap *src, bool flag);
+ void paintEvent ( QPaintEvent* );
+ void mousePressEvent ( QMouseEvent* );
+
+ void setStatusText ( const QString& );
+ void cancelUserSelectedTiles();
+ void drawTileNumber();
+
+ void hilightTile ( POSITION&, bool on=true, bool refresh=true );
+ void putTile ( POSITION& , bool refresh = true);
+ void removeTile ( POSITION& , bool refresh = true);
+ void setRemovedTilePair(POSITION &a, POSITION &b);
+ void clearRemovedTilePair(POSITION &a, POSITION &b);
+ void transformPointToPosition( const QPoint&, POSITION& );
+
+ bool isMatchingTile( POSITION&, POSITION& );
+ bool generateStartPosition2();
+ bool findMove( POSITION&, POSITION& );
+ int moveCount( );
+ short findAllMatchingTiles( POSITION& );
+ void stopMatchAnimation();
+ void stackTiles(unsigned char t, unsigned short h, unsigned short x,unsigned short y);
+ void initialiseRemovedTiles();
+
+ int requiredWidth();
+ int requiredHeight();
+
+ void calcShadow(int e, int y, int x, int &left, int &right, int &corn);
+
+
+ // new bits for game generation
+ void randomiseFaces();
+ int tilesAllocated;
+ int tilesUsed;
+ void getFaces(POSITION &a, POSITION &b);
+ UCHAR tilePair[144];
+
+ KRandomSequence random;
+
+ Tileset theTiles;
+ Background theBackground;
+
+ BoardLayout theBoardLayout;
+
+
+ int iPosCount; // count of valid positions in PosTable
+ POSITION PosTable[BoardLayout::maxTiles]; // Table of all possible positions
+ POSITION MouseClickPos1, MouseClickPos2;
+ POSITION TimerPos1, TimerPos2;
+
+ enum STATES { Stop, Demo, Help, Animation, Match } TimerState;
+ int iTimerStep;
+
+ short matchCount;
+ bool showMatch;
+ bool showHelp;
+
+ QTimer *timer;
+
+ // offscreen draw area.
+ QPixmap backBuffer; // pixmap to render to
+
+
+
+ bool updateBackBuffer; // does board need redrawing. Not if it is just a repaint
+
+ bool gamePaused;
+
+ // storage for hiscore claculation
+ unsigned short cheatsUsed;
+
+ // seed for the random number generator used for this game
+ long gameGenerationNum;
+
+ // storage to keep track of removed tiles
+ unsigned char removedCharacter[9];
+ unsigned char removedBamboo[9];
+ unsigned char removedRod[9];
+ unsigned char removedDragon[3];
+ unsigned char removedWind[9];
+ unsigned char removedFlower[4];
+ unsigned char removedSeason[4];
+
+ // new bits for new game generation, with solvability
+ int numTiles;
+ POSITION tilePositions[BoardLayout::maxTiles];
+ DEPENDENCY positionDepends[BoardLayout::maxTiles];
+ void generateTilePositions();
+ void generatePositionDepends();
+ int tileAt(int x, int y, int z);
+ bool generateSolvableGame();
+ bool onlyFreeInLine(int position);
+ int selectPosition(int lastPosition);
+ void placeTile(int position, int tile);
+ void updateDepend(int position);
+
+public:
+ GAMEDATA Game;
+};
+
+#endif // BOARDWIDGET_H
+
diff --git a/kmahjongg/hi128-app-kmahjongg.png b/kmahjongg/hi128-app-kmahjongg.png
new file mode 100644
index 00000000..56cbb779
--- /dev/null
+++ b/kmahjongg/hi128-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/hi16-app-kmahjongg.png b/kmahjongg/hi16-app-kmahjongg.png
new file mode 100644
index 00000000..6fa84fe8
--- /dev/null
+++ b/kmahjongg/hi16-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/hi22-app-kmahjongg.png b/kmahjongg/hi22-app-kmahjongg.png
new file mode 100644
index 00000000..92fb02e4
--- /dev/null
+++ b/kmahjongg/hi22-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/hi32-app-kmahjongg.png b/kmahjongg/hi32-app-kmahjongg.png
new file mode 100644
index 00000000..b7d5bce6
--- /dev/null
+++ b/kmahjongg/hi32-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/hi48-app-kmahjongg.png b/kmahjongg/hi48-app-kmahjongg.png
new file mode 100644
index 00000000..aec8a449
--- /dev/null
+++ b/kmahjongg/hi48-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/hi64-app-kmahjongg.png b/kmahjongg/hi64-app-kmahjongg.png
new file mode 100644
index 00000000..d3238aa7
--- /dev/null
+++ b/kmahjongg/hi64-app-kmahjongg.png
Binary files differ
diff --git a/kmahjongg/kmahjongg.cpp b/kmahjongg/kmahjongg.cpp
new file mode 100644
index 00000000..7c18ed50
--- /dev/null
+++ b/kmahjongg/kmahjongg.cpp
@@ -0,0 +1,562 @@
+/*
+
+ $Id$
+
+ kmahjongg, the classic mahjongg game for KDE project
+
+ Requires the Qt widget libraries, available at no cost at
+ http://www.troll.no
+
+ Copyright (C) 1997 Mathias Mueller <[email protected]>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+*/
+
+#include <limits.h>
+
+#include <kaboutdata.h>
+#include <kaction.h>
+#include <kconfigdialog.h>
+#include <kinputdialog.h>
+#include <kmenubar.h>
+#include <kmessagebox.h>
+#include <kstdgameaction.h>
+
+#include <kio/netaccess.h>
+
+#include "prefs.h"
+#include "kmahjongg.h"
+#include "settings.h"
+#include "GameTimer.h"
+#include "Editor.h"
+
+static const char *gameMagic = "kmahjongg-game-v1.0";
+
+//----------------------------------------------------------
+// Defines
+//----------------------------------------------------------
+#define ID_STATUS_TILENUMBER 1
+#define ID_STATUS_MESSAGE 2
+#define ID_STATUS_GAME 3
+
+int is_paused = 0;
+
+/**
+ Constructor.
+*/
+KMahjongg::KMahjongg( QWidget* parent, const char *name)
+ : KMainWindow(parent, name)
+{
+ boardEditor = 0;
+
+ // init board widget
+ bw = new BoardWidget( this );
+ setCentralWidget( bw );
+
+ previewLoad = new Preview(this);
+
+ setupStatusBar();
+ setupKAction();
+
+ gameTimer = new GameTimer(toolBar());
+ toolBar()->insertWidget(ID_GAME_TIMER, gameTimer->width() , gameTimer);
+ toolBar()->alignItemRight( ID_GAME_TIMER, true );
+
+ theHighScores = new HighScore(this);
+
+
+ bDemoModeActive = false;
+
+ connect( bw, SIGNAL( statusTextChanged(const QString&, long) ),
+ SLOT( showStatusText(const QString&, long) ) );
+
+ connect( bw, SIGNAL( tileNumberChanged(int,int,int) ),
+ SLOT( showTileNumber(int,int,int) ) );
+
+ connect( bw, SIGNAL( demoModeChanged(bool) ),
+ SLOT( demoModeChanged(bool) ) );
+
+ connect( bw, SIGNAL( gameOver(unsigned short , unsigned short)), this,
+ SLOT( gameOver(unsigned short , unsigned short)));
+
+
+ connect(bw, SIGNAL(gameCalculated()),
+ this, SLOT(timerReset()));
+
+ // Make connections for the preview load dialog
+ connect( previewLoad, SIGNAL( boardRedraw(bool) ),
+ bw, SLOT( drawBoard(bool) ) );
+
+ connect( previewLoad, SIGNAL( layoutChange() ),
+ this, SLOT( newGame() ) );
+
+
+ connect( previewLoad, SIGNAL( loadBackground(const QString&, bool) ),
+ bw, SLOT(loadBackground(const QString&, bool) ) );
+
+ connect( previewLoad, SIGNAL( loadTileset(const QString &) ),
+ bw, SLOT(loadTileset(const QString&) ) );
+ connect( previewLoad, SIGNAL( loadBoard(const QString&) ),
+ SLOT(loadBoardLayout(const QString&) ) );
+
+ startNewGame( );
+
+}
+
+// ---------------------------------------------------------
+KMahjongg::~KMahjongg()
+{
+ delete previewLoad;
+ delete theHighScores;
+ delete bw;
+}
+
+// ---------------------------------------------------------
+void KMahjongg::setupKAction()
+{
+ // game
+ KStdGameAction::gameNew(this, SLOT(newGame()), actionCollection());
+ KStdGameAction::load(this, SLOT(loadGame()), actionCollection());
+ KStdGameAction::save(this, SLOT(saveGame()), actionCollection());
+ KStdGameAction::quit(this, SLOT(close()), actionCollection());
+ KStdGameAction::restart(this, SLOT(restartGame()), actionCollection());
+ new KAction(i18n("New Numbered Game..."), "newnum", 0, this, SLOT(startNewNumeric()), actionCollection(), "game_new_numeric");
+ new KAction(i18n("Open Th&eme..."), 0, this, SLOT(openTheme()), actionCollection(), "game_open_theme");
+ new KAction(i18n("Open &Tileset..."), 0, this, SLOT(openTileset()), actionCollection(), "game_open_tileset");
+ new KAction(i18n("Open &Background..."), 0, this, SLOT(openBackground()), actionCollection(), "game_open_background");
+ new KAction(i18n("Open La&yout..."), 0, this, SLOT(openLayout()), actionCollection(), "game_open_layout");
+ new KAction(i18n("Sa&ve Theme..."), 0, this, SLOT(saveTheme()), actionCollection(), "game_save_theme");
+ // originally "file" ends here
+ KStdGameAction::hint(bw, SLOT(helpMove()), actionCollection());
+ new KAction(i18n("Shu&ffle"), "reload", 0, bw, SLOT(shuffle()), actionCollection(), "move_shuffle");
+ demoAction = KStdGameAction::demo(this, SLOT(demoMode()), actionCollection());
+ showMatchingTilesAction = new KToggleAction(i18n("Show &Matching Tiles"), 0, this, SLOT(showMatchingTiles()), actionCollection(), "options_show_matching_tiles");
+ showMatchingTilesAction->setCheckedState(i18n("Hide &Matching Tiles"));
+ showMatchingTilesAction->setChecked(Prefs::showMatchingTiles());
+ bw->setShowMatch( Prefs::showMatchingTiles() );
+ KStdGameAction::highscores(this, SLOT(showHighscores()), actionCollection());
+ pauseAction = KStdGameAction::pause(this, SLOT(pause()), actionCollection());
+
+ // TODO: store the background ; open on startup
+ // TODO: same about layout
+ // TODO: same about theme
+
+ // move
+ undoAction = KStdGameAction::undo(this, SLOT(undo()), actionCollection());
+ redoAction = KStdGameAction::redo(this, SLOT(redo()), actionCollection());
+
+ // edit
+ new KAction(i18n("&Board Editor"), 0, this, SLOT(slotBoardEditor()), actionCollection(), "edit_board_editor");
+
+ // settings
+ KStdAction::preferences(this, SLOT(showSettings()), actionCollection());
+
+ setupGUI();
+}
+
+// ---------------------------------------------------------
+void KMahjongg::setupStatusBar()
+{
+ // The following isn't possible with the new KStatusBar anymore.
+ // The correct fix is probably to reverse the order of adding the
+ // widgets. :/
+ // Just commenting it out for now (order is not as important
+ // as compilation), in case someone comes up with a better fix.
+ // pStatusBar->setInsertOrder( KStatusBar::RightToLeft );
+
+ tilesLeftLabel= new QLabel("Removed: 0000/0000", statusBar());
+ tilesLeftLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken );
+ statusBar()->addWidget(tilesLeftLabel, tilesLeftLabel->sizeHint().width(), ID_STATUS_GAME);
+
+
+ gameNumLabel = new QLabel("Game: 000000000000000000000", statusBar());
+ gameNumLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken );
+ statusBar()->addWidget(gameNumLabel, gameNumLabel->sizeHint().width(), ID_STATUS_TILENUMBER);
+
+
+ statusLabel= new QLabel("Kmahjongg", statusBar());
+ statusLabel->setFrameStyle( QFrame::Panel | QFrame::Sunken );
+ statusBar()->addWidget(statusLabel, statusLabel->sizeHint().width(), ID_STATUS_MESSAGE);
+
+ // pStatusBar->setAlignment( ID_STATUS_TILENUMBER, AlignCenter );
+}
+
+void KMahjongg::setDisplayedWidth()
+{
+ bw->setDisplayedWidth();
+/* setFixedSize( bw->size() +
+ QSize( 2, (!statusBar()->isHidden() ? statusBar()->height() : 0)
+ + 2 + menuBar()->height() ) );
+ toolBar()->setFixedWidth(bw->width());*/
+ toolBar()->alignItemRight( ID_GAME_TIMER, true );
+ bw->drawBoard();
+}
+
+
+// ---------------------------------------------------------
+void KMahjongg::startNewNumeric()
+{
+ bool ok;
+ int s = KInputDialog::getInteger(i18n("New Game"),i18n("Enter game number:"),0,0,INT_MAX,1,&ok,this);
+ if (ok) startNewGame(s);
+}
+
+void KMahjongg::undo()
+{
+ bw->Game.allow_redo += bw->undoMove();
+ demoModeChanged(false);
+}
+
+void KMahjongg::redo()
+{
+ if (bw->Game.allow_redo >0) {
+ bw->Game.allow_redo--;
+ bw->redoMove();
+ demoModeChanged(false);
+ }
+}
+
+/**
+ * Show Configure dialog.
+ */
+void KMahjongg::showSettings(){
+ if(KConfigDialog::showDialog("settings"))
+ return;
+
+ KConfigDialog *dialog = new KConfigDialog(this, "settings", Prefs::self(), KDialogBase::Swallow);
+ dialog->addPage(new Settings(0, "General"), i18n("General"), "package_settings");
+ connect(dialog, SIGNAL(settingsChanged()), bw, SLOT(loadSettings()));
+ connect(dialog, SIGNAL(settingsChanged()), this, SLOT(setDisplayedWidth()));
+ dialog->show();
+}
+
+void KMahjongg::demoMode()
+{
+ if( bDemoModeActive ) {
+ bw->stopDemoMode();
+ } else {
+ // we assume demo mode removes tiles so we can
+ // disbale redo here.
+ bw->Game.allow_redo=false;
+ bw->startDemoMode();
+ }
+
+}
+
+void KMahjongg::pause()
+{
+ is_paused = !is_paused;
+ demoModeChanged(false);
+ gameTimer->pause();
+ bw->pause();
+}
+
+void KMahjongg::showMatchingTiles()
+{
+ Prefs::setShowMatchingTiles(!Prefs::showMatchingTiles());
+ bw->setShowMatch( Prefs::showMatchingTiles() );
+ showMatchingTilesAction->setChecked(Prefs::showMatchingTiles());
+ Prefs::writeConfig();
+}
+
+void KMahjongg::showHighscores()
+{
+ theHighScores->exec(bw->getLayoutName());
+}
+
+void KMahjongg::openTheme()
+{
+ previewLoad->initialise(Preview::theme);
+ previewLoad->exec();
+}
+
+void KMahjongg::saveTheme()
+{
+ previewLoad->initialise(Preview::theme);
+ previewLoad->saveTheme();
+}
+
+void KMahjongg::openLayout()
+{
+ previewLoad->initialise(Preview::board);
+ previewLoad->exec();
+}
+
+void KMahjongg::openBackground()
+{
+ previewLoad->initialise(Preview::background);
+ previewLoad->exec();
+}
+
+void KMahjongg::openTileset()
+{
+ previewLoad->initialise(Preview::tileset);
+ previewLoad->exec();
+}
+
+void KMahjongg::slotBoardEditor()
+{
+ if (!boardEditor)
+ boardEditor = new Editor(this);
+ boardEditor->exec();
+}
+
+//----------------------------------------------------------
+// signalled from the prieview dialog to generate a new game
+// we don't make startNewGame a slot because it has a default
+// param.
+
+void KMahjongg::newGame()
+{
+ startNewGame();
+}
+
+
+
+// ---------------------------------------------------------
+void KMahjongg::startNewGame( int item )
+{
+ if( ! bDemoModeActive ) {
+ bw->calculateNewGame(item);
+
+ // initialise button states
+ bw->Game.allow_redo = bw->Game.allow_undo = 0;
+
+ timerReset();
+
+ // update the initial enabled/disabled state for
+ // the menu and the tool bar.
+ demoModeChanged(false);
+ }
+}
+
+// ---------------------------------------------------------
+void KMahjongg::timerReset() {
+
+ // initialise the scoring system
+ gameElapsedTime = 0;
+
+ // start the game timer
+ gameTimer->start();
+
+}
+
+
+// ---------------------------------------------------------
+
+void KMahjongg::gameOver(
+ unsigned short numRemoved,
+ unsigned short cheats)
+{
+ int time;
+ int score;
+
+ gameTimer->pause();
+ long gameNum = bw->getGameNum();
+ KMessageBox::information(this, i18n("You have won!"));
+ bw->animateMoveList();
+ int elapsed = gameTimer->toInt();
+
+ time = score = 0;
+
+ // get the time in milli secs
+ // subtract from 20 minutes to get bonus. if longer than 20 then ignore
+ time = (60*20) - gameTimer->toInt();
+ if (time <0)
+ time =0;
+ // conv back to secs (max bonus = 60*20 = 1200
+
+ // points per removed tile bonus (for deragon max = 144*10 = 1440
+ score += (numRemoved * 20);
+ // time bonus one point per second under one hour
+ score += time;
+ // points per cheat penalty (max penalty = 1440 for dragon)
+ score -= (cheats *20);
+ if (score < 0)
+ score = 0;
+
+ theHighScores->checkHighScore(score, elapsed, gameNum, bw->getBoardName());
+
+ timerReset();
+}
+
+// ---------------------------------------------------------
+void KMahjongg::showStatusText( const QString &msg, long board )
+{
+ statusLabel->setText(msg);
+ QString str = i18n("Game number: %1").arg(board);
+ gameNumLabel->setText(str);
+
+}
+
+// ---------------------------------------------------------
+void KMahjongg::showTileNumber( int iMaximum, int iCurrent, int iLeft )
+{
+ // Hmm... seems iCurrent is the number of remaining tiles, not removed ...
+ //QString szBuffer = i18n("Removed: %1/%2").arg(iCurrent).arg(iMaximum);
+ QString szBuffer = i18n("Removed: %1/%2 Combinations left: %3").arg(iMaximum-iCurrent).arg(iMaximum).arg(iLeft);
+ tilesLeftLabel->setText(szBuffer);
+
+ // Update here since undo allow is effected by demo mode
+ // removal. However we only change the enabled state of the
+ // items when not in demo mode
+ bw->Game.allow_undo = iMaximum != iCurrent;
+
+ // update undo menu item, if demomode is inactive
+ if( ! bDemoModeActive && !is_paused)
+ {
+
+// pMenuBar->setItemEnabled( ID_EDIT_UNDO, bw->Game.allow_undo);
+// toolBar->setItemEnabled( ID_EDIT_UNDO, bw->Game.allow_undo);
+ undoAction->setEnabled(bw->Game.allow_undo);
+ }
+}
+
+
+// ---------------------------------------------------------
+void KMahjongg::demoModeChanged( bool bActive)
+{
+ bDemoModeActive = bActive;
+
+ pauseAction->setChecked(is_paused);
+ demoAction->setChecked(bActive || is_paused);
+
+ if (is_paused)
+ stateChanged("paused");
+ else if (bActive)
+ stateChanged("active");
+ else {
+ stateChanged("inactive");
+ undoAction->setEnabled(bw->Game.allow_undo);
+ redoAction->setEnabled(bw->Game.allow_redo);
+ }
+}
+
+void KMahjongg::loadBoardLayout(const QString &file) {
+ bw->loadBoardLayout(file);
+}
+
+void KMahjongg::tileSizeChanged() {
+ bw->tileSizeChanged();
+ setDisplayedWidth();
+}
+
+
+void KMahjongg::loadGame() {
+ GAMEDATA in;
+ char buffer[1024];
+ QString fname;
+
+ // Get the name of the file to load
+ KURL url = KFileDialog::getOpenURL( NULL, "*.kmgame", this, i18n("Load Game" ) );
+
+ if ( url.isEmpty() )
+ return;
+
+ KIO::NetAccess::download( url, fname, this );
+
+ // open the file for reading
+ FILE *outFile = fopen( QFile::encodeName(fname), "r");
+ if (outFile == NULL) {
+ KMessageBox::sorry(this,
+ i18n("Could not read from file. Aborting."));
+ return;
+ }
+
+ // verify the magic
+ fscanf(outFile, "%1023s\n", buffer);
+ if (strcmp(buffer, gameMagic) != 0) {
+ KMessageBox::sorry(this,
+ i18n("File format not recognized."));
+ fclose(outFile);
+ return;
+ }
+
+ //ed the elapsed time
+ fscanf(outFile, "%1023s\n", buffer);
+ gameTimer->fromString(buffer);
+
+ // suck out all the game data
+ fread(&in, sizeof(GAMEDATA), 1, outFile);
+ memcpy(&bw->Game, &in, sizeof(GAMEDATA));
+
+ // close the file before exit
+ fclose(outFile);
+
+ KIO::NetAccess::removeTempFile( fname );
+
+ // refresh the board
+ bw->gameLoaded();
+}
+
+void KMahjongg::restartGame() {
+ if( ! bDemoModeActive ) {
+ bw->calculateNewGame(bw->getGameNum());
+
+ // initialise button states
+ bw->Game.allow_redo = bw->Game.allow_undo = 0;
+
+ timerReset();
+
+ // update the initial enabled/disabled state for
+ // the menu and the tool bar.
+ demoModeChanged(false);
+ if (is_paused)
+ {
+ pauseAction->setChecked(false);
+ is_paused = false;
+ bw->pause();
+ }
+ }
+}
+
+void KMahjongg::saveGame() {
+
+ // Get the name of the file to save
+ KURL url = KFileDialog::getSaveURL( NULL, "*.kmgame", this, i18n("Save Game" ) );
+
+ if ( url.isEmpty() )
+ return;
+
+ if( !url.isLocalFile() )
+ {
+ KMessageBox::sorry( this, i18n( "Only saving to local files currently supported." ) );
+ return;
+ }
+
+ FILE *outFile = fopen( QFile::encodeName(url.path()), "w");
+ if (outFile == NULL) {
+ KMessageBox::sorry(this,
+ i18n("Could not write to file. Aborting."));
+ return;
+ }
+
+ // stick in the magic id string
+ fprintf(outFile, "%s\n", gameMagic);
+
+ // Now stick in the elapsed time for the game
+ fprintf(outFile, "%s\n", gameTimer->toString().utf8().data());
+
+
+ // chuck in all the game data
+ fwrite(&bw->Game, sizeof(GAMEDATA), 1, outFile);
+
+ // close the file before exit
+ fclose(outFile);
+}
+
+
+#include "kmahjongg.moc"
diff --git a/kmahjongg/kmahjongg.desktop b/kmahjongg/kmahjongg.desktop
new file mode 100644
index 00000000..4ffb95bc
--- /dev/null
+++ b/kmahjongg/kmahjongg.desktop
@@ -0,0 +1,72 @@
+[Desktop Entry]
+Name=KMahjongg
+Name[af]=Kmahjong
+Name[ar]=لعبة KMahjongg
+Name[be]=Маджонг
+Name[bn]=কে-মাহজং
+Name[eo]=Mahjongo
+Name[hi]=के-महजोंग
+Name[ne]=केडीई माहजोङ
+Name[pa]=ਕੇ-ਮਹਿਜੋਂਗ
+Name[pl]=Mahjongg
+Name[sv]=Kmahjongg
+Name[tg]=KМаҷонг
+Name[th]=มาจง - K
+Name[zh_TW]=KMahjongg 麻將
+Exec=kmahjongg %i %m -caption "%c"
+Type=Application
+GenericName=Mahjongg-like Tile Game
+GenericName[be]=Гульня ў маджонг
+GenericName[bg]=Игра с плочки
+GenericName[bn]=মাহজং-জাতীয় টালির খেলা
+GenericName[br]=Ur c'hoari teol a seurt gant Mahjongg
+GenericName[bs]=Igra nalik na Mahjongg
+GenericName[ca]=Joc de mosaics a l'estil Mahjongg
+GenericName[cs]=Hra s dlaždicemi podobná Mahjongg
+GenericName[cy]=Gêm Deiliau sy'n debyg i Mahjongg
+GenericName[da]=Mahjongg-lignende flisespil
+GenericName[de]=Mahjongg-ähnliches Spiel mit Steinen
+GenericName[el]=Παιχνίδι παρόμοιο με το Mahjongg
+GenericName[eo]=Mahjongg-simila ludo
+GenericName[es]=Juego de fichas similar al Mahjongg
+GenericName[et]=Mahjonggi moodi klotsimäng
+GenericName[eu]=Mahjongg-en antzeko fitxa-jokoa
+GenericName[fa]=بازی کاشی شبیه Mahjongg
+GenericName[fi]=Mahjonggin kaltainen peli
+GenericName[fr]=Jeu de tuiles dans le style du Mahjongg
+GenericName[ga]=Cluiche Tíleanna Mar Mahjongg
+GenericName[he]=חיקוי Mahjongg, משחק אבנים (קלפים)
+GenericName[hr]=Igra s pločicama poput Mahjongga
+GenericName[hu]=Mahjongg
+GenericName[is]=Leikur sem líkist Mahjongg
+GenericName[it]=Gioco di tessere simile a Mahjongg
+GenericName[ja]=上海マージャン牌ゲーム
+GenericName[km]=ល្បែង​ក្បឿងដូច Mahjongg
+GenericName[ko]=시센-쇼 마작과 같은 타일 게임
+GenericName[lt]=Mahjongg primenantis žaidimas
+GenericName[lv]=Mahjongg līdzīga spēle
+GenericName[mk]=Игра со плочки слична на Mahjongg
+GenericName[nb]=Mahjongg-aktig brikkespill
+GenericName[nds]=Mahjongg-liek Speel
+GenericName[ne]=माहजोङ जस्तै टायल खेल
+GenericName[nl]=Mahjongg-achtig stenenspel
+GenericName[nn]=Mahjongg-aktig brikkespel
+GenericName[pl]=Gra typu Mahjonng
+GenericName[pt]=Jogo de Padrões tipo Mahjongg
+GenericName[pt_BR]=Jogo de Ladrilhos parecido com Mahjongg
+GenericName[ru]=Маджонг
+GenericName[se]=Mahjongg-lágan bihttáspeallu
+GenericName[sk]=Hra typu Mahjongg
+GenericName[sl]=Igra s ploščicami, podobna Mahjonggu
+GenericName[sr]=Игра са пољима налик на Mahjongg
+GenericName[sr@Latn]=Igra sa poljima nalik na Mahjongg
+GenericName[sv]=Mahjongg-liknande brickspel
+GenericName[ta]=மாஹ்ஜோங்-போன்ற ஓடு விளையாட்டு
+GenericName[uk]=Гра з плитками подібна до Mahjongg
+GenericName[zh_CN]=对对碰
+GenericName[zh_TW]=麻將牌遊戲
+Terminal=false
+Icon=kmahjongg
+X-KDE-StartupNotify=true
+X-DCOP-ServiceType=Multi
+Categories=Qt;KDE;Game;BoardGame;
diff --git a/kmahjongg/kmahjongg.h b/kmahjongg/kmahjongg.h
new file mode 100644
index 00000000..a4b3ac3f
--- /dev/null
+++ b/kmahjongg/kmahjongg.h
@@ -0,0 +1,119 @@
+/*
+
+ $Id$
+
+ kmahjongg, the classic mahjongg game for KDE project
+
+ Requires the Qt widget libraries, available at no cost at
+ http://www.troll.no
+
+ Copyright (C) 1997 Mathias Mueller <[email protected]>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+*/
+
+#ifndef _KMAHJONGG_H
+#define _KMAHJONGG_H
+
+#include <kmainwindow.h>
+
+#include "KmTypes.h"
+#include "Tileset.h"
+#include "Background.h"
+#include "BoardLayout.h"
+#include "Preview.h"
+#include "HighScore.h"
+#include "boardwidget.h"
+
+class GameTimer;
+class Editor;
+
+class KToggleAction;
+class QLabel;
+
+/**
+ ...
+ @author Mathias
+*/
+class KMahjongg : public KMainWindow
+{
+ Q_OBJECT
+
+ public:
+ KMahjongg( QWidget* parent = 0, const char *name = 0);
+ ~KMahjongg();
+
+ public slots:
+ void startNewGame( int num = -1 );
+ void showStatusText ( const QString& , long);
+ void showTileNumber( int iMaximum, int iCurrent, int iLeft );
+ void demoModeChanged( bool bActive );
+ void gameOver( unsigned short removed, unsigned short cheats);
+ void loadBoardLayout(const QString&);
+ void setDisplayedWidth();
+ void newGame();
+ void timerReset();
+
+ void tileSizeChanged();
+
+
+private slots:
+ void showSettings();
+
+ void startNewNumeric();
+ void saveGame();
+ void loadGame();
+ void restartGame();
+ void undo();
+ void redo();
+ void pause();
+ void demoMode();
+ void showMatchingTiles();
+ void showHighscores();
+ void slotBoardEditor();
+ void openTheme();
+ void saveTheme();
+ void openLayout();
+ void openBackground();
+ void openTileset();
+
+protected:
+ void setupKAction();
+ void setupStatusBar();
+
+private:
+ // number of seconds since the start of the game
+ unsigned long gameElapsedTime;
+ BoardWidget* bw;
+
+ QLabel *gameNumLabel;
+ QLabel *tilesLeftLabel;
+ QLabel *statusLabel;
+
+ GameTimer *gameTimer;
+ HighScore *theHighScores;
+ Preview *previewLoad;
+ Editor* boardEditor;
+
+ bool bDemoModeActive;
+
+ KToggleAction *showMatchingTilesAction, *pauseAction, *demoAction;
+ KAction *undoAction, *redoAction;
+
+};
+
+#endif
+
diff --git a/kmahjongg/kmahjongg.kcfg b/kmahjongg/kmahjongg.kcfg
new file mode 100644
index 00000000..427b4474
--- /dev/null
+++ b/kmahjongg/kmahjongg.kcfg
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0
+ http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
+ <kcfgfile name="kmahjonggrc"/>
+ <group name="General">
+ <entry name="TileSet" type="String" key="Tileset_file">
+ <label>The tile-set to use.</label>
+ </entry>
+ <entry name="Background" type="String" key="Background_file">
+ <label>The background to use.</label>
+ </entry>
+ <entry name="Layout" type="String" key="Layout_file">
+ <label>The layout of the tiles.</label>
+ </entry>
+ <entry name="ShowRemoved" type="Bool" key="showRemoved">
+ <label>Whether to show removed tiles.</label>
+ <default>false</default>
+ </entry>
+ <entry name="MiniTiles" type="Bool" key="Use_mini_tiles">
+ <label>Whether to use miniature tiles.</label>
+ <default>false</default>
+ </entry>
+ <entry name="ShowShadows" type="Bool" key="Shadows_on">
+ <label>Whether the tiles have shadows.</label>
+ <default>false</default>
+ </entry>
+ <entry name="SolvableGames" type="Bool" key="Solvable_game">
+ <label>Whether all games should be solvable.</label>
+ <default>true</default>
+ </entry>
+ <entry name="TiledBackground" type="Bool" key="Background_tiled">
+ <label>Whether the background should be tiled instead of scaled.</label>
+ <default>true</default>
+ </entry>
+ <entry name="PlayAnimation" type="Bool" key="Play_animation">
+ <label>Whether an animation should be played on victory.</label>
+ <default>true</default>
+ </entry>
+ <entry name="ShowMatchingTiles" type="Bool">
+ <label>Whether matching tiles are shown.</label>
+ <default>false</default>
+ </entry>
+ </group>
+</kcfg>
diff --git a/kmahjongg/kmahjonggui.rc b/kmahjongg/kmahjonggui.rc
new file mode 100644
index 00000000..e8c4bd5b
--- /dev/null
+++ b/kmahjongg/kmahjonggui.rc
@@ -0,0 +1,102 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kmahjongg" version="2">
+
+<MenuBar>
+ <Menu name="game"><text>&amp;Game</text>
+ <Action name="game_new_numeric" append="new_merge" />
+ <Action name="game_open_theme" append="new_merge" />
+ <Action name="game_open_tileset" append="new_merge" />
+ <Action name="game_open_background" append="new_merge" />
+ <Action name="game_open_layout" append="new_merge" />
+ <Action name="game_save_theme" append="save_merge" />
+ </Menu>
+ <Menu name="edit"><text>&amp;Edit</text>
+ <Action name="edit_board_editor" />
+ </Menu>
+ <Menu name="move"><text>&amp;Move</text>
+ <Action name="move_shuffle"/>
+ </Menu>
+ <Menu name="settings"><text>&amp;Settings</text>
+ <Action name="options_show_matching_tiles" append="show_merge"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="mainToolBar" noMerge="1"><text>Main Toolbar</text>
+ <Action name="game_new" />
+ <Action name="game_new_numeric" />
+ <Action name="game_load" />
+ <Action name="game_save" />
+ <Action name="move_undo" />
+ <Action name="move_redo" />
+ <Action name="game_pause" />
+ <Action name="move_hint" />
+</ToolBar>
+
+<State name="inactive">
+ <enable>
+ <Action name="game_new"/>
+ <Action name="game_new_numeric"/>
+ <Action name="game_load"/>
+ <Action name="game_save"/>
+ <Action name="game_highscores"/>
+ <Action name="game_open_theme"/>
+ <Action name="game_open_tileset"/>
+ <Action name="game_open_background"/>
+ <Action name="game_open_layout"/>
+ <Action name="edit_board_editor"/>
+ <Action name="move_hint"/>
+ <Action name="game_show_matching_tiles"/>
+ <Action name="game_pause"/>
+ <Action name="move_demo"/>
+ </enable>
+ <disable>
+ </disable>
+</State>
+
+<State name="active">
+ <enable>
+ <Action name="move_demo"/>
+ </enable>
+ <disable>
+ <Action name="game_new"/>
+ <Action name="game_new_numeric"/>
+ <Action name="game_load"/>
+ <Action name="game_save"/>
+ <Action name="game_highscores"/>
+ <Action name="game_open_theme"/>
+ <Action name="game_open_tileset"/>
+ <Action name="game_open_background"/>
+ <Action name="game_open_layout"/>
+ <Action name="edit_board_editor"/>
+ <Action name="move_hint"/>
+ <Action name="game_show_matching_tiles"/>
+ <Action name="game_pause"/>
+ <Action name="move_undo"/>
+ <Action name="move_redo"/>
+ </disable>
+</State>
+
+<State name="paused">
+ <enable>
+ <Action name="game_pause"/>
+ </enable>
+ <disable>
+ <Action name="game_new"/>
+ <Action name="game_new_numeric"/>
+ <Action name="game_load"/>
+ <Action name="game_save"/>
+ <Action name="game_highscores"/>
+ <Action name="game_open_theme"/>
+ <Action name="game_open_tileset"/>
+ <Action name="game_open_background"/>
+ <Action name="game_open_layout"/>
+ <Action name="edit_board_editor"/>
+ <Action name="move_hint"/>
+ <Action name="game_show_matching_tiles"/>
+ <Action name="move_undo"/>
+ <Action name="move_redo"/>
+ <Action name="move_demo"/>
+ </disable>
+</State>
+
+</kpartgui>
diff --git a/kmahjongg/main.cpp b/kmahjongg/main.cpp
new file mode 100644
index 00000000..6564a842
--- /dev/null
+++ b/kmahjongg/main.cpp
@@ -0,0 +1,38 @@
+#include "kmahjongg.h"
+#include "version.h"
+
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <kimageio.h>
+
+static const char description[] = I18N_NOOP("Mahjongg for KDE");
+
+int main( int argc, char** argv )
+{
+ KAboutData aboutData( "kmahjongg", I18N_NOOP("KMahjongg"),
+ KMAHJONGG_VERSION, description, KAboutData::License_GPL,
+ "(c) 1997, Mathias Mueller");
+ aboutData.addAuthor("Mathias Mueller", I18N_NOOP("Original Author"), "[email protected]");
+ aboutData.addAuthor("Albert Astals Cid", I18N_NOOP("Current maintainer"), "[email protected]");
+ aboutData.addAuthor("David Black", I18N_NOOP("Rewrite and Extension"), "[email protected]");
+ aboutData.addAuthor("Michael Haertjens", I18N_NOOP("Solvable game generation\nbased on algorithm by Michael Meeks in GNOME mahjongg"), "[email protected]");
+ aboutData.addAuthor("Osvaldo Stark", I18N_NOOP("Tile set contributor and web page maintainer"), "[email protected]");
+ aboutData.addCredit("Benjamin Meyer", I18N_NOOP("Code cleanup"), "[email protected]");
+
+ KCmdLineArgs::init( argc, argv, &aboutData );
+
+ KApplication a;
+ KGlobal::locale()->insertCatalogue("libkdegames");
+ KImageIO::registerFormats();
+
+ if (a.isRestored())
+ RESTORE(KMahjongg)
+ else {
+ KMahjongg *app = new KMahjongg;
+ a.setMainWidget(app);
+ app->show();
+ }
+ return a.exec();
+}
+
diff --git a/kmahjongg/pics/Makefile.am b/kmahjongg/pics/Makefile.am
new file mode 100644
index 00000000..1b879ee0
--- /dev/null
+++ b/kmahjongg/pics/Makefile.am
@@ -0,0 +1,19 @@
+theme_DATA = default.theme pirates.theme
+themedir = $(kde_datadir)/kmahjongg/pics
+
+pics_DATA = kmahjongg.png kmahjongg_bgnd.png splash.png newnum.xpm
+picsdir = $(kde_datadir)/kmahjongg/pics
+
+layout_DATA = default.layout cross.layout pirates.layout \
+ pyramid.layout stax.layout test.layout test2.layout \
+ tower.layout triangle.layout
+layoutdir = $(kde_datadir)/kmahjongg/pics
+
+bgnd_DATA = default.bgnd haze.bgnd pirates.bgnd slate.bgnd \
+ wood.bgnd
+bgnddir = $(kde_datadir)/kmahjongg/pics
+
+tileset_DATA = default.tileset pirates.tileset runes.tileset traditional.tileset
+tilesetdir = $(kde_datadir)/kmahjongg/pics
+
+EXTRA_DIST = $(pics_DATA) $(layout_DATA) $(bgnd_DATA) $(tileset_DATA) $(theme_DATA)
diff --git a/kmahjongg/pics/cross.layout b/kmahjongg/pics/cross.layout
new file mode 100644
index 00000000..39c58f1e
--- /dev/null
+++ b/kmahjongg/pics/cross.layout
@@ -0,0 +1,81 @@
+kmahjongg-layout-v1.0
+12....12121212121212121212....12
+4312..43434343434343434343..1243
+124312........1212........124312
+43124312......4343......12431243
+..43124312....1212....12431243..
+....43124312..4343..12431243....
+......43124312121212431243......
+........4312434343431243........
+........1243121212124312........
+......12431243434343124312......
+....12431243..1212..43124312....
+..12431243....4343....43124312..
+12431243......1212......43124312
+431243........4343........431243
+1243..12121212121212121212..4312
+43....43434343434343434343....43
+12......1212121212121212......12
+4312....4343434343434343....1243
+124312........1212........124312
+43124312......4343......12431243
+..43124312....1212....12431243..
+....431243....4343....431243....
+......4312....1212....1243......
+........43....4343....43........
+........12....1212....12........
+......1243....4343....4312......
+....124312....1212....124312....
+..12431243....4343....43124312..
+12431243......1212......43124312
+431243........4343........431243
+1243....1212121212121212....4312
+43......4343434343434343......43
+12........121212121212........12
+4312......434343434343......1243
+124312........1212........124312
+43124312......4343......12431243
+..43124312....1212....12431243..
+....431243....4343....431243....
+......4312....1212....1243......
+........43....4343....43........
+........12....1212....12........
+......1243....4343....4312......
+....124312....1212....124312....
+..12431243....4343....43124312..
+12431243......1212......43124312
+431243........4343........431243
+1243......121212121212......4312
+43........434343434343........43
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/default.bgnd b/kmahjongg/pics/default.bgnd
new file mode 100644
index 00000000..2f0e4612
--- /dev/null
+++ b/kmahjongg/pics/default.bgnd
Binary files differ
diff --git a/kmahjongg/pics/default.layout b/kmahjongg/pics/default.layout
new file mode 100644
index 00000000..f72ed794
--- /dev/null
+++ b/kmahjongg/pics/default.layout
@@ -0,0 +1,86 @@
+kmahjongg-layout-v1.0
+# Level 0 -------------------------
+...121212121212121212121212.....
+...434343434343434343434343.....
+.......1212121212121212.........
+.......4343434343434343.........
+.....12121212121212121212.......
+.....43434343434343434343.......
+...121212121212121212121212.....
+.124343434343434343434343431212.
+.431212121212121212121212124343.
+...434343434343434343434343.....
+.....12121212121212121212.......
+.....43434343434343434343.......
+.......1212121212121212.........
+.......4343434343434343.........
+...121212121212121212121212.....
+...434343434343434343434343.....
+# Level 1 -------------------------
+................................
+................................
+.........121212121212...........
+.........434343434343...........
+.........121212121212...........
+.........434343434343...........
+.........121212121212...........
+.........434343434343...........
+.........121212121212...........
+.........434343434343...........
+.........121212121212...........
+.........434343434343...........
+.........121212121212...........
+.........434343434343...........
+................................
+................................
+# Level 2 -------------------------
+................................
+................................
+................................
+................................
+...........12121212.............
+...........43434343.............
+...........12121212.............
+...........43434343.............
+...........12121212.............
+...........43434343.............
+...........12121212.............
+...........43434343.............
+................................
+................................
+................................
+................................
+# Level 3 -------------------------
+................................
+................................
+................................
+................................
+................................
+................................
+.............1212...............
+.............4343...............
+.............1212...............
+.............4343...............
+................................
+................................
+................................
+................................
+................................
+................................
+# Level 4 -------------------------
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+..............12................
+..............43................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/default.theme b/kmahjongg/pics/default.theme
new file mode 100644
index 00000000..9ba994bc
--- /dev/null
+++ b/kmahjongg/pics/default.theme
@@ -0,0 +1,4 @@
+kmahjongg-theme-v1.0
+:default.tileset
+:default.bgnd
+:default.layout
diff --git a/kmahjongg/pics/default.tileset b/kmahjongg/pics/default.tileset
new file mode 100644
index 00000000..b35cda6c
--- /dev/null
+++ b/kmahjongg/pics/default.tileset
Binary files differ
diff --git a/kmahjongg/pics/haze.bgnd b/kmahjongg/pics/haze.bgnd
new file mode 100644
index 00000000..f32784ff
--- /dev/null
+++ b/kmahjongg/pics/haze.bgnd
Binary files differ
diff --git a/kmahjongg/pics/kmahjongg.png b/kmahjongg/pics/kmahjongg.png
new file mode 100644
index 00000000..5e0ac37c
--- /dev/null
+++ b/kmahjongg/pics/kmahjongg.png
Binary files differ
diff --git a/kmahjongg/pics/kmahjongg_bgnd.png b/kmahjongg/pics/kmahjongg_bgnd.png
new file mode 100644
index 00000000..14d90033
--- /dev/null
+++ b/kmahjongg/pics/kmahjongg_bgnd.png
Binary files differ
diff --git a/kmahjongg/pics/newnum.xpm b/kmahjongg/pics/newnum.xpm
new file mode 100644
index 00000000..bc0b96ee
--- /dev/null
+++ b/kmahjongg/pics/newnum.xpm
@@ -0,0 +1,30 @@
+/* XPM */
+static char*numnew[]={
+"22 22 5 1",
+"c c #c0c0c0",
+"# c #000000",
+". c None",
+"b c #dcdcdc",
+"a c #ffffff",
+"......................",
+"......................",
+"......................",
+"......................",
+"......#######.........",
+"......#aaaabb#........",
+"......#aaaacab#.......",
+"......#aaaacaab#......",
+"......#aaaac####......",
+"......#aaaaaccc#......",
+"......#aaaaaaaa#......",
+"......#a#a##a#a#......",
+"......#a#aa#a#a#......",
+"......#a#a##a#a#......",
+"......#a#a##a#a#......",
+"......#a#a##a#a#......",
+"......#aaaaaaaa#......",
+"......##########......",
+"......................",
+"......................",
+"......................",
+"......................"};
diff --git a/kmahjongg/pics/pirates.bgnd b/kmahjongg/pics/pirates.bgnd
new file mode 100644
index 00000000..2e96211f
--- /dev/null
+++ b/kmahjongg/pics/pirates.bgnd
Binary files differ
diff --git a/kmahjongg/pics/pirates.layout b/kmahjongg/pics/pirates.layout
new file mode 100644
index 00000000..c5bb895c
--- /dev/null
+++ b/kmahjongg/pics/pirates.layout
@@ -0,0 +1,81 @@
+kmahjongg-layout-v1.0
+.............121212.......12..12
+.............434343.......43..43
+.............12.............12..
+.........1212431212.........43..
+.........4343124343.......12..12
+.......12121243121212.....43..43
+.......43434312434343...........
+...1212121212431212121212.......
+...4343434343124343434343....12.
+12...........43..............43.
+43...........12..........121212.
+.12..........43..........434343.
+.431212121212121212121212121212.
+...4343434343434343434343434343.
+.......1212121212121212121212...
+.......4343434343434343434343...
+.............12.................
+.............43.................
+.............12.............12..
+.........12124312...........43..
+.........43431243...............
+.........1212431212.............
+.........4343124343.............
+.......12121243121212...........
+.......43434312434343........12.
+.............43..............43.
+.............12..........121212.
+.............43..........434343.
+.....121212121212121212121212...
+.....434343434343434343434343...
+.........1212121212121212.......
+.........4343434343434343.......
+................................
+................................
+................................
+...........12..12...............
+...........43..43...............
+.........1212..1212.............
+.........4343..4343.............
+.........1212..1212.............
+.........4343..4343.............
+................................
+.............................12.
+...........................1243.
+......121212121212121212.1243...
+......434343434343434343.43.....
+.........1212121212121212.......
+.........4343434343434343.......
+................................
+................................
+................................
+...........12..12...............
+...........43..43...............
+...........12..12...............
+...........43..43...............
+.........12......12.............
+.........43......43.............
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/pirates.theme b/kmahjongg/pics/pirates.theme
new file mode 100644
index 00000000..748b3ffa
--- /dev/null
+++ b/kmahjongg/pics/pirates.theme
@@ -0,0 +1,5 @@
+kmahjongg-theme-v1.0
+:pirates.tileset
+:pirates.bgnd
+:pirates.layout
+
diff --git a/kmahjongg/pics/pirates.tileset b/kmahjongg/pics/pirates.tileset
new file mode 100644
index 00000000..77bab899
--- /dev/null
+++ b/kmahjongg/pics/pirates.tileset
Binary files differ
diff --git a/kmahjongg/pics/pyramid.layout b/kmahjongg/pics/pyramid.layout
new file mode 100644
index 00000000..edd634dc
--- /dev/null
+++ b/kmahjongg/pics/pyramid.layout
@@ -0,0 +1,86 @@
+kmahjongg-layout-v1.0
+# Level 0 -------------------------
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+# Level 1 -------------------------
+................................
+................................
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+................................
+................................
+# Level 2 -------------------------
+................................
+................................
+................................
+................................
+........1212121212121212........
+........4343434343434343........
+........1212121212121212........
+........4343434343434343........
+........1212121212121212........
+........4343434343434343........
+........1212121212121212........
+........4343434343434343........
+................................
+................................
+................................
+................................
+# Level 3 -------------------------
+................................
+................................
+................................
+................................
+................................
+................................
+..........121212121212..........
+..........434343434343..........
+..........121212121212..........
+..........434343434343..........
+................................
+................................
+................................
+................................
+................................
+................................
+# Level 4 -------------------------
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+............12121212............
+............43434343............
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/runes.tileset b/kmahjongg/pics/runes.tileset
new file mode 100644
index 00000000..30106ae2
--- /dev/null
+++ b/kmahjongg/pics/runes.tileset
Binary files differ
diff --git a/kmahjongg/pics/slate.bgnd b/kmahjongg/pics/slate.bgnd
new file mode 100644
index 00000000..07d3f73e
--- /dev/null
+++ b/kmahjongg/pics/slate.bgnd
Binary files differ
diff --git a/kmahjongg/pics/splash.png b/kmahjongg/pics/splash.png
new file mode 100644
index 00000000..dba4cf67
--- /dev/null
+++ b/kmahjongg/pics/splash.png
Binary files differ
diff --git a/kmahjongg/pics/stax.layout b/kmahjongg/pics/stax.layout
new file mode 100644
index 00000000..f5595dca
--- /dev/null
+++ b/kmahjongg/pics/stax.layout
@@ -0,0 +1,81 @@
+kmahjongg-layout-v1.0
+................................
+12121212..121212121212..12121212
+43434343..434343434343..43434343
+12............1212............12
+43............4343............43
+12121212......1212......12121212
+43434343......4343......43434343
+12............1212............12
+43............4343............43
+12121212......1212......12121212
+43434343......4343......43434343
+12............1212............12
+43............4343............43
+12121212..121212121212..12121212
+43434343..434343434343..43434343
+................................
+................................
+121212.....1212121212.....121212
+434343.....4343434343.....434343
+12.............12.............12
+43.............43.............43
+121212.........12.........121212
+434343.........43.........434343
+12.............12.............12
+43.............43.............43
+121212.........12.........121212
+434343.........43.........434343
+12.............12.............12
+43.............43.............43
+121212.....1212121212.....121212
+434343.....4343434343.....434343
+................................
+................................
+1212.........121212.........1212
+4343.........434343.........4343
+12.............12.............12
+43.............43.............43
+1212...........12...........1212
+4343...........43...........4343
+12.............12.............12
+43.............43.............43
+1212...........12...........1212
+4343...........43...........4343
+12.............12.............12
+43.............43.............43
+1212.........121212.........1212
+4343.........434343.........4343
+................................
+................................
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+12.............12.............12
+43.............43.............43
+................................
+................................
+................................
+................................
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+...............12...............
+...............43...............
+................................
+................................
+................................
diff --git a/kmahjongg/pics/test.layout b/kmahjongg/pics/test.layout
new file mode 100644
index 00000000..d46a4e21
--- /dev/null
+++ b/kmahjongg/pics/test.layout
@@ -0,0 +1,81 @@
+kmahjongg-layout-v1.0
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+.1212121212121212121212121212...
+.4343434343434343434343434343...
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/test2.layout b/kmahjongg/pics/test2.layout
new file mode 100644
index 00000000..aedc6290
--- /dev/null
+++ b/kmahjongg/pics/test2.layout
@@ -0,0 +1,81 @@
+kmahjongg-layout-v1.0
+................................
+................................
+.121212121212121212.............
+.434343434343434343.............
+.121212121212121212.............
+.434343434343434343.............
+.121212121212121212.............
+.434343434343434343.............
+.121212121212121212.............
+.434343434343434343.............
+.121212121212121212.............
+.434343434343434343.............
+.121212121212121212.............
+.434343434343434343.............
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/tower.layout b/kmahjongg/pics/tower.layout
new file mode 100644
index 00000000..76fe96a0
--- /dev/null
+++ b/kmahjongg/pics/tower.layout
@@ -0,0 +1,86 @@
+kmahjongg-layout-v1.0
+# Level 0 -------------------------
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+....121212121212121212121212....
+....434343434343434343434343....
+# Level 1 -------------------------
+................................
+................................
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+......12121212121212121212......
+......43434343434343434343......
+................................
+................................
+# Level 2 -------------------------
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
+# Level 3 -------------------------
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
+# Level 4 -------------------------
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
+................................
+................................
+......121212........121212......
+......434343........434343......
+......121212........121212......
+......434343........434343......
+................................
+................................
diff --git a/kmahjongg/pics/traditional.tileset b/kmahjongg/pics/traditional.tileset
new file mode 100644
index 00000000..e4c7a93a
--- /dev/null
+++ b/kmahjongg/pics/traditional.tileset
Binary files differ
diff --git a/kmahjongg/pics/triangle.layout b/kmahjongg/pics/triangle.layout
new file mode 100644
index 00000000..e1dd4891
--- /dev/null
+++ b/kmahjongg/pics/triangle.layout
@@ -0,0 +1,86 @@
+kmahjongg-layout-v1.0
+# Level 0 -------------------------
+.121212121212121212121212121212.
+.434343434343434343434343434343.
+...12121212121212121212121212...
+...43434343434343434343434343...
+.....1212121212121212121212.....
+.....4343434343434343434343.....
+.......121212121212121212.......
+.......434343434343434343.......
+.........12121212121212.........
+.........43434343434343.........
+...........1212121212...........
+...........4343434343...........
+.............121212.............
+.............434343.............
+...............12...............
+...............43...............
+# Level 1 -------------------------
+...12121212121212121212121212...
+...43434343434343434343434343...
+.....1212121212121212121212.....
+.....4343434343434343434343.....
+.......121212121212121212.......
+.......434343434343434343.......
+.........12121212121212.........
+.........43434343434343.........
+...........1212121212...........
+...........4343434343...........
+.............121212.............
+.............434343.............
+...............12...............
+...............43...............
+................................
+................................
+# Level 2 -------------------------
+.....1212121212121212121212.....
+.....4343434343434343434343.....
+.......121212121212121212.......
+.......434343434343434343.......
+.........12121212121212.........
+.........43434343434343.........
+...........1212121212...........
+...........4343434343...........
+.............121212.............
+.............434343.............
+...............12...............
+...............43...............
+................................
+................................
+................................
+................................
+# Level 3 -------------------------
+.......121212121212121212.......
+.......434343434343434343.......
+.........12121212121212.........
+.........43434343434343.........
+...........1212121212...........
+...........4343434343...........
+.............121212.............
+.............434343.............
+...............12...............
+...............43...............
+................................
+................................
+................................
+................................
+................................
+................................
+# Level 4 -------------------------
+.........12121212121212.........
+.........43434343434343.........
+...........1212121212...........
+...........4343434343...........
+.............121212.............
+.............434343.............
+...............12...............
+...............43...............
+................................
+................................
+................................
+................................
+................................
+................................
+................................
+................................
diff --git a/kmahjongg/pics/wood.bgnd b/kmahjongg/pics/wood.bgnd
new file mode 100644
index 00000000..1433b16c
--- /dev/null
+++ b/kmahjongg/pics/wood.bgnd
Binary files differ
diff --git a/kmahjongg/prefs.kcfgc b/kmahjongg/prefs.kcfgc
new file mode 100644
index 00000000..1498936f
--- /dev/null
+++ b/kmahjongg/prefs.kcfgc
@@ -0,0 +1,7 @@
+# Code generation options for kconfig_compiler
+File=kmahjongg.kcfg
+#IncludeFiles=defines.h
+ClassName=Prefs
+Singleton=true
+Mutators=true
+#CustomAdditions=true
diff --git a/kmahjongg/settings.ui b/kmahjongg/settings.ui
new file mode 100644
index 00000000..e53c6e37
--- /dev/null
+++ b/kmahjongg/settings.ui
@@ -0,0 +1,171 @@
+<!DOCTYPE UI><UI version="3.1" stdsetdef="1">
+<class>Settings</class>
+<widget class="QWidget">
+ <property name="name">
+ <cstring>Settings</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>274</width>
+ <height>200</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="margin">
+ <number>0</number>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <widget class="QFrame">
+ <property name="name">
+ <cstring>frame3</cstring>
+ </property>
+ <property name="frameShape">
+ <enum>StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Raised</enum>
+ </property>
+ <property name="lineWidth">
+ <number>0</number>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>groupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>General</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_ShowRemoved</cstring>
+ </property>
+ <property name="text">
+ <string>Show removed tiles</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_SolvableGames</cstring>
+ </property>
+ <property name="text">
+ <string>Generate solvable games</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_PlayAnimation</cstring>
+ </property>
+ <property name="text">
+ <string>Play winning animation</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <spacer row="2" column="0">
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Vertical</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>16</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QButtonGroup" row="1" column="1">
+ <property name="name">
+ <cstring>kcfg_TiledBackground</cstring>
+ </property>
+ <property name="title">
+ <string>Background</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>Background_scale</cstring>
+ </property>
+ <property name="text">
+ <string>Scale</string>
+ </property>
+ </widget>
+ <widget class="QRadioButton">
+ <property name="name">
+ <cstring>Background_tiled</cstring>
+ </property>
+ <property name="text">
+ <string>Tiled</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QGroupBox" row="1" column="0">
+ <property name="name">
+ <cstring>groupBox2</cstring>
+ </property>
+ <property name="title">
+ <string>Tiles</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_ShowShadows</cstring>
+ </property>
+ <property name="text">
+ <string>Draw shadows</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_MiniTiles</cstring>
+ </property>
+ <property name="text">
+ <string>Use mini-tiles</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kmahjongg/version.h b/kmahjongg/version.h
new file mode 100644
index 00000000..fb1c1583
--- /dev/null
+++ b/kmahjongg/version.h
@@ -0,0 +1 @@
+#define KMAHJONGG_VERSION "0.7.9"