/* $Id$ kmahjongg, the classic mahjongg game for TDE project Requires the TQt widget libraries, available at no cost at http://www.troll.no Copyright (C) 1997 Mathias Mueller This program 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 #include #include #include #include #include #include #include #include #include "prefs.h" #include "kmahjongg.h" #include "settings.h" #include "GameTimer.h" #include "Editor.h" static const char *gameMagic = "kmahjongg-game-v1.0"; //---------------------------------------------------------- // Defines //---------------------------------------------------------- #define ID_STATUS_TILENUMBER 1 #define ID_STATUS_MESSAGE 2 #define ID_STATUS_GAME 3 int is_paused = 0; /** Constructor. */ KMahjongg::KMahjongg( TQWidget* parent, const char *name) : TDEMainWindow(parent, name) { boardEditor = 0; // init board widget bw = new BoardWidget( this ); setCentralWidget( bw ); previewLoad = new Preview(this); setupStatusBar(); setupTDEAction(); gameTimer = new GameTimer(toolBar()); toolBar()->insertWidget(ID_GAME_TIMER, gameTimer->width() , gameTimer); toolBar()->alignItemRight( ID_GAME_TIMER, true ); theHighScores = new HighScore(this); bDemoModeActive = false; connect( bw, TQT_SIGNAL( statusTextChanged(const TQString&, long) ), TQT_SLOT( showStatusText(const TQString&, long) ) ); connect( bw, TQT_SIGNAL( tileNumberChanged(int,int,int) ), TQT_SLOT( showTileNumber(int,int,int) ) ); connect( bw, TQT_SIGNAL( demoModeChanged(bool) ), TQT_SLOT( demoModeChanged(bool) ) ); connect( bw, TQT_SIGNAL( gameOver(unsigned short , unsigned short)), this, TQT_SLOT( gameOver(unsigned short , unsigned short))); connect(bw, TQT_SIGNAL(gameCalculated()), this, TQT_SLOT(timerReset())); // Make connections for the preview load dialog connect( previewLoad, TQT_SIGNAL( boardRedraw(bool) ), bw, TQT_SLOT( drawBoard(bool) ) ); connect( previewLoad, TQT_SIGNAL( layoutChange() ), this, TQT_SLOT( newGame() ) ); connect( previewLoad, TQT_SIGNAL( loadBackground(const TQString&, bool) ), bw, TQT_SLOT(loadBackground(const TQString&, bool) ) ); connect( previewLoad, TQT_SIGNAL( loadTileset(const TQString &) ), bw, TQT_SLOT(loadTileset(const TQString&) ) ); connect( previewLoad, TQT_SIGNAL( loadBoard(const TQString&) ), TQT_SLOT(loadBoardLayout(const TQString&) ) ); startNewGame( ); } // --------------------------------------------------------- KMahjongg::~KMahjongg() { delete previewLoad; delete theHighScores; delete bw; } // --------------------------------------------------------- void KMahjongg::setupTDEAction() { // game KStdGameAction::gameNew(TQT_TQOBJECT(this), TQT_SLOT(newGame()), actionCollection()); KStdGameAction::load(TQT_TQOBJECT(this), TQT_SLOT(loadGame()), actionCollection()); KStdGameAction::save(TQT_TQOBJECT(this), TQT_SLOT(saveGame()), actionCollection()); KStdGameAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), actionCollection()); KStdGameAction::restart(TQT_TQOBJECT(this), TQT_SLOT(restartGame()), actionCollection()); new TDEAction(i18n("New Numbered Game..."), "newnum", 0, TQT_TQOBJECT(this), TQT_SLOT(startNewNumeric()), actionCollection(), "game_new_numeric"); new TDEAction(i18n("Open Th&eme..."), 0, TQT_TQOBJECT(this), TQT_SLOT(openTheme()), actionCollection(), "game_open_theme"); new TDEAction(i18n("Open &Tileset..."), 0, TQT_TQOBJECT(this), TQT_SLOT(openTileset()), actionCollection(), "game_open_tileset"); new TDEAction(i18n("Open &Background..."), 0, TQT_TQOBJECT(this), TQT_SLOT(openBackground()), actionCollection(), "game_open_background"); new TDEAction(i18n("Open La&yout..."), 0, TQT_TQOBJECT(this), TQT_SLOT(openLayout()), actionCollection(), "game_open_layout"); new TDEAction(i18n("Sa&ve Theme..."), 0, TQT_TQOBJECT(this), TQT_SLOT(saveTheme()), actionCollection(), "game_save_theme"); // originally "file" ends here KStdGameAction::hint(TQT_TQOBJECT(bw), TQT_SLOT(helpMove()), actionCollection()); new TDEAction(i18n("Shu&ffle"), "reload", 0, TQT_TQOBJECT(bw), TQT_SLOT(shuffle()), actionCollection(), "move_shuffle"); demoAction = KStdGameAction::demo(TQT_TQOBJECT(this), TQT_SLOT(demoMode()), actionCollection()); showMatchingTilesAction = new TDEToggleAction(i18n("Show &Matching Tiles"), 0, TQT_TQOBJECT(this), TQT_SLOT(showMatchingTiles()), actionCollection(), "options_show_matching_tiles"); showMatchingTilesAction->setCheckedState(i18n("Hide &Matching Tiles")); showMatchingTilesAction->setChecked(Prefs::showMatchingTiles()); bw->setShowMatch( Prefs::showMatchingTiles() ); KStdGameAction::highscores(TQT_TQOBJECT(this), TQT_SLOT(showHighscores()), actionCollection()); pauseAction = KStdGameAction::pause(TQT_TQOBJECT(this), TQT_SLOT(pause()), actionCollection()); // TODO: store the background ; open on startup // TODO: same about layout // TODO: same about theme // move undoAction = KStdGameAction::undo(TQT_TQOBJECT(this), TQT_SLOT(undo()), actionCollection()); redoAction = KStdGameAction::redo(TQT_TQOBJECT(this), TQT_SLOT(redo()), actionCollection()); // edit new TDEAction(i18n("&Board Editor"), 0, TQT_TQOBJECT(this), TQT_SLOT(slotBoardEditor()), actionCollection(), "edit_board_editor"); // settings KStdAction::preferences(TQT_TQOBJECT(this), TQT_SLOT(showSettings()), actionCollection()); setupGUI(); } // --------------------------------------------------------- void KMahjongg::setupStatusBar() { // The following isn't possible with the new KStatusBar anymore. // The correct fix is probably to reverse the order of adding the // widgets. :/ // Just commenting it out for now (order is not as important // as compilation), in case someone comes up with a better fix. // pStatusBar->setInsertOrder( KStatusBar::RightToLeft ); tilesLeftLabel= new TQLabel("Removed: 0000/0000", statusBar()); tilesLeftLabel->setFrameStyle( TQFrame::Panel | TQFrame::Sunken ); statusBar()->addWidget(tilesLeftLabel, tilesLeftLabel->sizeHint().width(), ID_STATUS_GAME); gameNumLabel = new TQLabel("Game: 000000000000000000000", statusBar()); gameNumLabel->setFrameStyle( TQFrame::Panel | TQFrame::Sunken ); statusBar()->addWidget(gameNumLabel, gameNumLabel->sizeHint().width(), ID_STATUS_TILENUMBER); statusLabel= new TQLabel("Kmahjongg", statusBar()); statusLabel->setFrameStyle( TQFrame::Panel | TQFrame::Sunken ); statusBar()->addWidget(statusLabel, statusLabel->sizeHint().width(), ID_STATUS_MESSAGE); // pStatusBar->setAlignment( ID_STATUS_TILENUMBER, AlignCenter ); } void KMahjongg::setDisplayedWidth() { bw->setDisplayedWidth(); /* setFixedSize( bw->size() + TQSize( 2, (!statusBar()->isHidden() ? statusBar()->height() : 0) + 2 + menuBar()->height() ) ); toolBar()->setFixedWidth(bw->width());*/ toolBar()->alignItemRight( ID_GAME_TIMER, true ); bw->drawBoard(); } // --------------------------------------------------------- void KMahjongg::startNewNumeric() { bool ok; int s = KInputDialog::getInteger(i18n("New Game"),i18n("Enter game number:"),0,0,INT_MAX,1,&ok,this); if (ok) startNewGame(s); } void KMahjongg::undo() { bw->Game.allow_redo += bw->undoMove(); demoModeChanged(false); } void KMahjongg::redo() { if (bw->Game.allow_redo >0) { bw->Game.allow_redo--; bw->redoMove(); demoModeChanged(false); } } /** * Show Configure dialog. */ void KMahjongg::showSettings(){ if(TDEConfigDialog::showDialog("settings")) return; TDEConfigDialog *dialog = new TDEConfigDialog(this, "settings", Prefs::self(), KDialogBase::Swallow); dialog->addPage(new Settings(0, "General"), i18n("General"), "package_settings"); connect(dialog, TQT_SIGNAL(settingsChanged()), bw, TQT_SLOT(loadSettings())); connect(dialog, TQT_SIGNAL(settingsChanged()), TQT_TQOBJECT(this), TQT_SLOT(setDisplayedWidth())); dialog->show(); } void KMahjongg::demoMode() { if( bDemoModeActive ) { bw->stopDemoMode(); } else { // we assume demo mode removes tiles so we can // disbale redo here. bw->Game.allow_redo=false; bw->startDemoMode(); } } void KMahjongg::pause() { is_paused = !is_paused; demoModeChanged(false); gameTimer->pause(); bw->pause(); } void KMahjongg::showMatchingTiles() { Prefs::setShowMatchingTiles(!Prefs::showMatchingTiles()); bw->setShowMatch( Prefs::showMatchingTiles() ); showMatchingTilesAction->setChecked(Prefs::showMatchingTiles()); Prefs::writeConfig(); } void KMahjongg::showHighscores() { theHighScores->exec(bw->getLayoutName()); } void KMahjongg::openTheme() { previewLoad->initialise(Preview::theme); previewLoad->exec(); } void KMahjongg::saveTheme() { previewLoad->initialise(Preview::theme); previewLoad->saveTheme(); } void KMahjongg::openLayout() { previewLoad->initialise(Preview::board); previewLoad->exec(); } void KMahjongg::openBackground() { previewLoad->initialise(Preview::background); previewLoad->exec(); } void KMahjongg::openTileset() { previewLoad->initialise(Preview::tileset); previewLoad->exec(); } void KMahjongg::slotBoardEditor() { if (!boardEditor) boardEditor = new Editor(this); boardEditor->exec(); } //---------------------------------------------------------- // signalled from the prieview dialog to generate a new game // we don't make startNewGame a slot because it has a default // param. void KMahjongg::newGame() { startNewGame(); } // --------------------------------------------------------- void KMahjongg::startNewGame( int item ) { if( ! bDemoModeActive ) { bw->calculateNewGame(item); // initialise button states bw->Game.allow_redo = bw->Game.allow_undo = 0; timerReset(); // update the initial enabled/disabled state for // the menu and the tool bar. demoModeChanged(false); } } // --------------------------------------------------------- void KMahjongg::timerReset() { // initialise the scoring system gameElapsedTime = 0; // start the game timer gameTimer->start(); } // --------------------------------------------------------- void KMahjongg::gameOver( unsigned short numRemoved, unsigned short cheats) { int time; int score; gameTimer->pause(); long gameNum = bw->getGameNum(); KMessageBox::information(this, i18n("You have won!")); bw->animateMoveList(); int elapsed = gameTimer->toInt(); time = score = 0; // get the time in milli secs // subtract from 20 minutes to get bonus. if longer than 20 then ignore time = (60*20) - gameTimer->toInt(); if (time <0) time =0; // conv back to secs (max bonus = 60*20 = 1200 // points per removed tile bonus (for deragon max = 144*10 = 1440 score += (numRemoved * 20); // time bonus one point per second under one hour score += time; // points per cheat penalty (max penalty = 1440 for dragon) score -= (cheats *20); if (score < 0) score = 0; theHighScores->checkHighScore(score, elapsed, gameNum, bw->getBoardName()); timerReset(); } // --------------------------------------------------------- void KMahjongg::showStatusText( const TQString &msg, long board ) { statusLabel->setText(msg); TQString str = i18n("Game number: %1").arg(board); gameNumLabel->setText(str); } // --------------------------------------------------------- void KMahjongg::showTileNumber( int iMaximum, int iCurrent, int iLeft ) { // Hmm... seems iCurrent is the number of remaining tiles, not removed ... //TQString szBuffer = i18n("Removed: %1/%2").arg(iCurrent).arg(iMaximum); TQString szBuffer = i18n("Removed: %1/%2 Combinations left: %3").arg(iMaximum-iCurrent).arg(iMaximum).arg(iLeft); tilesLeftLabel->setText(szBuffer); // Update here since undo allow is effected by demo mode // removal. However we only change the enabled state of the // items when not in demo mode bw->Game.allow_undo = iMaximum != iCurrent; // update undo menu item, if demomode is inactive if( ! bDemoModeActive && !is_paused) { // pMenuBar->setItemEnabled( ID_EDIT_UNDO, bw->Game.allow_undo); // toolBar->setItemEnabled( ID_EDIT_UNDO, bw->Game.allow_undo); undoAction->setEnabled(bw->Game.allow_undo); } } // --------------------------------------------------------- void KMahjongg::demoModeChanged( bool bActive) { bDemoModeActive = bActive; pauseAction->setChecked(is_paused); demoAction->setChecked(bActive || is_paused); if (is_paused) stateChanged("paused"); else if (bActive) stateChanged("active"); else { stateChanged("inactive"); undoAction->setEnabled(bw->Game.allow_undo); redoAction->setEnabled(bw->Game.allow_redo); } } void KMahjongg::loadBoardLayout(const TQString &file) { bw->loadBoardLayout(file); } void KMahjongg::tileSizeChanged() { bw->tileSizeChanged(); setDisplayedWidth(); } void KMahjongg::loadGame() { GAMEDATA in; char buffer[1024]; TQString fname; // Get the name of the file to load KURL url = KFileDialog::getOpenURL( NULL, "*.kmgame", this, i18n("Load Game" ) ); if ( url.isEmpty() ) return; TDEIO::NetAccess::download( url, fname, this ); // open the file for reading FILE *outFile = fopen( TQFile::encodeName(fname), "r"); if (outFile == NULL) { KMessageBox::sorry(this, i18n("Could not read from file. Aborting.")); return; } // verify the magic fscanf(outFile, "%1023s\n", buffer); if (strcmp(buffer, gameMagic) != 0) { KMessageBox::sorry(this, i18n("File format not recognized.")); fclose(outFile); return; } //ed the elapsed time fscanf(outFile, "%1023s\n", buffer); gameTimer->fromString(buffer); // suck out all the game data fread(&in, sizeof(GAMEDATA), 1, outFile); memcpy(&bw->Game, &in, sizeof(GAMEDATA)); // close the file before exit fclose(outFile); TDEIO::NetAccess::removeTempFile( fname ); // refresh the board bw->gameLoaded(); } void KMahjongg::restartGame() { if( ! bDemoModeActive ) { bw->calculateNewGame(bw->getGameNum()); // initialise button states bw->Game.allow_redo = bw->Game.allow_undo = 0; timerReset(); // update the initial enabled/disabled state for // the menu and the tool bar. demoModeChanged(false); if (is_paused) { pauseAction->setChecked(false); is_paused = false; bw->pause(); } } } void KMahjongg::saveGame() { // Get the name of the file to save KURL url = KFileDialog::getSaveURL( NULL, "*.kmgame", this, i18n("Save Game" ) ); if ( url.isEmpty() ) return; if( !url.isLocalFile() ) { KMessageBox::sorry( this, i18n( "Only saving to local files currently supported." ) ); return; } FILE *outFile = fopen( TQFile::encodeName(url.path()), "w"); if (outFile == NULL) { KMessageBox::sorry(this, i18n("Could not write to file. Aborting.")); return; } // stick in the magic id string fprintf(outFile, "%s\n", gameMagic); // Now stick in the elapsed time for the game fprintf(outFile, "%s\n", gameTimer->toString().utf8().data()); // chuck in all the game data fwrite(&bw->Game, sizeof(GAMEDATA), 1, outFile); // close the file before exit fclose(outFile); } #include "kmahjongg.moc"