/* 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 "kbggnubg.moc" #include "kbggnubg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kbgstatus.h" #include "kbgboard.h" #include "version.h" // == cube ===================================================================== /* * Double the cube for the player that can double - asks player */ void KBgEngineGNU::cube() { handleCommand("double"); } /* * Double the cube for player w */ void KBgEngineGNU::doubleCube(const int w) { dummy = w; // avoid compiler warning cube(); } void KBgEngineGNU::handleLine(const TQString &l) { if (l.isEmpty()) return; TQString line(l); /* * Start of a new game/match */ if (line.contains(TQRegExp("^gnubg rolls [1-6], .* rolls [1-6]\\."))) { KRegExp e("^gnubg rolls ([1-6]), .* rolls ([1-6])\\."); e.match(line.latin1()); if (int r = strcmp(e.group(1), e.group(2))) turn = (r < 0) ? uRoll : tRoll; } /* * Bug fixes for older versions of GNUBG - to be removed */ if (line.contains(TQRegExp("^.* cannot move\\..+$"))) { KRegExp e("(^.* cannot move.)(.*$)"); e.match(line.latin1()); handleLine(e.group(1)); handleLine(e.group(2)); return; } if (line.contains(TQRegExp("^Are you sure you want to start a new game, and discard the one in progress\\?"))) { KRegExp e("(^Are you sure you want to start a new game, and discard the one in progress\\? )(.+$)"); e.match(line.latin1()); handleLine(e.group(1)); handleLine(e.group(2)); return; } /* * Cube handling */ if (line.contains(TQRegExp("^gnubg accepts and immediately redoubles to [0-9]+\\.$"))) { // redoubles mess up the game counter "turn" //KBgStatus st(board); //st.setCube(32, BOTH); //emit newState(st); } if (line.contains(TQRegExp("^gnubg doubles\\.$"))) { // TODO: we need some generic class for this. the class // can be shared between all engines #if 0 KBgStatus st(board); int ret = KMessageBox::warningYesNoCancel (0, i18n("gnubg doubles the cube to %1.").arg(2*st.cube(THEM)), i18n("gnubg doubles"), i18n("&Accept"), i18n("Re&double"), i18n("&Reject"), true); switch (ret) { case KMessageBox::Yes: handleCommand("accept"); break; case KMessageBox::No: handleCommand("redouble"); break; case KMessageBox::Cancel: handleCommand("reject"); break; } #endif } /* * Ignore the following messages */ if (line.contains(TQRegExp("^TTY boards will be given in raw format"))) { line = " "; } /* * Board messages */ if (line.contains(TQRegExp("^board:"))) { KBgStatus st(line); /* * Do preliminary analysis of board */ if (st.doubled()) { --turn; return; } if (strcmp(board.latin1(),line.latin1())) ++turn %= maxTurn; board = line; /* * Act according to the current state in the move/roll cycle */ switch (turn) { case uRoll: if (st.cube() > 0) { emit infoText(i18n("Please roll or double.")); KNotifyClient::event("roll or double"); } else { emit infoText(i18n("Please roll.")); KNotifyClient::event("roll"); } emit allowCommand(Roll, true); emit allowCommand(Cube, true); break; case uMove: st.setDice(THEM, 0, 0); st.setDice(THEM, 1, 0); emit infoText(i18n("You roll %1 and %2.").arg(st.dice(US, 0)).arg(st.dice(US, 1))); switch (st.moves()) { case 0: // get a message break; case 1: emit infoText(i18n("Please move 1 piece.")); break; default: emit infoText(i18n("Please move %1 pieces.").arg(st.moves())); break; } emit allowCommand(Roll, false); break; case tRoll: break; case tMove: st.setDice(US, 0, 0); st.setDice(US, 1, 0); emit infoText(i18n("gnubg rolls %1 and %2.").arg(st.dice(THEM, 0)).arg(st.dice(THEM, 1))); if (st.moves() == 0) emit infoText(i18n("gnubg cannot move.")); break; } /* * Bookkeeping */ undoCounter = 0; toMove = st.moves(); emit allowMoving(st.turn() == US); emit newState(st); emit statText(i18n("%1 vs. %2").arg(st.player(US)).arg(st.player(THEM))); emit allowCommand(Load, true ); emit allowCommand(Undo, false); emit allowCommand(Redo, false); emit allowCommand(Done, false); return; } /* * Show the line... */ line.replace(TQRegExp(" "), " "); if (!line.isEmpty()) emit infoText(line); } /* * Handle textual commands. All commands are passed to gnubg. */ void KBgEngineGNU::handleCommand(const TQString& cmd) { cmdList += cmd; nextCommand(); } // == start and init games ===================================================== /* * Start a new game. */ void KBgEngineGNU::newGame() { /* * If there is a game running we warn the user first */ if (gameRunning && (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; /* * Start new game */ handleCommand("new game"); if (gameRunning) handleCommand("yes"); gameRunning = true; emit infoText(i18n("Starting a new game.")); } // == various slots & functions ================================================ /* * Quitting is fine at any time */ bool KBgEngineGNU::queryClose() { return true; } /* * Quitting is fine at any time */ bool KBgEngineGNU::queryExit() { return true; } /* * Load the last known sane state of the board */ void KBgEngineGNU::load() { handleCommand("show board"); } /* * Store if cmd is allowed or not */ void KBgEngineGNU::setAllowed(int cmd, bool f) { switch (cmd) { case Roll: rollingAllowed = f; return; case Undo: undoPossible = f; return; case Cube: doublePossible = f; return; case Done: donePossible = f; return; } } // == configuration handling =================================================== void KBgEngineGNU::setupOk() { // nothing yet } void KBgEngineGNU::setupCancel() { // nothing yet } void KBgEngineGNU::setupDefault() { // nothing yet } void KBgEngineGNU::getSetupPages(KDialogBase *nb) { /* * Main Widget */ TQVBox *w = nb->addVBoxPage(i18n("GNU Engine"), i18n("Here you can configure the GNU backgammon engine"), kapp->iconLoader()->loadIcon(PROG_NAME "_engine", TDEIcon::Desktop)); } /* * Restore settings */ void KBgEngineGNU::readConfig() { TDEConfig* config = kapp->config(); config->setGroup("gnu engine"); // nothing yet } /* * Save the engine specific settings */ void KBgEngineGNU::saveConfig() { TDEConfig* config = kapp->config(); config->setGroup("gnu engine"); // nothing yet } // ***************************************************************************** // ***************************************************************************** // ***************************************************************************** // ***************************************************************************** // ***************************************************************************** // ***************************************************************************** // == constructor, destructor and other ======================================== /* * Constructor */ KBgEngineGNU::KBgEngineGNU(TQWidget *parent, TQString *name, TQPopupMenu *pmenu) : KBgEngine(parent, name, pmenu) { // obsolete nameUS = "US"; nameTHEM = "THEM"; random.setSeed(getpid()*time(NULL)); /* * internal statue variables */ rollingAllowed = undoPossible = gameRunning = donePossible = false; connect(this, TQT_SIGNAL(allowCommand(int, bool)), this, TQT_SLOT(setAllowed(int, bool))); /* * Setup of menu */ resAction = new TDEAction(i18n("&Restart GNU Backgammon"), 0, this, TQT_SLOT(startGNU()), this); resAction->setEnabled(false); resAction->plug(menu); /* * Restore last stored settings */ readConfig(); } /* * Destructor. Kill the child process and that's it. */ KBgEngineGNU::~KBgEngineGNU() { gnubg.kill(); } // == start, restart, termination of gnubg ===================================== /* * Start the GNU Backgammon process in the background and set up * some communication links. */ void KBgEngineGNU::start() { /* * Will be started later */ cmdTimer = new TQTimer(this); connect(cmdTimer, TQT_SIGNAL(timeout()), TQT_SLOT(nextCommand()) ); emit infoText(i18n("This is experimental code which currently requires a specially " "patched version of GNU Backgammon.

")); /* * Initialize variables */ partline = board = ""; /* * Start the process - this requires that gnubg is in the PATH */ gnubg << "gnubg" << "--tty"; connect(&gnubg, TQT_SIGNAL(processExited(TDEProcess *)), this, TQT_SLOT(gnubgExit(TDEProcess *))); connect(&gnubg, TQT_SIGNAL(receivedStderr(TDEProcess *, char *, int)), this, TQT_SLOT(receiveData(TDEProcess *, char *, int))); connect(&gnubg, TQT_SIGNAL(receivedStdout(TDEProcess *, char *, int)), this, TQT_SLOT(receiveData(TDEProcess *, char *, int))); connect(&gnubg, TQT_SIGNAL(wroteStdin(TDEProcess *)), this, TQT_SLOT(wroteStdin(TDEProcess *))); startGNU(); } /* * Actually start the background process. */ void KBgEngineGNU::startGNU() { resAction->setEnabled(false); if (!gnubg.start(TDEProcess::NotifyOnExit, TDEProcess::All)) KMessageBox::information((TQWidget *)parent(), i18n("Could not start the GNU Backgammon process.\n" "Make sure the program is in your PATH and is " "called \"gnubg\".\n" "Make sure that your copy is at least version 0.10")); /* * Set required gnubg options */ handleCommand("set output rawboard on"); } /* * The gnubg process has died. Stop all user activity and allow a restart. */ void KBgEngineGNU::gnubgExit(TDEProcess *proc) { ct->stop(); cmdTimer->stop(); emit allowCommand(Undo, false); emit allowCommand(Roll, false); emit allowCommand(Done, false); emit allowCommand(Cube, false); emit allowCommand(Load, false); emit allowMoving(false); emit infoText(TQString("
") + i18n("The GNU Backgammon process (%1) has exited. ") .arg(proc->pid()) + "
"); resAction->setEnabled(true); } // == communication callbacks with GNU bg ====================================== /* * Last command has been sent. Try to send pending ones. */ void KBgEngineGNU::wroteStdin(TDEProcess *proc) { if (!proc->isRunning()) return; nextCommand(); } /* * Try to send the next command from the command list to gnubg. * If it fails, make sure we call ourselves again. */ void KBgEngineGNU::nextCommand() { if (!gnubg.isRunning()) return; for (TQStringList::Iterator it = cmdList.begin(); it != cmdList.end(); ++it) { TQString s = (*it) + "\n"; if (!gnubg.writeStdin(s.latin1(), strlen(s.latin1()))) { cmdTimer->start(250, true); cmdList.remove(TQString()); return; } (*it) = TQString(); } cmdList.remove(TQString()); cmdTimer->stop(); } /* * Get data from GNU Backgammon and process them. Note that we may have * to buffer the last line and wait for the closing newline... */ void KBgEngineGNU::receiveData(TDEProcess *proc, char *buffer, int buflen) { if (!proc->isRunning()) return; char *buf = new char[buflen+1]; memcpy(buf, buffer, buflen); buf[buflen] = '\0'; TQStringList l(TQStringList::split('\n', buf, true)); /* * Restore partial lines from the previous time */ l.first() = partline + l.first(); partline = ""; if (buf[buflen-1] != '\n') { partline = l.last(); l.remove(partline); } delete[] buf; /* * Handle the information from gnubg */ for (TQStringList::Iterator it = l.begin(); it != l.end(); ++it) handleLine(*it); } // == moving =================================================================== /* * Finish the last move - called by the timer and directly by the user */ void KBgEngineGNU::done() { ct->stop(); emit allowMoving(false); emit allowCommand(Done, false); emit allowCommand(Undo, false); emit allowCommand(Redo, false); // Transform the string to FIBS format lastmove.replace(0, 2, "move "); lastmove.replace(TQRegExp("\\+"), " "); lastmove.replace(TQRegExp("\\-"), " "); // sent it to the server handleCommand(lastmove); } /* * Undo the last move */ void KBgEngineGNU::undo() { ct->stop(); redoPossible = true; ++undoCounter; emit allowMoving(true); emit allowCommand(Done, false); emit allowCommand(Redo, true); emit undoMove(); } /* * Redo the last move */ void KBgEngineGNU::redo() { --undoCounter; emit redoMove(); } /* * Take the move string and make the changes on the working copy * of the state. */ void KBgEngineGNU::handleMove(TQString *s) { lastmove = *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 */ redoPossible &= ((moves < toMove) && (undoCounter > 0)); emit allowCommand(Undo, moves > 0); emit allowCommand(Redo, redoPossible); emit allowCommand(Done, moves == toMove); if (moves == toMove && cl >= 0) { emit allowMoving(false); ct->start(cl, true); } } // == dice & rolling =========================================================== /* * Roll random dice for the player whose turn it is. We can ignore the * value of w, since we have the turn value. */ void KBgEngineGNU::roll() { if (turn == uRoll) handleCommand("roll"); } void KBgEngineGNU::rollDice(const int w) { roll(); } // EOF