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/kbackgammon/engines/offline/kbgoffline.cpp

811 lines
20 KiB

/* 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 <tqlayout.h>
#include <tqbuttongroup.h>
#include <tqcheckbox.h>
#include <tqtimer.h>
#include <tqspinbox.h>
#include <tqwhatsthis.h>
#include <tqlineedit.h>
#include <tqvbox.h>
#include <kapplication.h>
#include <kmessagebox.h>
#include <kiconloader.h>
#include <kstdaction.h>
#include <tdeconfig.h>
#include <klocale.h>
#include <tdemainwindow.h>
#include <klineeditdlg.h>
#include <tdeaction.h>
#include <krandomsequence.h>
#include <ktabctl.h>
#include <stdlib.h>
#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
*/
TDEAction *mNew, *mSwap;
TDEToggleAction *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 TDEAction(i18n("&New Game..."), 0, this, TQT_SLOT(newGame()), this);
d->mSwap = new TDEAction(i18n("&Swap Colors"), 0, this, TQT_SLOT(swapColors()), this);
d->mEdit = new TDEToggleAction(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("<br/><br/><br/>");
/*
* 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