/* Yo Emacs, this -*- C++ -*- Copyright (C) 1999-2001 Jens Hoefkens jens@hoefkens.com 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. $Id$ */ #include "kbgoffline.moc" #include "kbgoffline.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "version.h" class KBgEngineOfflinePrivate { public: /* * Various flags, representing the current status of the game */ bool mRollFlag, mUndoFlag, mDoneFlag, mCubeFlag, mGameFlag, mRedoFlag; /* * Store two copies of the game: one backup and a working copy */ KBgStatus mGame[2]; /* * Use the standard method of obtaining random numbers */ KRandomSequence *mRandom; /* * Game actions */ KAction *mNew, *mSwap; KToggleAction *mEdit; /* * Player's names */ TQString mName[2]; /* * Who did the last roll */ int mRoll; /* * How many checkers to move */ int mMove; /* * Count the number of available undos */ int mUndo; /* * Entry fields for the names */ TQLineEdit *mLe[2]; }; // == constructor, destructor and other ======================================== /* * Constructor */ KBgEngineOffline::KBgEngineOffline(TQWidget *parent, TQString *name, TQPopupMenu *pmenu) : KBgEngine(parent, name, pmenu) { d = new KBgEngineOfflinePrivate(); /* * get some entropy for the dice */ d->mRandom = new KRandomSequence; d->mRandom->setSeed(0); /* * Create engine specific actions */ d->mNew = new KAction(i18n("&New Game..."), 0, this, TQT_SLOT(newGame()), this); d->mSwap = new KAction(i18n("&Swap Colors"), 0, this, TQT_SLOT(swapColors()), this); d->mEdit = new KToggleAction(i18n("&Edit Mode"), 0, this, TQT_SLOT(toggleEditMode()), this); d->mEdit->setChecked(false); /* * create & initialize the menu */ d->mNew->plug(menu); d->mEdit->plug(menu); d->mSwap->plug(menu); /* * get standard board and set it */ initGame(); emit newState(d->mGame[0]); /* * initialize the commit timeout */ ct = new TQTimer(this); connect(ct, TQT_SIGNAL(timeout()), this, TQT_SLOT(done())); /* * internal statue variables */ d->mRollFlag = d->mUndoFlag = d->mGameFlag = d->mDoneFlag = false; connect(this, TQT_SIGNAL(allowCommand(int, bool)), this, TQT_SLOT(setAllowed(int, bool))); /* * Restore last stored settings */ readConfig(); } /* * Destructor. The only child is the popup menu. */ KBgEngineOffline::~KBgEngineOffline() { saveConfig(); delete d->mRandom; delete d; } // == configuration handling =================================================== /* * Put the engine specific details in the setup dialog */ void KBgEngineOffline::getSetupPages(KDialogBase *nb) { /* * Main Widget */ TQVBox *vbp = nb->addVBoxPage(i18n("Offline Engine"), i18n("Use this to configure the Offline engine"), kapp->iconLoader()->loadIcon(PROG_NAME "_engine", KIcon::Desktop)); /* * Get a multi page work space */ KTabCtl *tc = new KTabCtl(vbp, "offline tabs"); /* * Player names */ TQWidget *w = new TQWidget(tc); TQGridLayout *gl = new TQGridLayout(w, 2, 1, nb->spacingHint()); /* * Group boxes */ TQGroupBox *gbn = new TQGroupBox(i18n("Names"), w); gl->addWidget(gbn, 0, 0); gl = new TQGridLayout(gbn, 2, 2, 20); d->mLe[0] = new TQLineEdit(d->mName[0], gbn); d->mLe[1] = new TQLineEdit(d->mName[1], gbn); TQLabel *lb[2]; lb[0] = new TQLabel(i18n("First player:"), gbn); lb[1] = new TQLabel(i18n("Second player:"), gbn); for (int i = 0; i < 2; i++) { gl->addWidget(lb[i], i, 0); gl->addWidget(d->mLe[i], i, 1); } TQWhatsThis::add(d->mLe[0], i18n("Enter the name of the first player.")); TQWhatsThis::add(d->mLe[1], i18n("Enter the name of the second player.")); /* * Done with the page, put it in */ gl->activate(); tc->addTab(w, i18n("&Player Names")); } /* * Called when the setup dialog is positively closed */ void KBgEngineOffline::setupOk() { d->mName[0] = d->mLe[0]->text(); d->mName[1] = d->mLe[1]->text(); } void KBgEngineOffline::setupDefault() { d->mName[0] = i18n("South"); d->mName[1] = i18n("North"); } void KBgEngineOffline::setupCancel() { // do nothing } /* * Restore settings */ void KBgEngineOffline::readConfig() { TDEConfig* config = kapp->config(); config->setGroup("offline engine"); d->mName[0] = config->readEntry("player-one", i18n("South")); // same as above d->mName[1] = config->readEntry("player-two", i18n("North")); // same as above cl = config->readNumEntry("timer", 2500); } /* * Save the engine specific settings */ void KBgEngineOffline::saveConfig() { TDEConfig* config = kapp->config(); config->setGroup("offline engine"); config->writeEntry("player-one", d->mName[0] ); config->writeEntry("player-two", d->mName[1]); config->writeEntry("timer", cl); } // == start and init games ===================================================== /* * Start a new game. */ void KBgEngineOffline::newGame() { int u = 0; int t = 0; /* * If there is a game running we warn the user first */ if (d->mGameFlag && (KMessageBox::warningYesNo((TQWidget *)parent(), i18n("A game is currently in progress. " "Starting a new one will terminate it."), TQString(), i18n("Start New Game"), i18n("Continue Old Game")) == KMessageBox::No)) return; /* * Separate from the previous game */ emit infoText("


"); /* * Get player's names - user can still cancel */ if (!queryPlayerName(US) || !queryPlayerName(THEM)) return; /* * let the games begin */ d->mGameFlag = true; /* * Initialize the board */ initGame(); /* * Figure out who starts by rolling */ while (u == t) { u = getRandom(); t = getRandom(); emit infoText(i18n("%1 rolls %2, %3 rolls %4."). arg(d->mName[0]).arg(u).arg(d->mName[1]).arg(t)); } if (u > t) { emit infoText(i18n("%1 makes the first move.").arg(d->mName[0])); d->mRoll = US; } else { emit infoText(i18n("%1 makes the first move.").arg(d->mName[1])); d->mRoll = THEM; int n = u; u = t; t = n; } /* * set the dice and tell the board */ rollDiceBackend(d->mRoll, u, t); /* * tell the user */ emit statText(i18n("%1 vs. %2").arg(d->mName[0]).arg(d->mName[1])); } /* * Initialize the state descriptors mGame[0|1] */ void KBgEngineOffline::initGame() { /* * nobody rolled yet */ d->mRoll = -1; /* * set up a standard game */ d->mGame[0].setCube(1, true, true); d->mGame[0].setDirection(+1); d->mGame[0].setColor(+1); for (int i = 1; i < 25; i++) d->mGame[0].setBoard(i, US, 0); d->mGame[0].setBoard( 1, US, 2); d->mGame[0].setBoard( 6, THEM, 5); d->mGame[0].setBoard( 8, THEM, 3); d->mGame[0].setBoard(12, US, 5); d->mGame[0].setBoard(13, THEM, 5); d->mGame[0].setBoard(17, US, 3); d->mGame[0].setBoard(19, US, 5); d->mGame[0].setBoard(24, THEM, 2); d->mGame[0].setHome(US, 0); d->mGame[0].setHome(THEM, 0); d->mGame[0].setBar(US, 0); d->mGame[0].setBar(THEM, 0); d->mGame[0].setDice(US , 0, 0); d->mGame[0].setDice(US , 1, 0); d->mGame[0].setDice(THEM, 0, 0); d->mGame[0].setDice(THEM, 1, 0); /* * save backup of the game state */ d->mGame[1] = d->mGame[0]; emit allowCommand(Load, true); } /* * Open a dialog to query for the name of player w. Return true unless * the dialog was canceled. */ bool KBgEngineOffline::queryPlayerName(int w) { bool ret = false; TQString *name; TQString text; if (w == US) { name = &d->mName[0]; text = i18n("Please enter the nickname of the player whose home\n" "is in the lower half of the board:"); } else { name = &d->mName[1]; text = i18n("Please enter the nickname of the player whose home\n" "is in the upper half of the board:"); } do { *name = KLineEditDlg::getText(text, *name, &ret, (TQWidget *)parent()); if (!ret) break; } while (name->isEmpty()); return ret; } // == moving =================================================================== /* * Finish the last move - called by the timer and directly by the used */ void KBgEngineOffline::done() { ct->stop(); emit allowMoving(false); emit allowCommand(Done, false); emit allowCommand(Undo, false); if (abs(d->mGame[0].home(d->mRoll)) == 15) { emit infoText(i18n("%1 wins the game. Congratulations!"). arg((d->mRoll == US) ? d->mName[0] : d->mName[1])); d->mGameFlag = false; emit allowCommand(Roll, false); emit allowCommand(Cube, false); } else { emit allowCommand(Roll, true); if (d->mGame[0].cube((d->mRoll == US ? THEM : US)) > 0) { d->mGame[0].setDice(US , 0, 0); d->mGame[0].setDice(US , 1, 0); d->mGame[0].setDice(THEM, 0, 0); d->mGame[0].setDice(THEM, 1, 0); emit newState(d->mGame[0]); emit getState(&d->mGame[0]); d->mGame[1] = d->mGame[0]; emit infoText(i18n("%1, please roll or double."). arg((d->mRoll == THEM) ? d->mName[0] : d->mName[1])); emit allowCommand(Cube, true); } else { roll(); emit allowCommand(Cube, false); } } } /* * Undo the last move */ void KBgEngineOffline::undo() { ct->stop(); d->mRedoFlag = true; ++d->mUndo; emit allowMoving(true); emit allowCommand(Done, false); emit allowCommand(Redo, true); emit undoMove(); } /* * Redo the last move */ void KBgEngineOffline::redo() { --d->mUndo; emit redoMove(); } /* * Take the move string and make the changes on the working copy * of the state. */ void KBgEngineOffline::handleMove(TQString *s) { int index = 0; TQString t = s->mid(index, s->find(' ', index)); index += 1 + t.length(); int moves = t.toInt(); /* * Allow undo and possibly start the commit timer */ d->mRedoFlag &= ((moves < d->mMove) && (d->mUndo > 0)); emit allowCommand(Undo, moves > 0); emit allowCommand(Redo, d->mRedoFlag); emit allowCommand(Done, moves == d->mMove); if (moves == d->mMove && cl) { emit allowMoving(false); ct->start(cl, true); } /* * Apply moves to d->mGame[1] and store results in d->mGame[0] */ d->mGame[0] = d->mGame[1]; /* * process each individual move */ for (int i = 0; i < moves; i++) { bool kick = false; t = s->mid(index, s->find(' ', index) - index); index += 1 + t.length(); char c = '-'; if (t.contains('+')) { c = '+'; kick = true; } TQString r = t.left(t.find(c)); if (r.contains("bar")) { d->mGame[0].setBar(d->mRoll, abs(d->mGame[0].bar(d->mRoll)) - 1); } else { int from = r.toInt(); d->mGame[0].setBoard(from, d->mRoll, abs(d->mGame[0].board(from)) - 1); } t.remove(0, 1 + r.length()); if (t.contains("off")) { d->mGame[0].setHome(d->mRoll, abs(d->mGame[0].home(d->mRoll)) + 1); } else { int to = t.toInt(); if (kick) { d->mGame[0].setBoard(to, d->mRoll, 0); int el = ((d->mRoll == US) ? THEM : US); d->mGame[0].setBar(el, abs(d->mGame[0].bar(el)) + 1); } d->mGame[0].setBoard(to, d->mRoll, abs(d->mGame[0].board(to)) + 1); } } } // == dice & rolling =========================================================== /* * Roll random dice for the player whose turn it is */ void KBgEngineOffline::roll() { rollDice((d->mRoll == US) ? THEM : US); } /* * If possible, roll random dice for player w */ void KBgEngineOffline::rollDice(const int w) { if ((d->mRoll != w) && d->mRollFlag) { rollDiceBackend(w, getRandom(), getRandom()); return; } emit infoText(i18n("It's not your turn to roll!")); } /* * Return a random integer between 1 and 6. According to the man * page of rand(), this is the way to go... */ int KBgEngineOffline::getRandom() { return 1+d->mRandom->getLong(6); } /* * Set the dice for player w to a and b. Reload the board and determine the * maximum number of moves */ void KBgEngineOffline::rollDiceBackend(const int w, const int a, const int b) { /* * This is a special case that stems from leaving the edit * mode. */ if (a == 0) return; /* * Set the dice and tel the board about the new state */ d->mGame[0].setDice(w, 0, a); d->mGame[0].setDice(w, 1, b); d->mGame[0].setDice((w == US) ? THEM : US, 0, 0); d->mGame[0].setDice((w == US) ? THEM : US, 1, 0); d->mGame[0].setTurn(w); d->mGame[1] = d->mGame[0]; d->mRoll = w; emit newState(d->mGame[0]); /* * No more roling until Done and no Undo yet */ emit allowCommand(Undo, false); emit allowCommand(Roll, false); d->mRedoFlag = false; d->mUndo = 0; /* * Tell the players how many checkers to move */ switch (d->mMove = d->mGame[0].moves()) { case -1: emit infoText(i18n("Game over!")); d->mGameFlag = false; emit allowCommand(Roll, false); emit allowCommand(Cube, false); emit allowMoving(false); break; case 0: emit infoText(i18n("%1, you cannot move."). arg((w == US) ? d->mName[0] : d->mName[1])); if (cl) ct->start(cl, true); emit allowMoving(false); break; // case 1: default: emit infoText(TQString((w == US) ? d->mName[0] : d->mName[1]) + i18n(", please move 1 piece.",", please move %n pieces.",d->mMove)); emit allowMoving(true); break; } } // == cube ===================================================================== /* * Double the cube for the player that can double - asks player */ void KBgEngineOffline::cube() { int w = ((d->mRoll == US) ? THEM : US); if (d->mRollFlag && d->mGame[0].cube(w) > 0) { emit allowCommand(Cube, false); if (KMessageBox::questionYesNo((TQWidget *)parent(), i18n("%1 has doubled. %2, do you accept the double?"). arg((w == THEM) ? d->mName[1] : d->mName[0]). arg((w == US) ? d->mName[1] : d->mName[0]), i18n("Doubling"), i18n("Accept"), i18n("Reject")) != KMessageBox::Yes) { d->mGameFlag = false; emit allowCommand(Roll, false); emit allowCommand(Cube, false); emit infoText(i18n("%1 wins the game. Congratulations!"). arg((w == US) ? d->mName[0] : d->mName[1])); return; } emit infoText(i18n("%1 has accepted the double. The game continues."). arg((w == THEM) ? d->mName[0] : d->mName[1])); if (d->mGame[0].cube(US)*d->mGame[0].cube(THEM) > 0) d->mGame[0].setCube(2, w == THEM, w == US); else d->mGame[0].setCube(2*d->mGame[0].cube(w), w == THEM, w == US); emit newState(d->mGame[0]); emit getState(&d->mGame[0]); d->mGame[1] = d->mGame[0]; roll(); } } /* * Double the cube for player w */ void KBgEngineOffline::doubleCube(const int) { cube(); } // == various slots & functions ================================================ /* * Check with the user if we should really quit in the middle of a * game. */ bool KBgEngineOffline::queryClose() { if (!d->mGameFlag) return true; switch (KMessageBox::warningContinueCancel((TQWidget *)parent(), i18n("In the middle of a game. " "Really quit?"), TQString(), KStdGuiItem::quit())) { case KMessageBox::Continue : return TRUE; case KMessageBox::Cancel : return FALSE; default: // cancel return FALSE; } return true; } /* * Quitting is fine at any time */ bool KBgEngineOffline::queryExit() { return true; } /* * Handle textual commands. Right now, all commands are ignored */ void KBgEngineOffline::handleCommand(const TQString& cmd) { emit infoText(i18n("Text commands are not yet working. " "The command '%1' has been ignored.").arg(cmd)); } /* * Load the last known sane state of the board */ void KBgEngineOffline::load() { if (d->mEdit->isChecked()) emit newState(d->mGame[1]); else { // undo up to four moves undo(); undo(); undo(); undo(); } } /* * Store if cmd is allowed or not */ void KBgEngineOffline::setAllowed(int cmd, bool f) { switch (cmd) { case Roll: d->mRollFlag = f; return; case Undo: d->mUndoFlag = f; return; case Cube: d->mCubeFlag = f; return; case Done: d->mDoneFlag = f; return; } } /* * Swaps the used colors on the board */ void KBgEngineOffline::swapColors() { d->mGame[1].setDice(US, 0, d->mGame[0].dice(US, 0)); d->mGame[1].setDice(US, 1, d->mGame[0].dice(US, 1)); d->mGame[1].setDice(THEM, 0, d->mGame[0].dice(THEM, 0)); d->mGame[1].setDice(THEM, 1, d->mGame[0].dice(THEM, 1)); d->mGame[1].setColor(d->mGame[1].color(THEM), US); emit newState(d->mGame[1]); emit getState(&d->mGame[1]); d->mGame[0] = d->mGame[1]; } /* * Switch back and forth between edit and play mode */ void KBgEngineOffline::toggleEditMode() { emit setEditMode(d->mEdit->isChecked()); if (d->mEdit->isChecked()) { ct->stop(); d->mNew->setEnabled(false); d->mSwap->setEnabled(false); emit allowCommand(Undo, false); emit allowCommand(Roll, false); emit allowCommand(Done, false); emit allowCommand(Cube, false); emit statText(i18n("%1 vs. %2 - Edit Mode").arg(d->mName[0]).arg(d->mName[1])); } else { d->mNew->setEnabled(true); d->mSwap->setEnabled(true); emit statText(i18n("%1 vs. %2").arg(d->mName[0]).arg(d->mName[1])); emit getState(&d->mGame[1]); d->mGame[0] = d->mGame[1]; emit allowCommand(Done, d->mDoneFlag); emit allowCommand(Cube, d->mCubeFlag); emit allowCommand(Undo, d->mUndoFlag); emit allowCommand(Roll, d->mRollFlag); int w =((d->mGame[0].dice(US, 0) && d->mGame[0].dice(US, 1)) ? US : THEM); rollDiceBackend(w, d->mGame[0].dice(w, 0), d->mGame[0].dice(w, 1)); } } // EOF