summaryrefslogtreecommitdiffstats
path: root/kshisen
diff options
context:
space:
mode:
Diffstat (limited to 'kshisen')
-rw-r--r--kshisen/ChangeLog113
-rw-r--r--kshisen/Makefile.am28
-rw-r--r--kshisen/app.cpp760
-rw-r--r--kshisen/app.h122
-rw-r--r--kshisen/board.cpp1082
-rw-r--r--kshisen/board.h189
-rw-r--r--kshisen/debug.h1
-rw-r--r--kshisen/hi128-app-kshisen.pngbin0 -> 12289 bytes
-rw-r--r--kshisen/hi16-app-kshisen.pngbin0 -> 721 bytes
-rw-r--r--kshisen/hi22-app-kshisen.pngbin0 -> 3825 bytes
-rw-r--r--kshisen/hi32-app-kshisen.pngbin0 -> 1967 bytes
-rw-r--r--kshisen/hi48-app-kshisen.pngbin0 -> 3576 bytes
-rw-r--r--kshisen/hi64-app-kshisen.pngbin0 -> 5039 bytes
-rw-r--r--kshisen/kshisen.desktop76
-rw-r--r--kshisen/kshisen.kcfg35
-rw-r--r--kshisen/kshisenui.rc16
-rw-r--r--kshisen/main.cpp74
-rw-r--r--kshisen/pics/Makefile.am5
-rw-r--r--kshisen/pics/kshisen_bgnd.pngbin0 -> 82905 bytes
-rw-r--r--kshisen/pics/tileset.pngbin0 -> 14708 bytes
-rw-r--r--kshisen/prefs.kcfgc6
-rw-r--r--kshisen/settings.ui255
-rw-r--r--kshisen/tileset.cpp147
-rw-r--r--kshisen/tileset.h59
-rw-r--r--kshisen/version.h14
25 files changed, 2982 insertions, 0 deletions
diff --git a/kshisen/ChangeLog b/kshisen/ChangeLog
new file mode 100644
index 00000000..fdc2a653
--- /dev/null
+++ b/kshisen/ChangeLog
@@ -0,0 +1,113 @@
+2004-05-14 Benjamin Meyer (v.1.5.1)
+ * Moved settings into a KConfigXT dialog.
+
+2003-05-23 Dave Corrie <kde@davecorrie.com> (v.1.4.90)
+
+ * GUI cleanup
+ * Double-buffer painting
+ * Replaced paused pixmap with i18n-friendly text
+ * Code cleanup
+ * Added 'Prefer Unscaled Tiles' feature
+ * Removed 'Is Game Solvable?' feature (it didn't work). It
+ will be re-instated when a suitable algorithm is present
+ * Fix bug 53893: Gravity should be applied before calculating hints
+
+2002-11-27 Dave Corrie <kde@davecorrie.com> (v.1.4.1)
+
+ In conjunction with Jason Lane:
+
+ * Move tile handling (loading/scaling/highlighting etc) into
+ separate class (class TileSet)
+ * Permit resize of playing window
+ * TODO: game startup optimization, double buffering, remove
+ paused icon (not i18n friendly)
+
+2002-08-01 Dave Corrie <kde@davecorrie.com> (v.1.4)
+
+ Recent changes of note. (Does not list all the changes that
+ happened in the last three years!)
+
+ * [Andreas Beckermann] make keybindings configurable
+ * [Jason Lane] statusbar shows number of tiles remaining
+ * [Jason Lane] smoothScale tiles when shrinking
+ * [Jason Lane] remove need for separate tile mask pixmap
+ * [Jason Lane] reset "cheat mode" flag when changing board
+ size or difficulty level
+ * unicode names are now shown correctly in highscore table
+ * improved speed of layout calculation
+ * reduced flicker when highlighting/unhighlighting tiles
+ * cache current tile scale value
+ * FINALLY fix undo/redo in gravity mode
+
+1999-06-19 Mario Weilguni <mweilguni@kde.org> (v.1.3)
+
+ * tiles now have a mask, better drawing
+ * pausing games is now possible, but the pixmap should
+ be i18n'ed by some means
+ * fixed undo/redo in combination with gravity
+ * fixed a mysterious undo bug in combination with gravity
+ * prepared for 2.0 (hopefully)
+
+1999-04-09 Mario Weilguni <mweilguni@kde.org> (v.1.2.2)
+
+ * fixed another bug in writeHighscore
+
+1999-04-04 Mario Weilguni <mweilguni@kde.org> (v.1.2.1)
+
+ * replace all locale->translate with i18n
+ * fixed a bug in the writeHighscore method
+
+1999-03-31 Mario Weilguni <mweilguni@kde.org> (v.1.2)
+
+ * added gravity.
+ * TODO: gravity and the check for unsolvable games do not work
+ together
+ * fixed menu accelerator
+
+1999-01-03 Mario Weilguni <mweilguni@kde.org> (v.1.1)
+
+ * some board sizes did not match the entry shown in the
+ menu. Fixed.
+ * fixed a few warnings with egcs
+ * removed my initial highscore
+
+1998-07-17 Mario Weilguni <mweilguni@kde.org>
+
+ * moved to version 1.0 (its stable enough now)
+
+1998-04-10 Mario Weilguni <mweilguni@kde.org>
+
+ * better highscore management. highscores are now score-based, not
+ time based
+ * board.cpp: fixed some bugs
+ * the game doesnt ask anymore for a name if the score
+ is not good enough for the hall of fame
+
+0.2.1:
+ * [Robert Williams] Added getHelpMenu()
+ * [Robert Williams] Added version.h
+
+0.2: added this changelog
+
+ fixed a bug in the pathfinder (reported and fixed
+ by Stephane Alnet <alnet@u-picardie.fr>)
+
+ fixed a bug in the Board::lighten() function on 8-bit
+ displays. Reported by Marc Diefenbruch
+ <md@filesvr1.informatik.uni-essen.de>
+
+ Added a "Hall of Fame", mostly taken from kreversi.
+
+ Added "About Qt" to keep the Trolls happy
+
+ Fixed that "player-has-won-and-must-go-to-the-bathroom"-bug
+ (the elapsed time was taken after getPlayerName() instead
+ of taking it before)
+
+ Fixed that "game-starts-before-midnight-and-ends-after-midnight"
+ bug. This will allow games with a duration up to 68 years,
+ hopefully enough for the common player. The first player who
+ breaks this limit should contact me in the year 2065; I will send
+ him 20 bottles of Austrian beer ("Murauer") :-)
+
+0.1: initial release
diff --git a/kshisen/Makefile.am b/kshisen/Makefile.am
new file mode 100644
index 00000000..ae805477
--- /dev/null
+++ b/kshisen/Makefile.am
@@ -0,0 +1,28 @@
+
+
+INCLUDES = -I$(top_srcdir)/libkdegames -I$(top_srcdir)/libkdegames/highscore $(all_includes)
+
+PICDIR = $(kde_datadir)/kshisen/pics
+
+bin_PROGRAMS = kshisen
+
+METASOURCES = board.moc app.moc
+
+noinst_HEADERS = app.h board.h tileset.h debug.h version.h
+
+kshisen_SOURCES = main.cpp board.cpp app.cpp tileset.cpp settings.ui prefs.kcfgc
+kshisen_LDADD = $(LIB_KDEGAMES) $(LIB_KDEUI)
+kshisen_DEPENDENCIES = $(LIB_KDEGAMES_DEP)
+kshisen_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+xdg_apps_DATA = kshisen.desktop
+kde_kcfg_DATA = kshisen.kcfg
+KDE_ICON = kshisen
+
+SUBDIRS = pics
+
+rcdir = $(kde_datadir)/kshisen
+rc_DATA = kshisenui.rc
+
+messages: rc.cpp
+ $(XGETTEXT) rc.cpp $(kshisen_SOURCES) -o $(podir)/kshisen.pot
diff --git a/kshisen/app.cpp b/kshisen/app.cpp
new file mode 100644
index 00000000..27f8cbf2
--- /dev/null
+++ b/kshisen/app.cpp
@@ -0,0 +1,760 @@
+/* Yo Emacs, this is -*- C++ -*-
+ *******************************************************************
+ *******************************************************************
+ *
+ *
+ * KSHISEN
+ *
+ *
+ *******************************************************************
+ *
+ * A japanese game similar to mahjongg
+ *
+ *******************************************************************
+ *
+ * created 1997 by Mario Weilguni <mweilguni@sime.com>
+ *
+ *******************************************************************
+ *
+ * This file is part of the KDE project "KSHISEN"
+ *
+ * KSHISEN 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, or (at your option)
+ * any later version.
+ *
+ * KSHISEN 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 KSHISEN; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *******************************************************************
+ */
+
+#include <kapplication.h>
+#include <kseparator.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kaction.h>
+#include <kstdgameaction.h>
+#include <khighscore.h>
+#include <kdebug.h>
+#include <kkeydialog.h>
+#include <kpopupmenu.h>
+#include <kstatusbar.h>
+#include <klocale.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+#include <kconfigdialog.h>
+
+#include <qlayout.h>
+#include <qtimer.h>
+#include <qlineedit.h>
+
+#include <cmath>
+
+#include "app.h"
+#include "prefs.h"
+#include "settings.h"
+
+App::App(QWidget *parent, const char *name) : KMainWindow(parent, name),
+ cheat(false)
+{
+ highscoreTable = new KHighscore(this);
+
+ // TODO?
+ // Would it make sense long term to have a kconfig update rather then
+ // havin both formats supported in the code?
+ if(highscoreTable->hasTable())
+ readHighscore();
+ else
+ readOldHighscore();
+
+ statusBar()->insertItem("", SBI_TIME);
+ statusBar()->insertItem("", SBI_TILES);
+ statusBar()->insertFixedItem(i18n(" Cheat mode "), SBI_CHEAT);
+ statusBar()->changeItem("", SBI_CHEAT);
+
+ initKAction();
+
+ board = new Board(this, "board");
+ loadSettings();
+
+ setCentralWidget(board);
+
+ setupGUI();
+
+ connect(board, SIGNAL(changed()), this, SLOT(enableItems()));
+
+ QTimer *t = new QTimer(this);
+ t->start(1000);
+ connect(t, SIGNAL(timeout()), this, SLOT(updateScore()));
+ connect(board, SIGNAL(endOfGame()), this, SLOT(slotEndOfGame()));
+ connect(board, SIGNAL(resized()), this, SLOT(boardResized()));
+
+ kapp->processEvents();
+
+ updateScore();
+ enableItems();
+}
+
+void App::initKAction()
+{
+ // Game
+ KStdGameAction::gameNew(this, SLOT(newGame()), actionCollection());
+ KStdGameAction::restart(this, SLOT(restartGame()), actionCollection());
+ KStdGameAction::pause(this, SLOT(pause()), actionCollection());
+ KStdGameAction::highscores(this, SLOT(hallOfFame()), actionCollection());
+ KStdGameAction::quit(this, SLOT(quitGame()), actionCollection());
+
+ // Move
+ KStdGameAction::undo(this, SLOT(undo()), actionCollection());
+ KStdGameAction::redo(this, SLOT(redo()), actionCollection());
+ KStdGameAction::hint(this, SLOT(hint()), actionCollection());
+ //new KAction(i18n("Is Game Solvable?"), 0, this,
+ // SLOT(isSolvable()), actionCollection(), "move_solvable");
+
+#ifdef DEBUGGING
+ (void)new KAction(i18n("&Finish"), 0, board, SLOT(finish()), actionCollection(), "move_finish");
+#endif
+
+ // Settings
+ KStdAction::preferences(this, SLOT(showSettings()), actionCollection());
+}
+
+void App::hallOfFame()
+{
+ showHighscore();
+}
+
+void App::newGame()
+{
+ board->newGame();
+ resetCheatMode();
+ enableItems();
+}
+
+void App::quitGame()
+{
+ kapp->quit();
+}
+
+void App::restartGame()
+{
+ board->setUpdatesEnabled(false);
+ while(board->canUndo())
+ board->undo();
+ board->setUpdatesEnabled(true);
+ board->update();
+ enableItems();
+}
+
+void App::isSolvable()
+{
+ if(board->solvable())
+ KMessageBox::information(this, i18n("This game is solvable."));
+ else
+ KMessageBox::information(this, i18n("This game is NOT solvable."));
+}
+
+void App::pause()
+{
+ bool paused = board->pause();
+ lockMenus(paused);
+}
+
+void App::undo()
+{
+ if(board->canUndo())
+ {
+ board->undo();
+ setCheatMode();
+ enableItems();
+ }
+}
+
+void App::redo()
+{
+ if(board->canRedo())
+ board->redo();
+ enableItems();
+}
+
+void App::hint()
+{
+#ifdef DEBUGGING
+ board->makeHintMove();
+#else
+ board->showHint();
+ setCheatMode();
+#endif
+ enableItems();
+}
+
+void App::loadSettings()
+{
+ // Setting 'Prefer Unscaled Tiles' to on is known to fail in the following
+ // situation: The Keramik window decoration is in use AND caption bubbles
+ // stick out above the title bar (i.e. Keramik's 'Draw small caption
+ // bubbles on active windows' configuration entry is set to off) AND the
+ // kshisen window is maximized.
+ //
+ // The user can work-around this situation by un-maximizing the window first.
+ if(Prefs::unscaled())
+ {
+ QSize s = board->unscaledSize();
+
+ // We would have liked to have used KMainWindow::sizeForCentralWidgetSize(),
+ // but this function does not seem to work when the toolbar is docked on the
+ // left. sizeForCentralWidgetSize() even reports a value 1 pixel too small
+ // when the toolbar is docked at the top...
+ // These bugs present in KDE: 3.1.90 (CVS >= 20030225)
+ //resize(sizeForCentralWidgetSize(s));
+
+ s += size() - board->size(); // compensate for chrome (toolbars, statusbars etc.)
+ resize(s);
+ //kdDebug() << "App::preferUnscaled() set size to: " << s.width() << " x " << s.height() << endl;
+ }
+}
+
+void App::lockMenus(bool lock)
+{
+ // Disable all actions apart from (un)pause, quit and those that are help-related.
+ // (Only undo/redo and hint actually *need* to be disabled, but disabling everything
+ // provides a good visual hint to the user, that they need to unpause to continue.
+ KPopupMenu* help = dynamic_cast<KPopupMenu*>(child("help", "KPopupMenu", false));
+ KActionPtrList actions = actionCollection()->actions();
+ KActionPtrList::iterator actionIter = actions.begin();
+ KActionPtrList::iterator actionIterEnd = actions.end();
+
+ while(actionIter != actionIterEnd)
+ {
+ KAction* a = *actionIter;
+ if(!a->isPlugged(help))
+ a->setEnabled(!lock);
+ ++actionIter;
+ }
+
+ actionCollection()->action(KStdGameAction::name(KStdGameAction::Pause))->setEnabled(true);
+ actionCollection()->action(KStdGameAction::name(KStdGameAction::Quit))->setEnabled(true);
+
+ enableItems();
+}
+
+void App::enableItems()
+{
+ if(!board->isPaused())
+ {
+ actionCollection()->action(KStdGameAction::name(KStdGameAction::Undo))->setEnabled(board->canUndo());
+ actionCollection()->action(KStdGameAction::name(KStdGameAction::Redo))->setEnabled(board->canRedo());
+ actionCollection()->action(KStdGameAction::name(KStdGameAction::Restart))->setEnabled(board->canUndo());
+ }
+}
+
+void App::boardResized()
+{
+ // If the board has been resized to a size that requires scaled tiles, then the
+ // 'Prefer Unscaled Tiles' option should be set to off.
+
+ //kdDebug() << "App::boardResized " << b->width() << " x " << b->height() << endl;
+ bool unscaled = Prefs::unscaled();
+ if(unscaled && board->size() != board->unscaledSize())
+ Prefs::setUnscaled(false);
+}
+
+void App::slotEndOfGame()
+{
+ if(board->tilesLeft() > 0)
+ {
+ KMessageBox::information(this, i18n("No more moves possible!"), i18n("End of Game"));
+ }
+ else
+ {
+ // create highscore entry
+ HighScore hs;
+ hs.seconds = board->getTimeForGame();
+ hs.x = board->x_tiles();
+ hs.y = board->y_tiles();
+ hs.gravity = (int)board->gravityFlag();
+
+ // check if we made it into Top10
+ bool isHighscore = false;
+ if(highscore.size() < HIGHSCORE_MAX)
+ isHighscore = true;
+ else if(isBetter(hs, highscore[HIGHSCORE_MAX-1]))
+ isHighscore = true;
+
+ if(isHighscore && !cheat)
+ {
+ hs.name = getPlayerName();
+ hs.date = time((time_t*)0);
+ int rank = insertHighscore(hs);
+ showHighscore(rank);
+ }
+ else
+ {
+ QString s = i18n("Congratulations! You made it in %1:%2:%3")
+ .arg(QString().sprintf("%02d", board->getTimeForGame()/3600))
+ .arg(QString().sprintf("%02d", (board->getTimeForGame() / 60) % 60))
+ .arg(QString().sprintf("%02d", board->getTimeForGame() % 60));
+
+ KMessageBox::information(this, s, i18n("End of Game"));
+ }
+ }
+
+ resetCheatMode();
+ board->newGame();
+}
+
+void App::updateScore()
+{
+ int t = board->getTimeForGame();
+ QString s = i18n(" Your time: %1:%2:%3 %4")
+ .arg(QString().sprintf("%02d", t / 3600 ))
+ .arg(QString().sprintf("%02d", (t / 60) % 60 ))
+ .arg(QString().sprintf("%02d", t % 60 ))
+ .arg(board->isPaused()?i18n("(Paused) "):QString::null);
+
+ statusBar()->changeItem(s, SBI_TIME);
+
+ // Number of tiles
+ int tl = (board->x_tiles() * board->y_tiles());
+ s = i18n(" Removed: %1/%2 ")
+ .arg(QString().sprintf("%d", tl - board->tilesLeft()))
+ .arg(QString().sprintf("%d", tl ));
+
+ statusBar()->changeItem(s, SBI_TILES);
+}
+
+void App::setCheatMode()
+{
+ // set the cheat mode if not set
+ if(!cheat)
+ {
+ cheat = true;
+ statusBar()->changeItem(i18n(" Cheat mode "), SBI_CHEAT);
+ }
+}
+
+void App::resetCheatMode()
+{
+ // reset cheat mode if set
+ if(cheat)
+ {
+ cheat = false;
+ statusBar()->changeItem("", SBI_CHEAT);
+ }
+}
+
+QString App::getPlayerName()
+{
+ QDialog *dlg = new QDialog(this, "Hall of Fame", true);
+
+ QLabel *l1 = new QLabel(i18n("You've made it into the \"Hall Of Fame\". Type in\nyour name so mankind will always remember\nyour cool rating."), dlg);
+ l1->setFixedSize(l1->sizeHint());
+
+ QLabel *l2 = new QLabel(i18n("Your name:"), dlg);
+ l2->setFixedSize(l2->sizeHint());
+
+ QLineEdit *e = new QLineEdit(dlg);
+ e->setText("XXXXXXXXXXXXXXXX");
+ e->setMinimumWidth(e->sizeHint().width());
+ e->setFixedHeight(e->sizeHint().height());
+ e->setText( lastPlayerName );
+ e->setFocus();
+
+ QPushButton *b = new KPushButton(KStdGuiItem::ok(), dlg);
+ b->setDefault(true);
+ b->setFixedSize(b->sizeHint());
+
+ connect(b, SIGNAL(released()), dlg, SLOT(accept()));
+ connect(e, SIGNAL(returnPressed()), dlg, SLOT(accept()));
+
+ // create layout
+ QVBoxLayout *tl = new QVBoxLayout(dlg, 10);
+ QHBoxLayout *tl1 = new QHBoxLayout();
+ tl->addWidget(l1);
+ tl->addSpacing(5);
+ tl->addLayout(tl1);
+ tl1->addWidget(l2);
+ tl1->addWidget(e);
+ tl->addSpacing(5);
+ tl->addWidget(b);
+ tl->activate();
+ tl->freeze();
+
+ dlg->exec();
+
+ lastPlayerName = e->text();
+ delete dlg;
+
+ if(lastPlayerName.isEmpty())
+ return " ";
+ return lastPlayerName;
+}
+
+int App::getScore(const HighScore &hs)
+{
+ double ntiles = hs.x*hs.y;
+ double tilespersec = ntiles/(double)hs.seconds;
+
+ double sizebonus = std::sqrt(ntiles/(double)(14.0 * 6.0));
+ double points = tilespersec / 0.14 * 100.0;
+
+ if(hs.gravity)
+ return (int)(2.0 * points * sizebonus);
+ else
+ return (int)(points * sizebonus);
+}
+
+bool App::isBetter(const HighScore &hs, const HighScore &than)
+{
+ if(getScore(hs) > getScore(than))
+ return true;
+ else
+ return false;
+}
+
+int App::insertHighscore(const HighScore &hs)
+{
+ int i;
+
+ if(highscore.size() == 0)
+ {
+ highscore.resize(1);
+ highscore[0] = hs;
+ writeHighscore();
+ return 0;
+ }
+ else
+ {
+ HighScore last = highscore[highscore.size() - 1];
+ if(isBetter(hs, last) || (highscore.size() < HIGHSCORE_MAX))
+ {
+ if(highscore.size() == HIGHSCORE_MAX)
+ {
+ highscore[HIGHSCORE_MAX - 1] = hs;
+ }
+ else
+ {
+ highscore.resize(highscore.size()+1);
+ highscore[highscore.size() - 1] = hs;
+ }
+
+ // sort in new entry
+ int bestsofar = highscore.size() - 1;
+ for(i = highscore.size() - 1; i > 0; i--)
+ {
+ if(isBetter(highscore[i], highscore[i-1]))
+ {
+ // swap entries
+ HighScore temp = highscore[i-1];
+ highscore[i-1] = highscore[i];
+ highscore[i] = temp;
+ bestsofar = i - 1;
+ }
+ }
+
+ writeHighscore();
+ return bestsofar;
+ }
+ }
+ return -1;
+}
+
+void App::readHighscore()
+{
+ QStringList hi_x, hi_y, hi_sec, hi_date, hi_grav, hi_name;
+ hi_x = highscoreTable->readList("x", HIGHSCORE_MAX);
+ hi_y = highscoreTable->readList("y", HIGHSCORE_MAX);
+ hi_sec = highscoreTable->readList("seconds", HIGHSCORE_MAX);
+ hi_date = highscoreTable->readList("date", HIGHSCORE_MAX);
+ hi_grav = highscoreTable->readList("gravity", HIGHSCORE_MAX);
+ hi_name = highscoreTable->readList("name", HIGHSCORE_MAX);
+
+ highscore.resize(0);
+
+ for (unsigned int i = 0; i < hi_x.count(); i++)
+ {
+ highscore.resize(i+1);
+
+ HighScore hs;
+
+ hs.x = hi_x[i].toInt();
+ hs.y = hi_y[i].toInt();
+ hs.seconds = hi_sec[i].toInt();
+ hs.date = hi_date[i].toInt();
+ hs.date = hi_date[i].toInt();
+ hs.gravity = hi_grav[i].toInt();
+ hs.name = hi_name[i];
+
+ highscore[i] = hs;
+ }
+}
+
+void App::readOldHighscore()
+{
+ // this is for before-KHighscore-highscores
+ int i;
+ QString s, e, grp;
+ KConfig *conf = kapp->config();
+
+ highscore.resize(0);
+ i = 0;
+ bool eol = false;
+ grp = conf->group();
+ conf->setGroup("Hall of Fame");
+ while ((i < (int)HIGHSCORE_MAX) && !eol)
+ {
+ s.sprintf("Highscore_%d", i);
+ if(conf->hasKey(s))
+ {
+ e = conf->readEntry(s);
+ highscore.resize(i+1);
+
+ HighScore hs;
+
+ QStringList e = conf->readListEntry(s, ' ');
+ int nelem = e.count();
+ hs.x = (*e.at(0)).toInt();
+ hs.y = (*e.at(1)).toInt();
+ hs.seconds = (*e.at(2)).toInt();
+ hs.date = (*e.at(3)).toInt();
+
+ if(nelem == 4) // old version <= 1.1
+ {
+ hs.gravity = 0;
+ hs.name = *e.at(4);
+ }
+ else
+ {
+ hs.gravity = (*e.at(4)).toInt();
+ hs.name = *e.at(5);
+ }
+
+ highscore[i] = hs;
+ }
+ else
+ {
+ eol = true;
+ }
+ i++;
+ }
+
+// // freshly installed, add my own highscore
+// if(highscore.size() == 0)
+// {
+// HighScore hs;
+// hs.x = 28;
+// hs.y = 16;
+// hs.seconds = 367;
+// hs.name = "Mario";
+// highscore.resize(1);
+// highscore[0] = hs;
+// }
+
+ // restore old group
+ conf->setGroup(grp);
+
+ // write in new KHighscore format
+ writeHighscore();
+ // read form KHighscore format
+ readHighscore();
+}
+
+void App::writeHighscore()
+{
+ int i;
+ QStringList hi_x, hi_y, hi_sec, hi_date, hi_grav, hi_name;
+ for(i = 0; i < (int)highscore.size(); i++)
+ {
+ HighScore hs = highscore[i];
+ hi_x.append(QString::number(hs.x));
+ hi_y.append(QString::number(hs.y));
+ hi_sec.append(QString::number(hs.seconds));
+ hi_date.append(QString::number(hs.date));
+ hi_grav.append(QString::number(hs.gravity));
+ hi_name.append(hs.name);
+ }
+ highscoreTable->writeList("x", hi_x);
+ highscoreTable->writeList("y", hi_y);
+ highscoreTable->writeList("seconds", hi_sec);
+ highscoreTable->writeList("date", hi_date);
+ highscoreTable->writeList("gravity", hi_grav);
+ highscoreTable->writeList("name", hi_name);
+ highscoreTable->sync();
+}
+
+void App::showHighscore(int focusitem)
+{
+ // this may look a little bit confusing...
+ QDialog *dlg = new QDialog(0, "hall_Of_fame", true);
+ dlg->setCaption(i18n("Hall of Fame"));
+
+ QVBoxLayout *tl = new QVBoxLayout(dlg, 10);
+
+ QLabel *l = new QLabel(i18n("Hall of Fame"), dlg);
+ QFont f = font();
+ f.setPointSize(24);
+ f.setBold(true);
+ l->setFont(f);
+ l->setFixedSize(l->sizeHint());
+ l->setFixedWidth(l->width() + 32);
+ l->setAlignment(AlignCenter);
+ tl->addWidget(l);
+
+ // insert highscores in a gridlayout
+ QGridLayout *table = new QGridLayout(12, 5, 5);
+ tl->addLayout(table, 1);
+
+ // add a separator line
+ KSeparator *sep = new KSeparator(dlg);
+ table->addMultiCellWidget(sep, 1, 1, 0, 4);
+
+ // add titles
+ f = font();
+ f.setBold(true);
+ l = new QLabel(i18n("Rank"), dlg);
+ l->setFont(f);
+ l->setMinimumSize(l->sizeHint());
+ table->addWidget(l, 0, 0);
+ l = new QLabel(i18n("Name"), dlg);
+ l->setFont(f);
+ l->setMinimumSize(l->sizeHint());
+ table->addWidget(l, 0, 1);
+ l = new QLabel(i18n("Time"), dlg);
+ l->setFont(f);
+ l->setMinimumSize(l->sizeHint());
+ table->addWidget(l, 0, 2);
+ l = new QLabel(i18n("Size"), dlg);
+ l->setFont(f);
+ l->setMinimumSize(l->sizeHint());
+ table->addWidget(l, 0, 3);
+ l = new QLabel(i18n("Score"), dlg);
+ l->setFont(f);
+ l->setMinimumSize(l->sizeHint().width()*3, l->sizeHint().height());
+ table->addWidget(l, 0, 4);
+
+ QString s;
+ QLabel *e[10][5];
+ unsigned i, j;
+
+ for(i = 0; i < 10; i++)
+ {
+ HighScore hs;
+ if(i < highscore.size())
+ hs = highscore[i];
+
+ // insert rank
+ s.sprintf("%d", i+1);
+ e[i][0] = new QLabel(s, dlg);
+
+ // insert name
+ if(i < highscore.size())
+ e[i][1] = new QLabel(hs.name, dlg);
+ else
+ e[i][1] = new QLabel("", dlg);
+
+ // insert time
+ QTime ti(0,0,0);
+ if(i < highscore.size())
+ {
+ ti = ti.addSecs(hs.seconds);
+ s.sprintf("%02d:%02d:%02d", ti.hour(), ti.minute(), ti.second());
+ e[i][2] = new QLabel(s, dlg);
+ }
+ else
+ {
+ e[i][2] = new QLabel("", dlg);
+ }
+
+ // insert size
+ if(i < highscore.size())
+ s.sprintf("%d x %d", hs.x, hs.y);
+ else
+ s = "";
+
+ e[i][3] = new QLabel(s, dlg);
+
+ // insert score
+ if(i < highscore.size())
+ {
+ s = QString("%1 %2")
+ .arg(getScore(hs))
+ .arg(hs.gravity ? i18n("(gravity)") : QString(""));
+ }
+ else
+ {
+ s = "";
+ }
+
+ e[i][4] = new QLabel(s, dlg);
+ e[i][4]->setAlignment(AlignRight);
+ }
+
+ f = font();
+ f.setBold(true);
+ f.setItalic(true);
+ for(i = 0; i < 10; i++)
+ {
+ for(j = 0; j < 5; j++)
+ {
+ e[i][j]->setMinimumHeight(e[i][j]->sizeHint().height());
+
+ if(j == 1)
+ e[i][j]->setMinimumWidth(std::max(e[i][j]->sizeHint().width(), 100));
+ else
+ e[i][j]->setMinimumWidth(std::max(e[i][j]->sizeHint().width(), 60));
+
+ if((int)i == focusitem)
+ e[i][j]->setFont(f);
+
+ table->addWidget(e[i][j], i+2, j, AlignCenter);
+ }
+ }
+
+ QPushButton *b = new KPushButton(KStdGuiItem::close(), dlg);
+
+ b->setFixedSize(b->sizeHint());
+
+ // connect the "Close"-button to done
+ connect(b, SIGNAL(clicked()), dlg, SLOT(accept()));
+ b->setDefault(true);
+ b->setFocus();
+
+ // make layout
+ tl->addSpacing(10);
+ tl->addWidget(b);
+ tl->activate();
+ tl->freeze();
+
+ dlg->exec();
+ delete dlg;
+}
+
+void App::keyBindings()
+{
+ KKeyDialog::configure(actionCollection(), this);
+}
+
+/**
+ * Show Settings dialog.
+ */
+void App::showSettings(){
+ if(KConfigDialog::showDialog("settings"))
+ return;
+
+ KConfigDialog *dialog = new KConfigDialog(this, "settings", Prefs::self(), KDialogBase::Swallow);
+ Settings *general = new Settings(0, "General");
+ dialog->addPage(general, i18n("General"), "package_settings");
+ connect(dialog, SIGNAL(settingsChanged()), this, SLOT(loadSettings()));
+ connect(dialog, SIGNAL(settingsChanged()), board, SLOT(loadSettings()));
+ dialog->show();
+}
+
+#include "app.moc"
diff --git a/kshisen/app.h b/kshisen/app.h
new file mode 100644
index 00000000..47808f9e
--- /dev/null
+++ b/kshisen/app.h
@@ -0,0 +1,122 @@
+/* Yo Emacs, this is -*- C++ -*-
+ *******************************************************************
+ *******************************************************************
+ *
+ *
+ * KSHISEN
+ *
+ *
+ *******************************************************************
+ *
+ * A japanese game similar to mahjongg
+ *
+ *******************************************************************
+ *
+ * created 1997 by Mario Weilguni <mweilguni@sime.com>
+ *
+ *******************************************************************
+ *
+ * This file is part of the KDE project "KSHISEN"
+ *
+ * KSHISEN 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, or (at your option)
+ * any later version.
+ *
+ * KSHISEN 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 KSHISEN; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *******************************************************************
+ */
+
+
+#ifndef __APP__H__
+#define __APP__H__
+
+// Should this get the whole HAVE_SYS_TIME_H TIME_WITH_SYS_TIME treatment?
+#include <time.h>
+
+#include <kmainwindow.h>
+#include "board.h"
+
+class KHighscore;
+
+struct HighScore
+{
+ QString name;
+ int seconds;
+ int x, y;
+ time_t date;
+ int gravity;
+};
+
+const unsigned HIGHSCORE_MAX = 10;
+
+class App : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+ App(QWidget *parent = 0, const char *name=0);
+
+private slots:
+ void loadSettings();
+
+ void slotEndOfGame();
+ void enableItems();
+ void updateScore();
+ void showSettings();
+
+ void newGame();
+ void quitGame();
+ void restartGame();
+ void isSolvable();
+ void pause();
+ void undo();
+ void redo();
+ void hint();
+ void hallOfFame();
+ void keyBindings();
+ void boardResized();
+
+private:
+ void lockMenus(bool);
+ QString getPlayerName();
+
+ /**
+ * Read the old (pre- @ref KHighscore) highscore table.
+ *
+ * This reads the config file first, then saves it in the new format and
+ * re-reads it again as a KHighscore table.
+ **/
+ void readOldHighscore();
+ void readHighscore();
+ void writeHighscore();
+ int insertHighscore(const HighScore &);
+ int getScore(const HighScore &);
+ bool isBetter(const HighScore &, const HighScore &);
+ void showHighscore(int focusitem = -1);
+
+ void initKAction();
+ void setCheatMode();
+ void resetCheatMode();
+
+private:
+ QString lastPlayerName;
+ Board *board;
+ QValueVector<HighScore> highscore;
+ KHighscore* highscoreTable;
+ bool cheat;
+
+ enum statusBarItems { SBI_TIME, SBI_TILES, SBI_CHEAT };
+
+};
+
+#endif
diff --git a/kshisen/board.cpp b/kshisen/board.cpp
new file mode 100644
index 00000000..ab61a912
--- /dev/null
+++ b/kshisen/board.cpp
@@ -0,0 +1,1082 @@
+/* Yo Emacs, this is -*- C++ -*-
+ *******************************************************************
+ *******************************************************************
+ *
+ *
+ * KSHISEN
+ *
+ *
+ *******************************************************************
+ *
+ * A japanese game similar to mahjongg
+ *
+ *******************************************************************
+ *
+ * created 1997 by Mario Weilguni <mweilguni@sime.com>
+ *
+ *******************************************************************
+ *
+ * This file is part of the KDE project "KSHISEN"
+ *
+ * KSHISEN 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, or (at your option)
+ * any later version.
+ *
+ * KSHISEN 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 KSHISEN; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *******************************************************************
+ */
+
+#include <kapplication.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kglobalsettings.h>
+#include <kdebug.h>
+
+#include <qpainter.h>
+#include <qpaintdevicemetrics.h>
+#include <qtimer.h>
+
+#include "board.h"
+#include "prefs.h"
+
+#define EMPTY 0
+#define DEFAULTDELAY 500
+#define DEFAULTSHUFFLE 4
+
+static int size_x[5] = {14, 18, 24, 26, 30};
+static int size_y[5] = { 6, 8, 12, 14, 16};
+static int DELAY[5] = {1000, 750, 500, 250, 125};
+
+Board::Board(QWidget *parent, const char *name) :
+ QWidget(parent, name, WResizeNoErase), field(0),
+ _x_tiles(0), _y_tiles(0),
+ _delay(125), paused(false),
+ gravity_flag(true), _solvable_flag(true),
+ grav_col_1(-1), grav_col_2(-1), highlighted_tile(-1)
+{
+ // Randomize
+ setShuffle(DEFAULTSHUFFLE);
+
+ random.setSeed(0);
+ starttime = time((time_t *)0);
+
+ setDelay(DEFAULTDELAY);
+ _redo.setAutoDelete(true);
+ _undo.setAutoDelete(true);
+
+ QPixmap bg(KGlobal::dirs()->findResource("appdata", "kshisen_bgnd.png"));
+ setBackgroundPixmap(bg);
+
+ loadSettings();
+}
+
+Board::~Board()
+{
+ delete [] field;
+}
+
+void Board::loadSettings(){
+ int index = Prefs::size();
+ setSize(size_x[index], size_y[index]);
+
+ setShuffle(Prefs::level() * 4 + 1);
+ setGravityFlag(Prefs::gravity());
+ setSolvableFlag(Prefs::solvable());
+ setDelay(DELAY[Prefs::speed()]);
+}
+
+int Board::x_tiles() const
+{
+ return _x_tiles;
+}
+
+int Board::y_tiles() const
+{
+ return _y_tiles;
+}
+
+void Board::setField(int x, int y, int value)
+{
+ if(x < 0 || y < 0 || x >= x_tiles() || y >= y_tiles())
+ {
+ kdFatal() << "Attempted write to invalid field position "
+ "(" << x << ", " << y << ")" << endl;
+ }
+
+ field[y * x_tiles() + x] = value;
+}
+
+int Board::getField(int x, int y) const
+{
+#ifdef DEBUGGING
+ if(x < -1 || y < -1 || x > x_tiles() || y > y_tiles())
+ {
+ kdFatal() << "Attempted read from invalid field position "
+ "(" << x << ", " << y << ")" << endl;
+ }
+#endif
+
+ if(x < 0 || y < 0 || x >= x_tiles() || y >= y_tiles())
+ return EMPTY;
+
+ return field[y * x_tiles() + x];
+}
+
+void Board::gravity(int col, bool update)
+{
+ if(gravity_flag)
+ {
+ int rptr = y_tiles()-1, wptr = y_tiles()-1;
+ while(rptr >= 0)
+ {
+ if(getField(col, wptr) != EMPTY)
+ {
+ rptr--;
+ wptr--;
+ }
+ else
+ {
+ if(getField(col, rptr) != EMPTY)
+ {
+ setField(col, wptr, getField(col, rptr));
+ setField(col, rptr, EMPTY);
+ if(update)
+ {
+ updateField(col, rptr);
+ updateField(col, wptr);
+ }
+ wptr--;
+ rptr--;
+ }
+ else
+ rptr--;
+ }
+ }
+ }
+}
+
+void Board::mousePressEvent(QMouseEvent *e)
+{
+ // Calculate field position
+ int pos_x = (e->pos().x() - xOffset()) / tiles.tileWidth();
+ int pos_y = (e->pos().y() - yOffset()) / tiles.tileHeight();
+
+ if(e->pos().x() < xOffset() || e->pos().y() < yOffset() ||
+ pos_x >= x_tiles() || pos_y >= y_tiles())
+ {
+ pos_x = -1;
+ pos_y = -1;
+ }
+
+ // Mark tile
+ if(e->button() == LeftButton)
+ {
+ clearHighlight();
+
+ if(pos_x != -1)
+ marked(pos_x, pos_y);
+ }
+
+ // Assist by highlighting all tiles of same type
+ if(e->button() == RightButton)
+ {
+ int clicked_tile = getField(pos_x, pos_y);
+
+ // Clear marked tile
+ if(mark_x != -1 && getField(mark_x, mark_y) != clicked_tile)
+ {
+ // We need to set mark_x and mark_y to -1 before calling
+ // updateField() to ensure the tile is redrawn as unmarked.
+ int oldmarkx = mark_x;
+ int oldmarky = mark_y;
+ mark_x = -1;
+ mark_y = -1;
+ updateField(oldmarkx, oldmarky, false);
+ }
+ else
+ {
+ mark_x = -1;
+ mark_y = -1;
+ }
+
+ // Perform highlighting
+ if(clicked_tile != highlighted_tile)
+ {
+ int old_highlighted = highlighted_tile;
+ highlighted_tile = clicked_tile;
+ for(int i = 0; i < x_tiles(); i++)
+ {
+ for(int j = 0; j < y_tiles(); j++)
+ {
+ const int field_tile = getField(i, j);
+ if(field_tile != EMPTY)
+ {
+ if(field_tile == old_highlighted)
+ updateField(i, j, false);
+ else if(field_tile == clicked_tile)
+ updateField(i, j, false);
+ }
+ }
+ }
+ }
+ }
+}
+
+// The board is centred inside the main playing area. xOffset/yOffset provide
+// the coordinates of the top-left corner of the board.
+int Board::xOffset() const
+{
+ return (width() - (tiles.tileWidth() * x_tiles())) / 2;
+}
+
+int Board::yOffset() const
+{
+ return (height() - (tiles.tileHeight() * y_tiles())) / 2;
+}
+
+void Board::setSize(int x, int y)
+{
+ if(x == x_tiles() && y == y_tiles())
+ return;
+
+ if(field != 0)
+ delete [] field;
+
+ field = new int[ x * y ];
+ _x_tiles = x;
+ _y_tiles = y;
+ for(int i = 0; i < x; i++)
+ for(int j = 0; j < y; j++)
+ setField(i, j, EMPTY);
+
+ // set the minimum size of the scalable window
+ const double MINIMUM_SCALE = 0.2;
+ int w = qRound(tiles.unscaledTileWidth() * MINIMUM_SCALE) * x_tiles();
+ int h = qRound(tiles.unscaledTileHeight() * MINIMUM_SCALE) * y_tiles();
+ w += tiles.unscaledTileWidth();
+ h += tiles.unscaledTileWidth();
+
+ setMinimumSize(w, h);
+
+ resizeBoard();
+ newGame();
+ emit changed();
+}
+
+void Board::resizeEvent(QResizeEvent*)
+{
+ resizeBoard();
+ emit resized();
+}
+
+void Board::resizeBoard()
+{
+ // calculate tile size required to fit all tiles in the window
+ int w = static_cast<int>( static_cast<double>(width() - tiles.unscaledTileWidth()) / x_tiles() );
+ int h = static_cast<int>( static_cast<double>(height() - tiles.unscaledTileWidth()) / y_tiles() );
+
+ const double MAXIMUM_SCALE = 2.0;
+ w = std::min(w, static_cast<int>((tiles.unscaledTileWidth() * MAXIMUM_SCALE) + 0.5));
+ h = std::min(h, static_cast<int>((tiles.unscaledTileHeight() * MAXIMUM_SCALE) + 0.5));
+
+ tiles.resizeTiles(w, h);
+}
+
+QSize Board::unscaledSize() const
+{
+ int w = tiles.unscaledTileWidth() * x_tiles() + tiles.unscaledTileWidth();
+ int h = tiles.unscaledTileHeight() * y_tiles() + tiles.unscaledTileWidth();
+ return QSize(w, h);
+}
+
+void Board::newGame()
+{
+ //kdDebug() << "NewGame" << endl;
+ int i, x, y, k;
+
+ mark_x = -1;
+ mark_y = -1;
+ highlighted_tile = -1; // will clear previous highlight
+
+ _undo.clear();
+ _redo.clear();
+ connection.clear();
+
+ // distribute all tiles on board
+ int cur_tile = 1;
+ for(y = 0; y < y_tiles(); y += 4)
+ {
+ for(x = 0; x < x_tiles(); ++x)
+ {
+ for(k = 0; k < 4 && y + k < y_tiles(); k++)
+ setField(x, y + k, cur_tile);
+
+ cur_tile++;
+ if(cur_tile > TileSet::nTiles)
+ cur_tile = 1;
+ }
+ }
+
+ if(getShuffle() == 0)
+ {
+ update();
+ starttime = time((time_t *)0);
+ emit changed();
+ return;
+ }
+
+ // shuffle the field
+ int tx = x_tiles();
+ int ty = y_tiles();
+ for(i = 0; i < x_tiles() * y_tiles() * getShuffle(); i++)
+ {
+ int x1 = random.getLong(tx);
+ int y1 = random.getLong(ty);
+ int x2 = random.getLong(tx);
+ int y2 = random.getLong(ty);
+ int t = getField(x1, y1);
+ setField(x1, y1, getField(x2, y2));
+ setField(x2, y2, t);
+ }
+
+ // do not make solvable if _solvable_flag is false
+ if(!_solvable_flag)
+ {
+ update();
+ starttime = time((time_t *)0);
+ emit changed();
+ return;
+ }
+
+
+ int fsize = x_tiles() * y_tiles() * sizeof(int);
+ int *oldfield = new int[x_tiles() * y_tiles()];
+ memcpy(oldfield, field, fsize); // save field
+ int *tiles = new int[x_tiles() * y_tiles()];
+ int *pos = new int[x_tiles() * y_tiles()];
+
+ while(!solvable(true))
+ {
+ //kdDebug() << "Not solvable" << endl;
+ //dumpBoard();
+
+ // generate a list of free tiles and positions
+ int num_tiles = 0;
+ for(i = 0; i < x_tiles() * y_tiles(); i++)
+ if(field[i] != EMPTY)
+ {
+ pos[num_tiles] = i;
+ tiles[num_tiles] = field[i];
+ num_tiles++;
+ }
+
+ // restore field
+ memcpy(field, oldfield, fsize);
+
+ // redistribute unsolved tiles
+ while(num_tiles > 0)
+ {
+ // get a random tile
+ int r1 = random.getLong(num_tiles);
+ int r2 = random.getLong(num_tiles);
+ int tile = tiles[r1];
+ int apos = pos[r2];
+
+ // truncate list
+ tiles[r1] = tiles[num_tiles-1];
+ pos[r2] = pos[num_tiles-1];
+ num_tiles--;
+
+ // put this tile on the new position
+ field[apos] = tile;
+ }
+
+ // remember field
+ memcpy(oldfield, field, fsize);
+ }
+
+
+ // restore field
+ memcpy(field, oldfield, fsize);
+ delete tiles;
+ delete pos;
+ delete oldfield;
+
+ update();
+ starttime = time((time_t *)0);
+ emit changed();
+}
+
+bool Board::isTileHighlighted(int x, int y) const
+{
+ if(x == mark_x && y == mark_y)
+ return true;
+
+ if(getField(x, y) == highlighted_tile)
+ return true;
+
+ if(!connection.empty())
+ {
+ if(x == connection.front().x && y == connection.front().y)
+ return true;
+
+ if(x == connection.back().x && y == connection.back().y)
+ return true;
+ }
+
+ return false;
+}
+
+void Board::updateField(int x, int y, bool erase)
+{
+ QRect r(xOffset() + x * tiles.tileWidth(),
+ yOffset() + y * tiles.tileHeight(),
+ tiles.tileWidth(),
+ tiles.tileHeight());
+
+ repaint(r, erase);
+}
+
+void Board::paintEvent(QPaintEvent *e)
+{
+
+ QRect ur = e->rect(); // rectangle to update
+ QPixmap pm(ur.size()); // Pixmap for double-buffering
+ pm.fill(this, ur.topLeft()); // fill with widget background
+ QPainter p(&pm);
+ p.translate(-ur.x(), -ur.y()); // use widget coordinate system
+
+ if(paused)
+ {
+ p.setFont(KGlobalSettings::largeFont());
+ p.drawText(rect(), Qt::AlignCenter, i18n("Game Paused"));
+ }
+ else
+ {
+ int w = tiles.tileWidth();
+ int h = tiles.tileHeight();
+ for(int i = 0; i < x_tiles(); i++)
+ {
+ for(int j = 0; j < y_tiles(); j++)
+ {
+ int tile = getField(i, j);
+ if(tile == EMPTY)
+ continue;
+
+ int xpos = xOffset() + i * w;
+ int ypos = yOffset() + j * h;
+ QRect r(xpos, ypos, w, h);
+ if(e->rect().intersects(r))
+ {
+ if(isTileHighlighted(i, j))
+ p.drawPixmap(xpos, ypos, tiles.highlightedTile(tile-1));
+ else
+ p.drawPixmap(xpos, ypos, tiles.tile(tile-1));
+ }
+ }
+ }
+ }
+ p.end();
+ bitBlt( this, ur.topLeft(), &pm );
+}
+
+void Board::marked(int x, int y)
+{
+ // make sure that the previous connection is correctly undrawn
+ undrawConnection();
+
+ if(getField(x, y) == EMPTY)
+ return;
+
+ if(x == mark_x && y == mark_y)
+ {
+ // unmark the piece
+ mark_x = -1;
+ mark_y = -1;
+ updateField(x, y, false);
+ return;
+ }
+
+ if(mark_x == -1)
+ {
+ mark_x = x;
+ mark_y = y;
+ updateField(x, y, false);
+ return;
+ }
+
+ int fld1 = getField(mark_x, mark_y);
+ int fld2 = getField(x, y);
+
+ // both field same?
+ if(fld1 != fld2)
+ return;
+
+ // trace
+ if(findPath(mark_x, mark_y, x, y, connection))
+ {
+ madeMove(mark_x, mark_y, x, y);
+ drawConnection(getDelay());
+ setField(mark_x, mark_y, EMPTY);
+ setField(x, y, EMPTY);
+ grav_col_1 = x;
+ grav_col_2 = mark_x;
+ mark_x = -1;
+ mark_y = -1;
+
+ // game is over?
+ // Must delay until after tiles fall to make this test
+ // See undrawConnection GP.
+ }
+ else
+ {
+ connection.clear();
+ }
+}
+
+
+void Board::clearHighlight()
+{
+ if(highlighted_tile != -1)
+ {
+ int old_highlight = highlighted_tile;
+ highlighted_tile = -1;
+
+ for(int i = 0; i < x_tiles(); i++)
+ for(int j = 0; j < y_tiles(); j++)
+ if(old_highlight == getField(i, j))
+ updateField(i, j, false);
+ }
+}
+
+// Can we make a path between two tiles with a single line?
+bool Board::canMakePath(int x1, int y1, int x2, int y2) const
+{
+ if(x1 == x2)
+ {
+ for(int i = std::min(y1, y2) + 1; i < std::max(y1, y2); i++)
+ if(getField(x1, i) != EMPTY)
+ return false;
+
+ return true;
+ }
+
+ if(y1 == y2)
+ {
+ for(int i = std::min(x1, x2) + 1; i < std::max(x1, x2); i++)
+ if(getField(i, y1) != EMPTY)
+ return false;
+
+ return true;
+ }
+
+ return false;
+}
+
+bool Board::findPath(int x1, int y1, int x2, int y2, Path& p) const
+{
+ p.clear();
+
+ if(findSimplePath(x1, y1, x2, y2, p))
+ return true;
+
+ // Find a path of 3 segments
+ const int dx[4] = { 1, 0, -1, 0 };
+ const int dy[4] = { 0, 1, 0, -1 };
+
+ for(int i = 0; i < 4; i++)
+ {
+ int newx = x1 + dx[i];
+ int newy = y1 + dy[i];
+ while(newx >= -1 && newx <= x_tiles() &&
+ newy >= -1 && newy <= y_tiles() &&
+ getField(newx, newy) == EMPTY)
+ {
+ if(findSimplePath(newx, newy, x2, y2, p))
+ {
+ p.push_front(Position(x1, y1));
+ return true;
+ }
+ newx += dx[i];
+ newy += dy[i];
+ }
+ }
+
+ return false;
+}
+
+// Find a path of 1 or 2 segments between tiles. Returns whether
+// a path was found, and if so, the path is returned via 'p'.
+bool Board::findSimplePath(int x1, int y1, int x2, int y2, Path& p) const
+{
+ // Find direct line (path of 1 segment)
+ if(canMakePath(x1, y1, x2, y2))
+ {
+ p.push_back(Position(x1, y1));
+ p.push_back(Position(x2, y2));
+ return true;
+ }
+
+ // If the tiles are in the same row or column, then a
+ // a 'simple path' cannot be found between them
+ if(x1 == x2 || y1 == y2)
+ return false;
+
+ // Find path of 2 segments (route A)
+ if(getField(x2, y1) == EMPTY && canMakePath(x1, y1, x2, y1) &&
+ canMakePath(x2, y1, x2, y2))
+ {
+ p.push_back(Position(x1, y1));
+ p.push_back(Position(x2, y1));
+ p.push_back(Position(x2, y2));
+ return true;
+ }
+
+ // Find path of 2 segments (route B)
+ if(getField(x1, y2) == EMPTY && canMakePath(x1, y1, x1, y2) &&
+ canMakePath(x1, y2, x2, y2))
+ {
+ p.push_back(Position(x1, y1));
+ p.push_back(Position(x1, y2));
+ p.push_back(Position(x2, y2));
+ return true;
+ }
+
+ return false;
+}
+
+void Board::drawConnection(int timeout)
+{
+ if(connection.empty())
+ return;
+
+ // lighten the fields
+ updateField(connection.front().x, connection.front().y);
+ updateField(connection.back().x, connection.back().y);
+
+ QPainter p;
+ p.begin(this);
+ p.setPen(QPen(QColor("red"), tiles.lineWidth()));
+
+ // Path.size() will always be >= 2
+ Path::const_iterator pathEnd = connection.end();
+ Path::const_iterator pt1 = connection.begin();
+ Path::const_iterator pt2 = pt1;
+ ++pt2;
+ while(pt2 != pathEnd)
+ {
+ p.drawLine( midCoord(pt1->x, pt1->y), midCoord(pt2->x, pt2->y) );
+ ++pt1;
+ ++pt2;
+ }
+
+ p.flush();
+ p.end();
+
+ QTimer::singleShot(timeout, this, SLOT(undrawConnection()));
+}
+
+void Board::undrawConnection()
+{
+ if(grav_col_1 != -1 || grav_col_2 != -1)
+ {
+ gravity(grav_col_1, true);
+ gravity(grav_col_2, true);
+ grav_col_1 = -1;
+ grav_col_2 = -1;
+ }
+
+ // is already undrawn?
+ if(connection.empty())
+ return;
+
+ // Redraw all affected fields
+
+ Path oldConnection = connection;
+ connection.clear();
+
+ // Path.size() will always be >= 2
+ Path::const_iterator pathEnd = oldConnection.end();
+ Path::const_iterator pt1 = oldConnection.begin();
+ Path::const_iterator pt2 = pt1;
+ ++pt2;
+ while(pt2 != pathEnd)
+ {
+ if(pt1->y == pt2->y)
+ {
+ for(int i = std::min(pt1->x, pt2->x); i <= std::max(pt1->x, pt2->x); i++)
+ updateField(i, pt1->y);
+ }
+ else
+ {
+ for(int i = std::min(pt1->y, pt2->y); i <= std::max(pt1->y, pt2->y); i++)
+ updateField(pt1->x, i);
+ }
+ ++pt1;
+ ++pt2;
+ }
+
+ Path dummyPath;
+ // game is over?
+ if(!getHint_I(dummyPath))
+ {
+ time_for_game = (int)difftime( time((time_t)0), starttime);
+ emit endOfGame();
+ }
+}
+
+QPoint Board::midCoord(int x, int y) const
+{
+ QPoint p;
+ int w = tiles.tileWidth();
+ int h = tiles.tileHeight();
+
+ if(x == -1)
+ p.setX(xOffset() - (w / 4));
+ else if(x == x_tiles())
+ p.setX(xOffset() + (w * x_tiles()) + (w / 4));
+ else
+ p.setX(xOffset() + (w * x) + (w / 2));
+
+ if(y == -1)
+ p.setY(yOffset() - (w / 4));
+ else if(y == y_tiles())
+ p.setY(yOffset() + (h * y_tiles()) + (w / 4));
+ else
+ p.setY(yOffset() + (h * y) + (h / 2));
+
+ return p;
+}
+
+void Board::setDelay(int newvalue)
+{
+ _delay = newvalue;
+}
+
+int Board::getDelay() const
+{
+ return _delay;
+}
+
+void Board::madeMove(int x1, int y1, int x2, int y2)
+{
+ Move *m = new Move(x1, y1, x2, y2, getField(x1, y1));
+ _undo.append(m);
+ while(_redo.count())
+ _redo.removeFirst();
+ emit changed();
+}
+
+bool Board::canUndo() const
+{
+ return !_undo.isEmpty();
+}
+
+bool Board::canRedo() const
+{
+ return !_redo.isEmpty();
+}
+
+void Board::undo()
+{
+ if(canUndo())
+ {
+ clearHighlight();
+ undrawConnection();
+ Move* m = _undo.last();
+ _undo.take();
+ if(gravityFlag())
+ {
+ int y;
+
+ // When both tiles reside in the same column, the order of undo is
+ // significant (we must undo the lower tile first).
+ if(m->x1 == m->x2 && m->y1 < m->y2)
+ {
+ std::swap(m->x1, m->x2);
+ std::swap(m->y1, m->y2);
+ }
+
+ for(y = 0; y < m->y1; y++)
+ {
+ setField(m->x1, y, getField(m->x1, y+1));
+ updateField(m->x1, y);
+ }
+
+ for(y = 0; y < m->y2; y++)
+ {
+ setField(m->x2, y, getField(m->x2, y+1));
+ updateField(m->x2, y);
+ }
+ }
+
+ setField(m->x1, m->y1, m->tile);
+ setField(m->x2, m->y2, m->tile);
+ updateField(m->x1, m->y1);
+ updateField(m->x2, m->y2);
+ _redo.prepend(m);
+ emit changed();
+ }
+}
+
+void Board::redo()
+{
+ if(canRedo())
+ {
+ clearHighlight();
+ undrawConnection();
+ Move* m = _redo.take(0);
+ setField(m->x1, m->y1, EMPTY);
+ setField(m->x2, m->y2, EMPTY);
+ updateField(m->x1, m->y1);
+ updateField(m->x2, m->y2);
+ gravity(m->x1, true);
+ gravity(m->x2, true);
+ _undo.append(m);
+ emit changed();
+ }
+}
+
+void Board::showHint()
+{
+ undrawConnection();
+
+ if(getHint_I(connection))
+ drawConnection(1000);
+}
+
+
+#ifdef DEBUGGING
+void Board::makeHintMove()
+{
+ Path p;
+
+ if(getHint_I(p))
+ {
+ mark_x = -1;
+ mark_y = -1;
+ marked(p.front().x, p.front().y);
+ marked(p.back().x, p.back().y);
+ }
+}
+
+void Board::finish()
+{
+ Path p;
+ bool ready=false;
+
+ while(!ready && getHint_I(p))
+ {
+ mark_x = -1;
+ mark_y = -1;
+ if(tilesLeft() == 2)
+ ready = true;
+ marked(p.front().x, p.front().y);
+ marked(p.back().x, p.back().y);
+ kapp->processEvents();
+ usleep(250*1000);
+ }
+}
+
+void Board::dumpBoard() const
+{
+ kdDebug() << "Board contents:" << endl;
+ for(int y = 0; y < y_tiles(); ++y)
+ {
+ QString row;
+ for(int x = 0; x < x_tiles(); ++x)
+ {
+ int tile = getField(x, y);
+ if(tile == EMPTY)
+ row += " --";
+ else
+ row += QString("%1").arg(getField(x, y), 3);
+ }
+ kdDebug() << row << endl;
+ }
+}
+#endif
+
+bool Board::getHint_I(Path& p) const
+{
+ //dumpBoard();
+ short done[TileSet::nTiles];
+ for( short index = 0; index < TileSet::nTiles; index++ )
+ done[index] = 0;
+
+ for(int x = 0; x < x_tiles(); x++)
+ {
+ for(int y = 0; y < y_tiles(); y++)
+ {
+ int tile = getField(x, y);
+ if(tile != EMPTY && done[tile - 1] != 4)
+ {
+ // for all these types of tile search path's
+ for(int xx = 0; xx < x_tiles(); xx++)
+ {
+ for(int yy = 0; yy < y_tiles(); yy++)
+ {
+ if(xx != x || yy != y)
+ {
+ if(getField(xx, yy) == tile)
+ if(findPath(x, y, xx, yy, p))
+ {
+ //kdDebug() << "path.size() == " << p.size() << endl;
+ //for(Path::const_iterator i = p.begin(); i != p.end(); ++i)
+ // kdDebug() << "pathEntry: (" << i->x << ", " << i->y
+ // << ") => " << getField(i->x, i->y) << endl;
+ return true;
+ }
+ }
+ }
+ }
+ done[tile - 1]++;
+ }
+ }
+ }
+
+ return false;
+}
+
+void Board::setShuffle(int newvalue)
+{
+ if(newvalue != _shuffle){
+ _shuffle = newvalue;
+ newGame();
+ }
+}
+
+int Board::getShuffle() const
+{
+ return _shuffle;
+}
+
+int Board::tilesLeft() const
+{
+ int left = 0;
+
+ for(int i = 0; i < x_tiles(); i++)
+ for(int j = 0; j < y_tiles(); j++)
+ if(getField(i, j) != EMPTY)
+ left++;
+
+ return left;
+}
+
+int Board::getCurrentTime() const
+{
+ return (int)difftime(time((time_t *)0),starttime);
+}
+
+int Board::getTimeForGame() const
+{
+ if(tilesLeft() == 0)
+ {
+ return time_for_game;
+ }
+ else
+ {
+ if(paused)
+ return (int)difftime(pause_start, starttime);
+ else
+ return (int)difftime(time((time_t *)0), starttime);
+ }
+}
+
+bool Board::solvable(bool norestore)
+{
+ int *oldfield = 0;
+
+ if(!norestore)
+ {
+ oldfield = new int [x_tiles() * y_tiles()];
+ memcpy(oldfield, field, x_tiles() * y_tiles() * sizeof(int));
+ }
+
+ Path p;
+ while(getHint_I(p))
+ {
+ kdFatal(getField(p.front().x, p.front().y) != getField(p.back().x, p.back().y))
+ << "Removing unmateched tiles: (" << p.front().x << ", " << p.front().y << ") => "
+ << getField(p.front().x, p.front().y) << " (" << p.back().x << ", " << p.back().y << ") => "
+ << getField(p.back().x, p.back().y) << endl;
+ setField(p.front().x, p.front().y, EMPTY);
+ setField(p.back().x, p.back().y, EMPTY);
+ //if(gravityFlag())
+ //{
+ // gravity(p.front().x, false);
+ // gravity(p.back().x, false);
+ //}
+ }
+
+ int left = tilesLeft();
+
+ if(!norestore)
+ {
+ memcpy(field, oldfield, x_tiles() * y_tiles() * sizeof(int));
+ delete [] oldfield;
+ }
+
+ return (bool)(left == 0);
+}
+
+bool Board::getSolvableFlag() const
+{
+ return _solvable_flag;
+}
+
+void Board::setSolvableFlag(bool value)
+{
+ if(value && !_solvable_flag && !solvable()){
+ _solvable_flag = value;
+ newGame();
+ }
+ else
+ _solvable_flag = value;
+}
+
+bool Board::gravityFlag() const
+{
+ return gravity_flag;
+}
+
+void Board::setGravityFlag(bool b)
+{
+ if( gravity_flag != b ){
+ if(canUndo() || canRedo())
+ newGame();
+ gravity_flag = b;
+ }
+}
+
+bool Board::pause()
+{
+ paused = !paused;
+ if(paused)
+ pause_start = time((time_t *)0);
+ else
+ starttime += (time_t) difftime( time((time_t *)0), pause_start);
+ update();
+
+ return paused;
+}
+
+QSize Board::sizeHint() const
+{
+ int dpi = QPaintDeviceMetrics(this).logicalDpiX();
+ if (dpi < 75)
+ dpi = 75;
+ return QSize(9*dpi,7*dpi);
+}
+
+#include "board.moc"
diff --git a/kshisen/board.h b/kshisen/board.h
new file mode 100644
index 00000000..c38fba57
--- /dev/null
+++ b/kshisen/board.h
@@ -0,0 +1,189 @@
+/* Yo Emacs, this is -*- C++ -*-
+ *******************************************************************
+ *******************************************************************
+ *
+ *
+ * KSHISEN
+ *
+ *
+ *******************************************************************
+ *
+ * A japanese game similar to mahjongg
+ *
+ *******************************************************************
+ *
+ * created 1997 by Mario Weilguni <mweilguni@sime.com>
+ *
+ *******************************************************************
+ *
+ * This file is part of the KDE project "KSHISEN"
+ *
+ * KSHISEN 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, or (at your option)
+ * any later version.
+ *
+ * KSHISEN 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 KSHISEN; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *******************************************************************
+ */
+
+#ifndef __BOARD__H__
+#define __BOARD__H__
+
+// Should this get the whole HAVE_SYS_TIME_H TIME_WITH_SYS_TIME treatment?
+#include <time.h>
+
+#include <krandomsequence.h>
+#include <list>
+#include "tileset.h"
+#include "debug.h"
+
+struct Position
+{
+ Position() : x(0), y(0) { }
+ Position(int _x, int _y) : x(_x), y(_y) { }
+ int x;
+ int y;
+};
+
+typedef std::list<Position> Path;
+
+class Move
+{
+public:
+ Move(int _x1, int _y1, int _x2, int _y2, int _tile) :
+ x1(_x1), y1(_y1), x2(_x2), y2(_y2), tile(_tile) { }
+
+ int x1, y1, x2, y2;
+ int tile;
+};
+
+class Board : public QWidget
+{
+ Q_OBJECT
+
+public:
+ Board(QWidget *parent = 0, const char *name=0);
+ ~Board();
+
+ virtual void paintEvent(QPaintEvent *);
+ virtual void mousePressEvent(QMouseEvent *);
+ virtual void resizeEvent(QResizeEvent*);
+
+ void setDelay(int);
+ int getDelay() const;
+
+ bool canUndo() const;
+ bool canRedo() const;
+ void redo();
+ void undo();
+
+ void setSize(int x, int y);
+ void resizeBoard();
+ QSize unscaledSize() const;
+ void newGame();
+ void setShuffle(int);
+ int getShuffle() const;
+
+ void showHint();
+ bool getHint_I(Path& p) const;
+
+#ifdef DEBUGGING
+ void makeHintMove();
+ void finish();
+ void dumpBoard() const;
+#endif
+
+ int tilesLeft() const;
+ int getCurrentTime() const;
+ int getTimeForGame() const;
+
+ bool solvable(bool norestore = FALSE);
+
+ bool getSolvableFlag() const;
+ void setSolvableFlag(bool);
+ bool gravityFlag() const;
+ void setGravityFlag(bool);
+
+ int x_tiles() const;
+ int y_tiles() const;
+
+ bool isPaused() const { return paused; }
+
+signals:
+ void markMatched();
+ void changed();
+ void endOfGame();
+ void resized();
+
+public slots:
+ bool pause();
+ void loadSettings();
+
+private slots:
+ void undrawConnection();
+ void gravity(int, bool);
+
+protected:
+ virtual QSize sizeHint() const;
+
+private: // functions
+ void initBoard();
+
+ int xOffset() const;
+ int yOffset() const;
+
+ void setField(int x, int y, int value);
+ int getField(int x, int y) const;
+ void updateField(int, int, bool erase = true);
+ void clearHighlight();
+ bool canMakePath(int x1, int y1, int x2, int y2) const;
+ bool findPath(int x1, int y1, int x2, int y2, Path& p) const;
+ bool findSimplePath(int x1, int y1, int x2, int y2, Path& p) const;
+ bool isTileHighlighted(int x, int y) const;
+ void drawConnection(int timeout);
+ QPoint midCoord(int x, int y) const;
+ void marked(int x, int y);
+ void madeMove(int x1, int y1, int x2, int y2);
+
+private:
+ time_t starttime;
+ time_t time_for_game;
+
+ TileSet tiles;
+
+ KRandomSequence random;
+
+ QPtrList<Move> _undo;
+ QPtrList<Move> _redo;
+
+ int undraw_timer_id;
+ int mark_x;
+ int mark_y;
+ Path connection;
+ int *field;
+ int _x_tiles;
+ int _y_tiles;
+ int _delay;
+ int _shuffle;
+
+ bool paused;
+ time_t pause_start;
+
+ bool gravity_flag;
+ bool _solvable_flag;
+ int grav_col_1, grav_col_2;
+
+ int highlighted_tile;
+};
+
+#endif
diff --git a/kshisen/debug.h b/kshisen/debug.h
new file mode 100644
index 00000000..aa79a7e5
--- /dev/null
+++ b/kshisen/debug.h
@@ -0,0 +1 @@
+//#define DEBUGGING
diff --git a/kshisen/hi128-app-kshisen.png b/kshisen/hi128-app-kshisen.png
new file mode 100644
index 00000000..8badc8f9
--- /dev/null
+++ b/kshisen/hi128-app-kshisen.png
Binary files differ
diff --git a/kshisen/hi16-app-kshisen.png b/kshisen/hi16-app-kshisen.png
new file mode 100644
index 00000000..9077c7c8
--- /dev/null
+++ b/kshisen/hi16-app-kshisen.png
Binary files differ
diff --git a/kshisen/hi22-app-kshisen.png b/kshisen/hi22-app-kshisen.png
new file mode 100644
index 00000000..237b1a66
--- /dev/null
+++ b/kshisen/hi22-app-kshisen.png
Binary files differ
diff --git a/kshisen/hi32-app-kshisen.png b/kshisen/hi32-app-kshisen.png
new file mode 100644
index 00000000..7f7ac9d9
--- /dev/null
+++ b/kshisen/hi32-app-kshisen.png
Binary files differ
diff --git a/kshisen/hi48-app-kshisen.png b/kshisen/hi48-app-kshisen.png
new file mode 100644
index 00000000..e67de754
--- /dev/null
+++ b/kshisen/hi48-app-kshisen.png
Binary files differ
diff --git a/kshisen/hi64-app-kshisen.png b/kshisen/hi64-app-kshisen.png
new file mode 100644
index 00000000..7ec56314
--- /dev/null
+++ b/kshisen/hi64-app-kshisen.png
Binary files differ
diff --git a/kshisen/kshisen.desktop b/kshisen/kshisen.desktop
new file mode 100644
index 00000000..4fa3712c
--- /dev/null
+++ b/kshisen/kshisen.desktop
@@ -0,0 +1,76 @@
+[Desktop Entry]
+Exec=kshisen %i %m -caption "%c"
+Type=Application
+DocPath=kshisen/index.html
+Name=Shisen-Sho
+Name[af]=Shisen-sho
+Name[be]=Ші-сен-сёе
+Name[bn]=শিসেন-শো
+Name[cs]=Šisen-Šo
+Name[eo]=Ŝisen-Ŝo
+Name[fr]=Shisen-sho
+Name[he]=שישן־שו
+Name[hi]=शाईसेन-शो
+Name[ja]=四川省
+Name[ne]=सिसेन शो
+Name[pa]=ਸ਼ਿਸੀਨ-ਸ਼ੋ
+Name[sk]=Šisen-Šo
+Name[ta]=ஷிசென்-ஷோ
+Name[tg]=Шисен-Шо
+Name[th]=ชิเซน-โช - K
+Name[uk]=Шісен-Шо
+Name[zh_CN]=连连看
+Name[zu]=I-Shisen-Sho
+GenericName=Shisen-Sho Mahjongg-like Tile Game
+GenericName[be]=Варыянт маджонга Шысен-сё
+GenericName[bg]=Игра с плочки
+GenericName[bn]=শিসেন-শো মাহজং-জাতীয় টালির খেলা
+GenericName[br]=Ur c'hoari teol a seurt gant Shisen*Sho Mahjongg
+GenericName[bs]=Shisen-Sho igra nalik na Mahjongg
+GenericName[ca]=Joc de mosaics Shisen-Sho a l'estil Mahjongg
+GenericName[cs]=Hra s dlaždicemi Šisen-Šo
+GenericName[cy]=Gêm Deiliau Shisen-Sho sy'n debyg i Mahjongg
+GenericName[da]=Shisen-Sho Mahjongg-lignende flisespil
+GenericName[de]=Mahjongg-ähnliches Shisen-Sho Spiel
+GenericName[el]=Παιχνίδι παρόμοιο με το Shisen-Sho Mahjongg
+GenericName[eo]=Shisen-Sho Mahjongg-simila kahel-ludo
+GenericName[es]=Juego de fichas similar al Shisen-Sho Mahjongg
+GenericName[et]=Mahjonggi moodi klotsimäng
+GenericName[eu]=Shisen-Sho Mahjongg-en antzeko fitxa-jokoa
+GenericName[fa]=بازی کاشی شبیه Shisen-Sho Mahjongg
+GenericName[fi]=Shisen-Sho Mahjongg--tyylinen palikkapeli
+GenericName[fr]=Jeu de tuiles Shisen-Sho dans le style Mahjongg
+GenericName[he]=Shisen Sho, חיקוי מה ג'ונג, משחק קלפים
+GenericName[hr]=Shisen-Sho igra s pločicama poput Mahjongga
+GenericName[hu]=Mahjongg-változat
+GenericName[is]=Shisen-Sho kubbaleikur líkur Mahjongg
+GenericName[it]=Shisen-Sho, gioco di tessere simile a Mahjongg
+GenericName[ja]=四川省マージャン牌ゲーム
+GenericName[km]=ល្បែង​ក្បឿង​ដូច Shisen-Sho Mahjongg
+GenericName[ko]=시센-쇼 마작과 같은 타일 게임
+GenericName[lv]=Shisen-Sho Mahjongg līdzīga spēle
+GenericName[mk]=Игра со плочки слична на Shisen-Sho Mahjongg
+GenericName[nb]=Shisen-Sho Mahjongg-lignende brikkespill
+GenericName[nds]=Mahjongg-liek Speel
+GenericName[ne]=सिनसेन शो माहाजोङ जस्तै टायल खेल
+GenericName[nl]=Shisen-Sho Mahjongg-achtig stenenspel
+GenericName[nn]=Shisen-Sho Mahjongg-liknande brikkespel
+GenericName[pa]=ਸ਼ਿਸੀਨ-ਸ਼ੋ ਮਹਿਜ਼ੋਗ ਵਰਗੀ ਖੇਡ
+GenericName[pl]=Gra typu Shisen-Sho Mahjongg
+GenericName[pt]=Jogo de Padrões Shisen-Sho
+GenericName[pt_BR]=Jogo de Ladrilhos parecido com Shisen-Sho Mahjongg
+GenericName[ru]=Ши-сен-сё
+GenericName[se]=Shisen-Sho Mahjongg-lágan bihttáspeallu
+GenericName[sk]=Dlaždičková hra typu Shisen-Sho Mahjongg
+GenericName[sl]=Igra s ploščicami Shisen-Sho, podobna Mahjonggu
+GenericName[sr]=Shisen-Sho, игра са пољима налик на Mahjongg
+GenericName[sr@Latn]=Shisen-Sho, igra sa poljima nalik na Mahjongg
+GenericName[sv]=Shisen-Sho Mahjongg-liknande brickspel
+GenericName[ta]=ஷிசெந்ஷோ மாஹ்ஜோங் போன்ற ஓடு விளையாட்டு
+GenericName[uk]=Шісен-Шо Махжонг-на кшталт гри Плитки
+GenericName[zh_CN]=类似连连看的麻将游戏
+GenericName[zh_TW]=四川省麻將牌遊戲
+Icon=kshisen
+X-KDE-StartupNotify=true
+X-DCOP-ServiceType=Multi
+Categories=Qt;KDE;Game;BoardGame;
diff --git a/kshisen/kshisen.kcfg b/kshisen/kshisen.kcfg
new file mode 100644
index 00000000..9c486ee8
--- /dev/null
+++ b/kshisen/kshisen.kcfg
@@ -0,0 +1,35 @@
+<?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="kshisenrc"/>
+ <group name="Game">
+ <entry name="Solvable" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="Gravity" type="Bool">
+ <default>true</default>
+ </entry>
+ <entry name="Unscaled" type="Bool">
+ <default>true</default>
+ </entry>
+
+
+ <entry name="Speed" type="Int">
+ <default>2</default>
+ <min>0</min>
+ <max>4</max>
+ </entry>
+ <entry name="Size" type="Int">
+ <default>2</default>
+ <min>0</min>
+ <max>4</max>
+ </entry>
+ <entry name="Level" type="Int">
+ <default>1</default>
+ <min>0</min>
+ <max>2</max>
+ </entry>
+ </group>
+</kcfg>
diff --git a/kshisen/kshisenui.rc b/kshisen/kshisenui.rc
new file mode 100644
index 00000000..dcac9fdc
--- /dev/null
+++ b/kshisen/kshisenui.rc
@@ -0,0 +1,16 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+<kpartgui name="kshisen" version="10">
+<MenuBar>
+ <Menu name="move"><!--
+ <Action name="move_solvable"/>
+ <Action name="move_finish"/>-->
+ </Menu>
+</MenuBar>
+<ToolBar name="mainToolBar"><text>Main Toolbar</text>
+ <Action name="move_undo"/>
+ <Action name="move_redo"/>
+ <Separator/>
+ <Action name="game_pause"/>
+</ToolBar>
+<StatusBar/>
+</kpartgui>
diff --git a/kshisen/main.cpp b/kshisen/main.cpp
new file mode 100644
index 00000000..161cffec
--- /dev/null
+++ b/kshisen/main.cpp
@@ -0,0 +1,74 @@
+/* Yo Emacs, this is -*- C++ -*-
+ *******************************************************************
+ *******************************************************************
+ *
+ *
+ * KSHISEN
+ *
+ *
+ *******************************************************************
+ *
+ * A japanese game similar to mahjongg
+ *
+ *******************************************************************
+ *
+ * created 1997 by Mario Weilguni <mweilguni@sime.com>
+ *
+ *******************************************************************
+ *
+ * This file is part of the KDE project "KSHISEN"
+ *
+ * KSHISEN 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, or (at your option)
+ * any later version.
+ *
+ * KSHISEN 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 KSHISEN; see the file COPYING. If not, write to
+ * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ *******************************************************************
+ */
+
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+#include <kconfig.h>
+
+#include "version.h"
+#include "app.h"
+
+static const char description[] = I18N_NOOP("A KDE game similiar to Mahjongg");
+
+// A hack to circumvent tricky i18n issue, not used later on in the code.
+// Both context and contents must be exactly the same as for the entry in
+// kdelibs/kdeui/ui_standards.rc
+static const char dummy[] = I18N_NOOP2("Menu title", "&Move");
+
+int main(int argc, char **argv)
+{
+ KAboutData aboutData( "kshisen", I18N_NOOP("Shisen-Sho"),
+ KSHISEN_VERSION, description, KAboutData::License_GPL,
+ "(c) 1997, Mario Weilguni");
+ aboutData.addAuthor("Dave Corrie", I18N_NOOP("Current Maintainer"), "kde@davecorrie.com");
+ aboutData.addAuthor("Mario Weilguni", I18N_NOOP("Original Author"), "mweilguni@sime.com");
+ aboutData.addCredit("Jason Lane", I18N_NOOP("Added 'tiles removed' counter\nTile smooth-scaling and window resizing"), "jglane@btopenworld.com");
+ aboutData.addCredit(0, I18N_NOOP("Thanks also to everyone who should be listed here but isn't!"), 0);
+ KCmdLineArgs::init( argc, argv, &aboutData );
+
+ KApplication a;
+ KGlobal::locale()->insertCatalogue("libkdegames");
+
+ App *app = new App();
+ app->show();
+ a.setMainWidget(app);
+ a.config()->sync();
+ return a.exec();
+}
+
diff --git a/kshisen/pics/Makefile.am b/kshisen/pics/Makefile.am
new file mode 100644
index 00000000..c65bf6fb
--- /dev/null
+++ b/kshisen/pics/Makefile.am
@@ -0,0 +1,5 @@
+
+pics_DATA = tileset.png kshisen_bgnd.png
+picsdir = $(kde_datadir)/kshisen/
+
+EXTRA_DIST = $(pics_DATA)
diff --git a/kshisen/pics/kshisen_bgnd.png b/kshisen/pics/kshisen_bgnd.png
new file mode 100644
index 00000000..d1a7df75
--- /dev/null
+++ b/kshisen/pics/kshisen_bgnd.png
Binary files differ
diff --git a/kshisen/pics/tileset.png b/kshisen/pics/tileset.png
new file mode 100644
index 00000000..b4286a7d
--- /dev/null
+++ b/kshisen/pics/tileset.png
Binary files differ
diff --git a/kshisen/prefs.kcfgc b/kshisen/prefs.kcfgc
new file mode 100644
index 00000000..63feff7f
--- /dev/null
+++ b/kshisen/prefs.kcfgc
@@ -0,0 +1,6 @@
+# Code generation options for kconfig_compiler
+File=kshisen.kcfg
+ClassName=Prefs
+Singleton=true
+#CustomAdditions=true
+Mutators=Gravity,Unscaled
diff --git a/kshisen/settings.ui b/kshisen/settings.ui
new file mode 100644
index 00000000..7f9846a6
--- /dev/null
+++ b/kshisen/settings.ui
@@ -0,0 +1,255 @@
+<!DOCTYPE UI><UI version="3.2" 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>385</width>
+ <height>381</height>
+ </rect>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_Gravity</cstring>
+ </property>
+ <property name="text">
+ <string>Gravity</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_Solvable</cstring>
+ </property>
+ <property name="text">
+ <string>Allow unsolvable games</string>
+ </property>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>difficulty_groupBox</cstring>
+ </property>
+ <property name="title">
+ <string>Board Difficulty</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QSlider" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>kcfg_Level</cstring>
+ </property>
+ <property name="maxValue">
+ <number>2</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="tickmarks">
+ <enum>Below</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="text">
+ <string>Easy</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="1">
+ <property name="name">
+ <cstring>textLabel3</cstring>
+ </property>
+ <property name="text">
+ <string>Hard</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>speed_groupBox</cstring>
+ </property>
+ <property name="title">
+ <string>Piece Removal Speed</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QSlider" row="0" column="0" rowspan="1" colspan="3">
+ <property name="name">
+ <cstring>kcfg_Speed</cstring>
+ </property>
+ <property name="maxValue">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="tickmarks">
+ <enum>Below</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel4</cstring>
+ </property>
+ <property name="text">
+ <string>Slow</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="2">
+ <property name="name">
+ <cstring>textLabel6</cstring>
+ </property>
+ <property name="text">
+ <string>Fast</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>kcfg_Unscaled</cstring>
+ </property>
+ <property name="text">
+ <string>Prefer unscaled tiles</string>
+ </property>
+ </widget>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>size_groupBox</cstring>
+ </property>
+ <property name="title">
+ <string>Tile Size</string>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel" row="1" column="0">
+ <property name="name">
+ <cstring>textLabel8</cstring>
+ </property>
+ <property name="text">
+ <string>14x6</string>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="1">
+ <property name="name">
+ <cstring>textLabel9</cstring>
+ </property>
+ <property name="text">
+ <string>18x8</string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="3">
+ <property name="name">
+ <cstring>textLabel11</cstring>
+ </property>
+ <property name="text">
+ <string>26x14</string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="4">
+ <property name="name">
+ <cstring>textLabel12</cstring>
+ </property>
+ <property name="text">
+ <string>30x16</string>
+ </property>
+ <property name="alignment">
+ <set>AlignVCenter|AlignRight</set>
+ </property>
+ </widget>
+ <widget class="QSlider" row="0" column="0" rowspan="1" colspan="5">
+ <property name="name">
+ <cstring>kcfg_Size</cstring>
+ </property>
+ <property name="minValue">
+ <number>0</number>
+ </property>
+ <property name="maxValue">
+ <number>4</number>
+ </property>
+ <property name="pageStep">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>2</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="tickmarks">
+ <enum>Below</enum>
+ </property>
+ </widget>
+ <widget class="QLabel" row="1" column="2">
+ <property name="name">
+ <cstring>textLabel10</cstring>
+ </property>
+ <property name="text">
+ <string>24x12</string>
+ </property>
+ <property name="alignment">
+ <set>AlignCenter</set>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ <spacer>
+ <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>20</height>
+ </size>
+ </property>
+ </spacer>
+ </vbox>
+</widget>
+<layoutdefaults spacing="6" margin="11"/>
+</UI>
diff --git a/kshisen/tileset.cpp b/kshisen/tileset.cpp
new file mode 100644
index 00000000..f44e2698
--- /dev/null
+++ b/kshisen/tileset.cpp
@@ -0,0 +1,147 @@
+/**
+ * tileset.cpp
+ *
+ * Copyright (c) 2002 Jason Lane <jglane@btopenworld.com>
+ * (c) 2002 Dave Corrie <kde@davecorrie.com>
+ *
+ * This file is part of KShisen.
+ *
+ * KMail 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 <kapplication.h>
+#include <kstandarddirs.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+
+#include <qimage.h>
+
+#include <algorithm>
+
+#include "tileset.h"
+
+TileSet::TileSet() : scaledTiles(nTiles)
+{
+ //loadTiles
+ QImage tileset(KGlobal::dirs()->findResource("appdata", "tileset.png"));
+ if(tileset.isNull())
+ {
+ KMessageBox::sorry(0, i18n("Cannot load tiles pixmap!"));
+ KApplication::exit(1);
+ }
+
+ // split into individual tiles
+ const int TILES_X = 9;
+ const int TILES_Y = 4;
+ unscaledTiles.reserve(nTiles);
+
+ int w = tileset.width() / TILES_X;
+ int h = tileset.height() / TILES_Y;
+ for(int row = 0; row < TILES_Y; row++)
+ {
+ for(int col = 0; col < TILES_X; col++)
+ unscaledTiles.push_back(tileset.copy(col * w, row * h, w, h));
+ }
+}
+
+TileSet::~TileSet()
+{
+}
+
+void TileSet::resizeTiles(int maxWidth, int maxHeight)
+{
+ // calculate largest tile size that will fit in maxWidth/maxHeight
+ // and maintain the tile's height-to-width ratio
+ double ratio = static_cast<double>(unscaledTileHeight()) / unscaledTileWidth();
+ if(maxWidth * ratio < maxHeight)
+ maxHeight = qRound(maxWidth * ratio);
+ else
+ maxWidth = qRound(maxHeight / ratio);
+
+ if(maxHeight == tileHeight() && maxWidth == tileWidth())
+ return;
+
+ //kdDebug() << "tile size: " << maxWidth << "x" << maxHeight << endl;
+
+ QImage img;
+ for(int i = 0; i < nTiles; i++)
+ {
+ if(maxHeight == unscaledTileHeight())
+ img = unscaledTiles[i].copy();//.convertDepth(32);
+ else
+ img = unscaledTiles[i].smoothScale(maxWidth, maxHeight);
+
+ scaledTiles[i].convertFromImage(img);
+ }
+}
+
+const QPixmap &TileSet::tile(int n) const
+{
+ return scaledTiles[n];
+}
+
+QPixmap TileSet::highlightedTile(int n) const
+{
+ const double LIGHTEN_FACTOR = 1.3;
+
+ // lighten the image
+ QImage img = scaledTiles[n].convertToImage().convertDepth(32);
+
+ for(int y = 0; y < img.height(); y++)
+ {
+ uchar* p = img.scanLine(y);
+ for(int x = 0; x < img.width() * 4; x++)
+ {
+ *p = static_cast<uchar>(std::min(255, static_cast<int>(*p * LIGHTEN_FACTOR)));
+ p++;
+ }
+ }
+
+ QPixmap highlightedTile;
+ highlightedTile.convertFromImage(img);
+
+ return highlightedTile;
+}
+
+int TileSet::lineWidth() const
+{
+ int width = qRound(tileHeight() / 10.0);
+ if(width < 3)
+ width = 3;
+
+ return width;
+}
+
+int TileSet:: tileWidth() const
+{
+ return scaledTiles[0].width();
+}
+
+int TileSet:: tileHeight() const
+{
+ return scaledTiles[0].height();
+}
+
+int TileSet:: unscaledTileHeight() const
+{
+ return unscaledTiles[0].height();
+}
+
+int TileSet:: unscaledTileWidth() const
+{
+ return unscaledTiles[0].width();
+}
+
diff --git a/kshisen/tileset.h b/kshisen/tileset.h
new file mode 100644
index 00000000..02905201
--- /dev/null
+++ b/kshisen/tileset.h
@@ -0,0 +1,59 @@
+/**
+ * tileset.h
+ *
+ * Copyright (c) 2002 Jason Lane <jglane@btopenworld.com>
+ * (c) 2002 Dave Corrie <kde@davecorrie.com>
+ *
+ * This file is part of KShisen.
+ *
+ * KMail 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 __IMAGEDATA__H__
+#define __IMAGEDATA__H__
+
+#include <qvaluevector.h>
+
+class TileSet
+{
+
+public:
+
+ static const int nTiles = 36;
+
+ TileSet();
+ ~TileSet();
+
+ void resizeTiles(int maxWidth, int maxHeight);
+
+ const QPixmap &tile(int n) const;
+ QPixmap highlightedTile(int n) const;
+
+ int lineWidth() const;
+
+ int tileWidth() const;
+ int tileHeight() const;
+ int unscaledTileHeight() const;
+ int unscaledTileWidth() const;
+
+private:
+
+ QValueVector<QPixmap> scaledTiles;
+ QValueVector<QImage> unscaledTiles;
+
+};
+
+#endif
+
diff --git a/kshisen/version.h b/kshisen/version.h
new file mode 100644
index 00000000..2921d135
--- /dev/null
+++ b/kshisen/version.h
@@ -0,0 +1,14 @@
+// Explanation of KShisen version numbering system
+//
+// Version numbers are of the form: MAJOR.MINOR.MICRO
+//
+// The MAJOR number should only be incremented when really major
+// changes occur. Such an event is not currently foreseeable.
+//
+// The MINOR number is increased for every branch of KDE from HEAD
+// (i.e. it's increased for the KDE3.1 branch, KDE3.2 branch etc.).
+//
+// The MICRO version is increased for every bug-fix to a branch.
+// MICRO numbers >= 90 are used for development milestones in HEAD.
+
+#define KSHISEN_VERSION "1.5.1"