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/gnubg/kbggnubg.cpp

711 lines
14 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 "kbggnubg.moc"
#include "kbggnubg.h"
#include <kapplication.h>
#include <kmessagebox.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <tqlayout.h>
#include <kiconloader.h>
#include <kstdaction.h>
#include <tqbuttongroup.h>
#include <tqcheckbox.h>
#include <kconfig.h>
#include <iostream>
#include <klocale.h>
#include <kmainwindow.h>
#include <klineeditdlg.h>
#include <tqregexp.h>
#include <kregexp.h>
#include <knotifyclient.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>
#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"
//KBgtqStatus 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
KBgtqStatus st(board);
int ret = KMessageBox::warningYesNoCancel
(0, i18n("gnubg doubles the cube to %1.").tqarg(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:"))) {
KBgtqStatus 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.").tqarg(st.dice(US, 0)).tqarg(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.").tqarg(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.").tqarg(st.dice(THEM, 0)).tqarg(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").tqarg(st.player(US)).tqarg(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(" "), "&nbsp;");
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", KIcon::Desktop));
}
/*
* Restore settings
*/
void KBgEngineGNU::readConfig()
{
KConfig* config = kapp->config();
config->setGroup("gnu engine");
// nothing yet
}
/*
* Save the engine specific settings
*/
void KBgEngineGNU::saveConfig()
{
KConfig* 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 KAction(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.<br/><br/>"));
/*
* Initialize variables
*/
partline = board = "";
/*
* Start the process - this requires that gnubg is in the PATH
*/
gnubg << "gnubg" << "--tty";
connect(&gnubg, TQT_SIGNAL(processExited(KProcess *)), this, TQT_SLOT(gnubgExit(KProcess *)));
connect(&gnubg, TQT_SIGNAL(receivedStderr(KProcess *, char *, int)),
this, TQT_SLOT(receiveData(KProcess *, char *, int)));
connect(&gnubg, TQT_SIGNAL(receivedStdout(KProcess *, char *, int)),
this, TQT_SLOT(receiveData(KProcess *, char *, int)));
connect(&gnubg, TQT_SIGNAL(wroteStdin(KProcess *)), this, TQT_SLOT(wroteStdin(KProcess *)));
startGNU();
}
/*
* Actually start the background process.
*/
void KBgEngineGNU::startGNU()
{
resAction->setEnabled(false);
if (!gnubg.start(KProcess::NotifyOnExit, KProcess::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(KProcess *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("<br/><font color=\"red\">") + i18n("The GNU Backgammon process (%1) has exited. ")
.tqarg(proc->pid()) + "</font><br/>");
resAction->setEnabled(true);
}
// == communication callbacks with GNU bg ======================================
/*
* Last command has been sent. Try to send pending ones.
*/
void KBgEngineGNU::wroteStdin(KProcess *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(KProcess *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