/* Yo Emacs, this is -*- C++ -*- ******************************************************************* ******************************************************************* * * * KSHISEN * * ******************************************************************* * * A japanese game similar to mahjongg * ******************************************************************* * * created 1997 by Mario Weilguni * ******************************************************************* * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "app.h" #include "prefs.h" #include "settings.h" App::App(TQWidget *parent, const char *name) : TDEMainWindow(parent, name), hintmode(false) { highscoreTable = new KHighscore(TQT_TQOBJECT(this)); // TODO? // Would it make sense long term to have a tdeconfig 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(" Hint mode "), SBI_HINT); statusBar()->changeItem("", SBI_HINT); initTDEAction(); board = new Board(this, "board"); loadSettings(); setCentralWidget(board); setupGUI(); connect(board, TQT_SIGNAL(changed()), TQT_TQOBJECT(this), TQT_SLOT(enableItems())); TQTimer *t = new TQTimer(this); t->start(1000); connect(t, TQT_SIGNAL(timeout()), TQT_TQOBJECT(this), TQT_SLOT(updateScore())); connect(board, TQT_SIGNAL(endOfGame()), TQT_TQOBJECT(this), TQT_SLOT(slotEndOfGame())); connect(board, TQT_SIGNAL(resized()), TQT_TQOBJECT(this), TQT_SLOT(boardResized())); kapp->processEvents(); updateScore(); enableItems(); } void App::initTDEAction() { // Game KStdGameAction::gameNew(TQT_TQOBJECT(this), TQT_SLOT(newGame()), actionCollection()); KStdGameAction::restart(TQT_TQOBJECT(this), TQT_SLOT(restartGame()), actionCollection()); KStdGameAction::pause(TQT_TQOBJECT(this), TQT_SLOT(pause()), actionCollection()); KStdGameAction::highscores(TQT_TQOBJECT(this), TQT_SLOT(hallOfFame()), actionCollection()); KStdGameAction::quit(TQT_TQOBJECT(this), TQT_SLOT(quitGame()), actionCollection()); // Move KStdGameAction::undo(TQT_TQOBJECT(this), TQT_SLOT(undo()), actionCollection()); KStdGameAction::redo(TQT_TQOBJECT(this), TQT_SLOT(redo()), actionCollection()); KStdGameAction::hint(TQT_TQOBJECT(this), TQT_SLOT(hint()), actionCollection()); //new TDEAction(i18n("Is Game Solvable?"), 0, this, // TQT_SLOT(isSolvable()), actionCollection(), "move_solvable"); #ifdef DEBUGGING (void)new TDEAction(i18n("&Finish"), 0, board, TQT_SLOT(finish()), actionCollection(), "move_finish"); #endif // Settings KStdAction::preferences(TQT_TQOBJECT(this), TQT_SLOT(showSettings()), actionCollection()); } void App::hallOfFame() { showHighscore(); } void App::newGame() { board->newGame(); resetHintMode(); 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(); setHintMode(); enableItems(); } } void App::redo() { if(board->canRedo()) board->redo(); enableItems(); } void App::hint() { #ifdef DEBUGGING board->makeHintMove(); #else board->showHint(); setHintMode(); #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()) { TQSize s = board->unscaledSize(); // We would have liked to have used TDEMainWindow::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. TDEPopupMenu* help = dynamic_cast(child("help", "TDEPopupMenu", false)); TDEActionPtrList actions = actionCollection()->actions(); TDEActionPtrList::iterator actionIter = actions.begin(); TDEActionPtrList::iterator actionIterEnd = actions.end(); while(actionIter != actionIterEnd) { TDEAction* 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 && !hintmode) { hs.name = getPlayerName(); hs.date = time((time_t*)0); int rank = insertHighscore(hs); showHighscore(rank); } else { TQString s = i18n("Congratulations! You made it in %1:%2:%3") .arg(TQString().sprintf("%02d", board->getTimeForGame()/3600)) .arg(TQString().sprintf("%02d", (board->getTimeForGame() / 60) % 60)) .arg(TQString().sprintf("%02d", board->getTimeForGame() % 60)); KMessageBox::information(this, s, i18n("End of Game")); } } resetHintMode(); board->newGame(); } void App::updateScore() { int t = board->getTimeForGame(); TQString s = i18n(" Your time: %1:%2:%3 %4") .arg(TQString().sprintf("%02d", t / 3600 )) .arg(TQString().sprintf("%02d", (t / 60) % 60 )) .arg(TQString().sprintf("%02d", t % 60 )) .arg(board->isPaused()?i18n("(Paused) "):TQString()); statusBar()->changeItem(s, SBI_TIME); // Number of tiles int tl = (board->x_tiles() * board->y_tiles()); s = i18n(" Removed: %1/%2 ") .arg(TQString().sprintf("%d", tl - board->tilesLeft())) .arg(TQString().sprintf("%d", tl )); statusBar()->changeItem(s, SBI_TILES); } void App::setHintMode() { // set the hintmode mode if not set if(!hintmode) { hintmode = true; statusBar()->changeItem(i18n(" Hint mode "), SBI_HINT); } } void App::resetHintMode() { // reset hintmode mode if set if(hintmode) { hintmode = false; statusBar()->changeItem("", SBI_HINT); } } TQString App::getPlayerName() { TQDialog *dlg = new TQDialog(this, "Hall of Fame", true); TQLabel *l1 = new TQLabel(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()); TQLabel *l2 = new TQLabel(i18n("Your name:"), dlg); l2->setFixedSize(l2->sizeHint()); TQLineEdit *e = new TQLineEdit(dlg); e->setText("XXXXXXXXXXXXXXXX"); e->setMinimumWidth(e->sizeHint().width()); e->setFixedHeight(e->sizeHint().height()); e->setText( lastPlayerName ); e->setFocus(); TQPushButton *b = new KPushButton(KStdGuiItem::ok(), dlg); b->setDefault(true); b->setFixedSize(b->sizeHint()); connect(b, TQT_SIGNAL(released()), dlg, TQT_SLOT(accept())); connect(e, TQT_SIGNAL(returnPressed()), dlg, TQT_SLOT(accept())); // create layout TQVBoxLayout *tl = new TQVBoxLayout(dlg, 10); TQHBoxLayout *tl1 = new TQHBoxLayout(); 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() { TQStringList 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; TQString s, e, grp; TDEConfig *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; TQStringList 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; TQStringList 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(TQString::number(hs.x)); hi_y.append(TQString::number(hs.y)); hi_sec.append(TQString::number(hs.seconds)); hi_date.append(TQString::number(hs.date)); hi_grav.append(TQString::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... TQDialog *dlg = new TQDialog(0, "hall_Of_fame", true); dlg->setCaption(i18n("Hall of Fame")); TQVBoxLayout *tl = new TQVBoxLayout(dlg, 10); TQLabel *l = new TQLabel(i18n("Hall of Fame"), dlg); TQFont 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 TQGridLayout *table = new TQGridLayout(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 TQLabel(i18n("Rank"), dlg); l->setFont(f); l->setMinimumSize(l->sizeHint()); table->addWidget(l, 0, 0); l = new TQLabel(i18n("Name"), dlg); l->setFont(f); l->setMinimumSize(l->sizeHint()); table->addWidget(l, 0, 1); l = new TQLabel(i18n("Time"), dlg); l->setFont(f); l->setMinimumSize(l->sizeHint()); table->addWidget(l, 0, 2); l = new TQLabel(i18n("Size"), dlg); l->setFont(f); l->setMinimumSize(l->sizeHint()); table->addWidget(l, 0, 3); l = new TQLabel(i18n("Score"), dlg); l->setFont(f); l->setMinimumSize(l->sizeHint().width()*3, l->sizeHint().height()); table->addWidget(l, 0, 4); TQString s; TQLabel *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 TQLabel(s, dlg); // insert name if(i < highscore.size()) e[i][1] = new TQLabel(hs.name, dlg); else e[i][1] = new TQLabel("", dlg); // insert time TQTime 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 TQLabel(s, dlg); } else { e[i][2] = new TQLabel("", dlg); } // insert size if(i < highscore.size()) s.sprintf("%d x %d", hs.x, hs.y); else s = ""; e[i][3] = new TQLabel(s, dlg); // insert score if(i < highscore.size()) { s = TQString("%1 %2") .arg(getScore(hs)) .arg(hs.gravity ? i18n("(gravity)") : TQString("")); } else { s = ""; } e[i][4] = new TQLabel(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); } } TQPushButton *b = new KPushButton(KStdGuiItem::close(), dlg); b->setFixedSize(b->sizeHint()); // connect the "Close"-button to done connect(b, TQT_SIGNAL(clicked()), dlg, TQT_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(TDEConfigDialog::showDialog("settings")) return; TDEConfigDialog *dialog = new TDEConfigDialog(this, "settings", Prefs::self(), KDialogBase::Swallow); Settings *general = new Settings(0, "General"); dialog->addPage(general, i18n("General"), "package_settings"); connect(dialog, TQT_SIGNAL(settingsChanged()), TQT_TQOBJECT(this), TQT_SLOT(loadSettings())); connect(dialog, TQT_SIGNAL(settingsChanged()), board, TQT_SLOT(loadSettings())); dialog->show(); } #include "app.moc"