summaryrefslogtreecommitdiffstats
path: root/kshisen/app.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kshisen/app.cpp')
-rw-r--r--kshisen/app.cpp760
1 files changed, 760 insertions, 0 deletions
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"