You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kshisen/app.cpp

761 lines
18 KiB

/* 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 <tqlayout.h>
#include <tqtimer.h>
#include <tqlineedit.h>
#include <cmath>
#include "app.h"
#include "prefs.h"
#include "settings.h"
App::App(TQWidget *parent, const char *name) : KMainWindow(parent, name),
cheat(false)
{
highscoreTable = new KHighscore(TQT_TQOBJECT(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, 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::initKAction()
{
// 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 KAction(i18n("Is Game Solvable?"), 0, this,
// TQT_SLOT(isSolvable()), actionCollection(), "move_solvable");
#ifdef DEBUGGING
(void)new KAction(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();
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())
{
TQSize 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
{
TQString s = i18n("Congratulations! You made it in %1:%2:%3")
.tqarg(TQString().sprintf("%02d", board->getTimeForGame()/3600))
.tqarg(TQString().sprintf("%02d", (board->getTimeForGame() / 60) % 60))
.tqarg(TQString().sprintf("%02d", board->getTimeForGame() % 60));
KMessageBox::information(this, s, i18n("End of Game"));
}
}
resetCheatMode();
board->newGame();
}
void App::updateScore()
{
int t = board->getTimeForGame();
TQString s = i18n(" Your time: %1:%2:%3 %4")
.tqarg(TQString().sprintf("%02d", t / 3600 ))
.tqarg(TQString().sprintf("%02d", (t / 60) % 60 ))
.tqarg(TQString().sprintf("%02d", t % 60 ))
.tqarg(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 ")
.tqarg(TQString().sprintf("%d", tl - board->tilesLeft()))
.tqarg(TQString().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);
}
}
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->tqsizeHint());
TQLabel *l2 = new TQLabel(i18n("Your name:"), dlg);
l2->setFixedSize(l2->tqsizeHint());
TQLineEdit *e = new TQLineEdit(dlg);
e->setText("XXXXXXXXXXXXXXXX");
e->setMinimumWidth(e->tqsizeHint().width());
e->setFixedHeight(e->tqsizeHint().height());
e->setText( lastPlayerName );
e->setFocus();
TQPushButton *b = new KPushButton(KStdGuiItem::ok(), dlg);
b->setDefault(true);
b->setFixedSize(b->tqsizeHint());
connect(b, TQT_SIGNAL(released()), dlg, TQT_SLOT(accept()));
connect(e, TQT_SIGNAL(returnPressed()), dlg, TQT_SLOT(accept()));
// create tqlayout
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;
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;
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->tqsizeHint());
l->setFixedWidth(l->width() + 32);
l->tqsetAlignment(AlignCenter);
tl->addWidget(l);
// insert highscores in a gridtqlayout
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->tqsizeHint());
table->addWidget(l, 0, 0);
l = new TQLabel(i18n("Name"), dlg);
l->setFont(f);
l->setMinimumSize(l->tqsizeHint());
table->addWidget(l, 0, 1);
l = new TQLabel(i18n("Time"), dlg);
l->setFont(f);
l->setMinimumSize(l->tqsizeHint());
table->addWidget(l, 0, 2);
l = new TQLabel(i18n("Size"), dlg);
l->setFont(f);
l->setMinimumSize(l->tqsizeHint());
table->addWidget(l, 0, 3);
l = new TQLabel(i18n("Score"), dlg);
l->setFont(f);
l->setMinimumSize(l->tqsizeHint().width()*3, l->tqsizeHint().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")
.tqarg(getScore(hs))
.tqarg(hs.gravity ? i18n("(gravity)") : TQString(""));
}
else
{
s = "";
}
e[i][4] = new TQLabel(s, dlg);
e[i][4]->tqsetAlignment(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]->tqsizeHint().height());
if(j == 1)
e[i][j]->setMinimumWidth(std::max(e[i][j]->tqsizeHint().width(), 100));
else
e[i][j]->setMinimumWidth(std::max(e[i][j]->tqsizeHint().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->tqsizeHint());
// connect the "Close"-button to done
connect(b, TQT_SIGNAL(clicked()), dlg, TQT_SLOT(accept()));
b->setDefault(true);
b->setFocus();
// make tqlayout
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, TQT_SIGNAL(settingsChanged()), TQT_TQOBJECT(this), TQT_SLOT(loadSettings()));
connect(dialog, TQT_SIGNAL(settingsChanged()), board, TQT_SLOT(loadSettings()));
dialog->show();
}
#include "app.moc"