diff options
Diffstat (limited to 'kmahjongg')
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 Binary files differnew file mode 100644 index 00000000..56cbb779 --- /dev/null +++ b/kmahjongg/hi128-app-kmahjongg.png diff --git a/kmahjongg/hi16-app-kmahjongg.png b/kmahjongg/hi16-app-kmahjongg.png Binary files differnew file mode 100644 index 00000000..6fa84fe8 --- /dev/null +++ b/kmahjongg/hi16-app-kmahjongg.png diff --git a/kmahjongg/hi22-app-kmahjongg.png b/kmahjongg/hi22-app-kmahjongg.png Binary files differnew file mode 100644 index 00000000..92fb02e4 --- /dev/null +++ b/kmahjongg/hi22-app-kmahjongg.png diff --git a/kmahjongg/hi32-app-kmahjongg.png b/kmahjongg/hi32-app-kmahjongg.png Binary files differnew file mode 100644 index 00000000..b7d5bce6 --- /dev/null +++ b/kmahjongg/hi32-app-kmahjongg.png diff --git a/kmahjongg/hi48-app-kmahjongg.png b/kmahjongg/hi48-app-kmahjongg.png Binary files differnew file mode 100644 index 00000000..aec8a449 --- /dev/null +++ b/kmahjongg/hi48-app-kmahjongg.png diff --git a/kmahjongg/hi64-app-kmahjongg.png b/kmahjongg/hi64-app-kmahjongg.png Binary files differnew file mode 100644 index 00000000..d3238aa7 --- /dev/null +++ b/kmahjongg/hi64-app-kmahjongg.png 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>&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>&Edit</text> + <Action name="edit_board_editor" /> + </Menu> + <Menu name="move"><text>&Move</text> + <Action name="move_shuffle"/> + </Menu> + <Menu name="settings"><text>&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 Binary files differnew file mode 100644 index 00000000..2f0e4612 --- /dev/null +++ b/kmahjongg/pics/default.bgnd 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 Binary files differnew file mode 100644 index 00000000..b35cda6c --- /dev/null +++ b/kmahjongg/pics/default.tileset diff --git a/kmahjongg/pics/haze.bgnd b/kmahjongg/pics/haze.bgnd Binary files differnew file mode 100644 index 00000000..f32784ff --- /dev/null +++ b/kmahjongg/pics/haze.bgnd diff --git a/kmahjongg/pics/kmahjongg.png b/kmahjongg/pics/kmahjongg.png Binary files differnew file mode 100644 index 00000000..5e0ac37c --- /dev/null +++ b/kmahjongg/pics/kmahjongg.png diff --git a/kmahjongg/pics/kmahjongg_bgnd.png b/kmahjongg/pics/kmahjongg_bgnd.png Binary files differnew file mode 100644 index 00000000..14d90033 --- /dev/null +++ b/kmahjongg/pics/kmahjongg_bgnd.png 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 Binary files differnew file mode 100644 index 00000000..2e96211f --- /dev/null +++ b/kmahjongg/pics/pirates.bgnd 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 Binary files differnew file mode 100644 index 00000000..77bab899 --- /dev/null +++ b/kmahjongg/pics/pirates.tileset 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 Binary files differnew file mode 100644 index 00000000..30106ae2 --- /dev/null +++ b/kmahjongg/pics/runes.tileset diff --git a/kmahjongg/pics/slate.bgnd b/kmahjongg/pics/slate.bgnd Binary files differnew file mode 100644 index 00000000..07d3f73e --- /dev/null +++ b/kmahjongg/pics/slate.bgnd diff --git a/kmahjongg/pics/splash.png b/kmahjongg/pics/splash.png Binary files differnew file mode 100644 index 00000000..dba4cf67 --- /dev/null +++ b/kmahjongg/pics/splash.png 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 Binary files differnew file mode 100644 index 00000000..e4c7a93a --- /dev/null +++ b/kmahjongg/pics/traditional.tileset 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 Binary files differnew file mode 100644 index 00000000..1433b16c --- /dev/null +++ b/kmahjongg/pics/wood.bgnd 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" |