diff options
author | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
---|---|---|
committer | toma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2009-11-25 17:56:58 +0000 |
commit | c90c389a8a8d9d8661e9772ec4144c5cf2039f23 (patch) | |
tree | 6d8391395bce9eaea4ad78958617edb20c6a7573 /kshisen | |
download | tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.tar.gz tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.zip |
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegames@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kshisen')
-rw-r--r-- | kshisen/ChangeLog | 113 | ||||
-rw-r--r-- | kshisen/Makefile.am | 28 | ||||
-rw-r--r-- | kshisen/app.cpp | 760 | ||||
-rw-r--r-- | kshisen/app.h | 122 | ||||
-rw-r--r-- | kshisen/board.cpp | 1082 | ||||
-rw-r--r-- | kshisen/board.h | 189 | ||||
-rw-r--r-- | kshisen/debug.h | 1 | ||||
-rw-r--r-- | kshisen/hi128-app-kshisen.png | bin | 0 -> 12289 bytes | |||
-rw-r--r-- | kshisen/hi16-app-kshisen.png | bin | 0 -> 721 bytes | |||
-rw-r--r-- | kshisen/hi22-app-kshisen.png | bin | 0 -> 3825 bytes | |||
-rw-r--r-- | kshisen/hi32-app-kshisen.png | bin | 0 -> 1967 bytes | |||
-rw-r--r-- | kshisen/hi48-app-kshisen.png | bin | 0 -> 3576 bytes | |||
-rw-r--r-- | kshisen/hi64-app-kshisen.png | bin | 0 -> 5039 bytes | |||
-rw-r--r-- | kshisen/kshisen.desktop | 76 | ||||
-rw-r--r-- | kshisen/kshisen.kcfg | 35 | ||||
-rw-r--r-- | kshisen/kshisenui.rc | 16 | ||||
-rw-r--r-- | kshisen/main.cpp | 74 | ||||
-rw-r--r-- | kshisen/pics/Makefile.am | 5 | ||||
-rw-r--r-- | kshisen/pics/kshisen_bgnd.png | bin | 0 -> 82905 bytes | |||
-rw-r--r-- | kshisen/pics/tileset.png | bin | 0 -> 14708 bytes | |||
-rw-r--r-- | kshisen/prefs.kcfgc | 6 | ||||
-rw-r--r-- | kshisen/settings.ui | 255 | ||||
-rw-r--r-- | kshisen/tileset.cpp | 147 | ||||
-rw-r--r-- | kshisen/tileset.h | 59 | ||||
-rw-r--r-- | kshisen/version.h | 14 |
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 <[email protected]> (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 <[email protected]> (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 <[email protected]> (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 <[email protected]> (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 <[email protected]> (v.1.2.2) + + * fixed another bug in writeHighscore + +1999-04-04 Mario Weilguni <[email protected]> (v.1.2.1) + + * replace all locale->translate with i18n + * fixed a bug in the writeHighscore method + +1999-03-31 Mario Weilguni <[email protected]> (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 <[email protected]> (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 <[email protected]> + + * moved to version 1.0 (it�s stable enough now) + +1998-04-10 Mario Weilguni <[email protected]> + + * better highscore management. highscores are now score-based, not + time based + * board.cpp: fixed some bugs + * the game doesn�t 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 <[email protected]>) + + fixed a bug in the Board::lighten() function on 8-bit + displays. Reported by Marc Diefenbruch + + 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 <[email protected]> + * + ******************************************************************* + * + * 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 <[email protected]> + * + ******************************************************************* + * + * 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 <[email protected]> + * + ******************************************************************* + * + * 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 <[email protected]> + * + ******************************************************************* + * + * 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 Binary files differnew file mode 100644 index 00000000..8badc8f9 --- /dev/null +++ b/kshisen/hi128-app-kshisen.png diff --git a/kshisen/hi16-app-kshisen.png b/kshisen/hi16-app-kshisen.png Binary files differnew file mode 100644 index 00000000..9077c7c8 --- /dev/null +++ b/kshisen/hi16-app-kshisen.png diff --git a/kshisen/hi22-app-kshisen.png b/kshisen/hi22-app-kshisen.png Binary files differnew file mode 100644 index 00000000..237b1a66 --- /dev/null +++ b/kshisen/hi22-app-kshisen.png diff --git a/kshisen/hi32-app-kshisen.png b/kshisen/hi32-app-kshisen.png Binary files differnew file mode 100644 index 00000000..7f7ac9d9 --- /dev/null +++ b/kshisen/hi32-app-kshisen.png diff --git a/kshisen/hi48-app-kshisen.png b/kshisen/hi48-app-kshisen.png Binary files differnew file mode 100644 index 00000000..e67de754 --- /dev/null +++ b/kshisen/hi48-app-kshisen.png diff --git a/kshisen/hi64-app-kshisen.png b/kshisen/hi64-app-kshisen.png Binary files differnew file mode 100644 index 00000000..7ec56314 --- /dev/null +++ b/kshisen/hi64-app-kshisen.png 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 <[email protected]> + * + ******************************************************************* + * + * 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"), "[email protected]"); + aboutData.addAuthor("Mario Weilguni", I18N_NOOP("Original Author"), "[email protected]"); + aboutData.addCredit("Jason Lane", I18N_NOOP("Added 'tiles removed' counter\nTile smooth-scaling and window resizing"), "[email protected]"); + 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 Binary files differnew file mode 100644 index 00000000..d1a7df75 --- /dev/null +++ b/kshisen/pics/kshisen_bgnd.png diff --git a/kshisen/pics/tileset.png b/kshisen/pics/tileset.png Binary files differnew file mode 100644 index 00000000..b4286a7d --- /dev/null +++ b/kshisen/pics/tileset.png 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 <[email protected]> + * (c) 2002 Dave Corrie <[email protected]> + * + * 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 <[email protected]> + * (c) 2002 Dave Corrie <[email protected]> + * + * 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" |