diff options
Diffstat (limited to 'kbackgammon/engines/fibs/kbgfibs.cpp')
-rw-r--r-- | kbackgammon/engines/fibs/kbgfibs.cpp | 2314 |
1 files changed, 2314 insertions, 0 deletions
diff --git a/kbackgammon/engines/fibs/kbgfibs.cpp b/kbackgammon/engines/fibs/kbgfibs.cpp new file mode 100644 index 00000000..06fdaec7 --- /dev/null +++ b/kbackgammon/engines/fibs/kbgfibs.cpp @@ -0,0 +1,2314 @@ +/* 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$ + +*/ + +/* + + TODO: popup dialog for accept/reject and join ?? + clear the chat history? + game over, clear the caption? + need show saved + need buddy list + need wait for player,... + +*/ + +#include "kbgfibs.h" +#include "kbgfibs.moc" + +#include <kapplication.h> +#include <kconfig.h> +#include <qtimer.h> +#include <qcheckbox.h> +#include <qlayout.h> +#include <qstring.h> +#include <qsocket.h> +#include <qpopupmenu.h> +#include <qgroupbox.h> +#include <qbuttongroup.h> +#include <qradiobutton.h> +#include <kmainwindow.h> +#include <klineeditdlg.h> +#include <kmessagebox.h> +#include <qdatetime.h> +#include <qwhatsthis.h> +#include <kaudioplayer.h> +#include <kstandarddirs.h> +#include <qvbox.h> +#include <kiconloader.h> +#include <ktabctl.h> +#include <kpassdlg.h> +#include <qcstring.h> +#include <knotifyclient.h> +#include <kaction.h> + + +#include <stdlib.h> +#include <stdio.h> +#include <time.h> +#include <iostream> +#include "kbgboard.h" +#include "kbgstatus.h" + +#include "clip.h" +#include "version.h" + + +void KBgEngineFIBS::start() +{ + // FIXME: open the child windows here and not in the constructor +} + +// == configuration handling =================================================== + +/* + * Restore settings and ask children to do the same + */ +void KBgEngineFIBS::readConfig() +{ + KConfig *config = kapp->config(); + config->setGroup("fibs engine"); + + // history variables + lastAway = config->readEntry("away_hist", ""); + + // various options + showMsg = config->readBoolEntry("pers_msg", false); + whoisInvite = config->readBoolEntry("whois_invite", false); + + // connection information + infoFIBS[FIBSHost] = config->readEntry("server", "fibs.com"); + infoFIBS[FIBSPort] = config->readEntry("port", "4321"); + infoFIBS[FIBSUser] = config->readEntry("user", ""); + infoFIBS[FIBSPswd] = config->readEntry("password", ""); + + // automatic messages + useAutoMsg[MsgBeg] = config->readBoolEntry("auto-beg", false); + useAutoMsg[MsgLos] = config->readBoolEntry("auto-los", false); + useAutoMsg[MsgWin] = config->readBoolEntry("auto-win", false); + + autoMsg[MsgBeg] = config->readEntry("msg-beg", ""); + autoMsg[MsgLos] = config->readEntry("msg-los", ""); + autoMsg[MsgWin] = config->readEntry("msg-win", ""); + + // ask the children to read their config options + playerlist->readConfig(); + chatWindow->readConfig(); +} + +/* + * Save the engine specific settings and tell all clients + */ +void KBgEngineFIBS::saveConfig() +{ + KConfig *config = kapp->config(); + config->setGroup("fibs engine"); + + // history variables + config->writeEntry("away_hist", lastAway); + + // various options + config->writeEntry("pers_msg", showMsg); + config->writeEntry("whois_invite", whoisInvite); + + // connection information + config->writeEntry("server", infoFIBS[FIBSHost]); + config->writeEntry("port", infoFIBS[FIBSPort]); + config->writeEntry("user", infoFIBS[FIBSUser]); + config->writeEntry("password", infoFIBS[FIBSPswd]); + + // automatic messages + config->writeEntry("auto-beg", useAutoMsg[MsgBeg]); + config->writeEntry("auto-los", useAutoMsg[MsgLos]); + config->writeEntry("auto-win", useAutoMsg[MsgWin]); + + config->writeEntry("msg-beg", autoMsg[MsgBeg]); + config->writeEntry("msg-los", autoMsg[MsgLos]); + config->writeEntry("msg-win", autoMsg[MsgWin]); + + // ask the children to read their config options + playerlist->saveConfig(); + chatWindow->saveConfig(); +} + +void KBgEngineFIBS::setupDefault() +{ + + cbp->setChecked(false); + cbi->setChecked(false); + + lec[FIBSHost]->setText("fibs.com"); + lec[FIBSPort]->setText("4321"); + + lec[FIBSUser]->clear(); + lec[FIBSPswd]->clear(); + + + chatWindow->setupDefault(); + playerlist->setupDefault(); +} + +void KBgEngineFIBS::setupCancel() +{ + chatWindow->setupCancel(); + playerlist->setupCancel(); +} + +/* + * Called when the setup dialog is positively closed + */ +void KBgEngineFIBS::setupOk() +{ + // various options + showMsg = cbp->isChecked(); + whoisInvite = cbi->isChecked(); + + // connection information + for (int i = 0; i < NumFIBS; i++) + infoFIBS[i] = lec[i]->text(); + + // automatic messages + for (int i = 0; i < NumMsg; i++) { + useAutoMsg[i] = cbm[i]->isChecked(); + autoMsg[i] = lem[i]->text(); + } + + chatWindow->setupOk(); + playerlist->setupOk(); + + // save settings + saveConfig(); +} + +/* + * Puts the FIBS specific setup into the dialog nb + */ +void KBgEngineFIBS::getSetupPages(KDialogBase *nb) +{ + /* + * Main Widget + */ + QVBox *vbp = nb->addVBoxPage(i18n("FIBS Engine"), i18n("Here you can configure the FIBS backgammon engine"), + kapp->iconLoader()->loadIcon(PROG_NAME "_engine", KIcon::Desktop)); + + /* + * Get a multi page work space + */ + KTabCtl *tc = new KTabCtl(vbp, "fibs tabs"); + + /* + * FIBS, local options + */ + QWidget *w = new QWidget(tc); + QGridLayout *gl = new QGridLayout(w, 3, 1, nb->spacingHint()); + + /* + * Group boxes + */ + QGroupBox *gbo = new QGroupBox(i18n("Options"), w); + QGroupBox *gbm = new QGroupBox(i18n("Automatic Messages"), w); + + gl->addWidget(gbo, 0, 0); + gl->addWidget(gbm, 1, 0); + + /* + * Options + */ + cbp = new QCheckBox(i18n("Show copy of personal messages in main window"), gbo); + cbi = new QCheckBox(i18n("Automatically request player info on invitation"), gbo); + + QWhatsThis::add(cbp, i18n("Usually, all messages sent directly to you by other players " + "are displayed only in the chat window. Check this box if you " + "would like to get a copy of these messages in the main window.")); + QWhatsThis::add(cbi, i18n("Check this box if you would like to receive information on " + "players that invite you to games.")); + + cbp->setChecked(showMsg); + cbi->setChecked(whoisInvite); + + gl = new QGridLayout(gbo, 2, 1, 20); + gl->addWidget(cbp, 0, 0); + gl->addWidget(cbi, 1, 0); + + /* + * Automatic messages + */ + gl = new QGridLayout(gbm, NumMsg, 2, 20); + + cbm[MsgBeg] = new QCheckBox(i18n("Start match:"), gbm); + cbm[MsgWin] = new QCheckBox(i18n("Win match:"), gbm); + cbm[MsgLos] = new QCheckBox(i18n("Lose match:"), gbm); + + QWhatsThis::add(cbm[MsgBeg], i18n("If you want to send a standard greeting to your " + "opponent whenever you start a new match, check " + "this box and write the message into the entry " + "field.")); + QWhatsThis::add(cbm[MsgWin], i18n("If you want to send a standard message to your " + "opponent whenever you won a match, check this box " + "and write the message into the entry field.")); + QWhatsThis::add(cbm[MsgLos], i18n("If you want to send a standard message to your " + "opponent whenever you lost a match, check this box " + "and write the message into the entry field.")); + + for (int i = 0; i < NumMsg; i++) { + lem[i] = new QLineEdit(autoMsg[i], gbm); + gl->addWidget(cbm[i], i, 0); + gl->addWidget(lem[i], i, 1); + connect(cbm[i], SIGNAL(toggled(bool)), lem[i], SLOT(setEnabled(bool))); + cbm[i]->setChecked(useAutoMsg[i]); + lem[i]->setEnabled(useAutoMsg[i]); + QWhatsThis::add(lem[i], QWhatsThis::textFor(cbm[i])); + } + + /* + * Put the page into the notebook + */ + gl->activate(); + tc->addTab(w, i18n("&Local")); + + + /* + * FIBS, connection setup + */ + w = new QWidget(tc); + gl = new QGridLayout(w, 3, 1, nb->spacingHint()); + + QGroupBox *gbc = new QGroupBox(i18n("Server"), w); + QGroupBox *gbk = new QGroupBox(i18n("Other"), w); + + gl->addWidget(gbc, 0, 0); + gl->addWidget(gbk, 1, 0); + + /* + * Server box + */ + gl = new QGridLayout(gbc, 4, 2, 20); + + QLabel *lbc[NumFIBS]; + + lbc[FIBSHost] = new QLabel(i18n("Server name:"), gbc); + lbc[FIBSPort] = new QLabel(i18n("Server port:"), gbc); + lbc[FIBSUser] = new QLabel(i18n("User name:"), gbc); + lbc[FIBSPswd] = new QLabel(i18n("Password:"), gbc); + + for (int i = 0; i < NumFIBS; i++) { + lec[i] = new QLineEdit(infoFIBS[i], gbc); + gl->addWidget(lbc[i], i, 0); + gl->addWidget(lec[i], i, 1); + } + lec[FIBSPswd]->setEchoMode(QLineEdit::Password); + + QWhatsThis::add(lec[FIBSHost], i18n("Enter here the host name of FIBS. With almost " + "absolute certainty this should be \"fibs.com\". " + "If you leave this blank, you will be asked again " + "at connection time.")); + QWhatsThis::add(lec[FIBSPort], i18n("Enter here the port number of FIBS. With almost " + "absolute certainty this should be \"4321\". " + "If you leave this blank, you will be asked again " + "at connection time.")); + QWhatsThis::add(lec[FIBSUser], i18n("Enter your login on FIBS here. If you do not have a " + "login yet, you should first create an account using " + "the corresponding menu entry. If you leave this blank, " + "you will be asked again at connection time.")); + QWhatsThis::add(lec[FIBSPswd], i18n("Enter your password on FIBS here. If you do not have a " + "login yet, you should first create an account using " + "the corresponding menu entry. If you leave this blank, " + "you will be asked again at connection time. The password " + "will not be visible.")); + + /* + * Connection keepalive + */ + cbk = new QCheckBox(i18n("Keep connections alive"), gbk); + + QWhatsThis::add(cbk, i18n("Usually, FIBS drops the connection after one hour of inactivity. When " + "you check this box, %1 will try to keep the connection alive, even " + "if you are not actually playing or chatting. Use this with caution " + "if you do not have flat-rate Internet access.").arg(PROG_NAME)); + + cbk->setChecked(keepalive); + + gl = new QGridLayout(gbk, 1, 1, nb->spacingHint()); + gl->addWidget(cbk, 0, 0); + + /* + * Done with the page, put it in + */ + gl->activate(); + tc->addTab(w, i18n("&Connection")); + + /* + * Ask children for settings + */ + chatWindow->getSetupPages(tc, nb->spacingHint()); + playerlist->getSetupPages(tc, nb->spacingHint()); + + /* + * TODO: future extensions + */ + w = new QWidget(tc); + tc->addTab(w, i18n("&Buddy List")); +} + + +// == functions related to the invitation menu ================================= + +/* + * Remove a player from the invitation list in the join menu + */ +void KBgEngineFIBS::cancelJoin(const QString &info) +{ + QRegExp patt = QRegExp("^" + info + " "); + + for (int i = 0; i <= numJoin; i++) { + if (actJoin[i]->text().contains(patt)) { + // move all entries starting at i+1 up by one... + for (int j = i; j < numJoin; j++) + actJoin[j]->setText(actJoin[j+1]->text()); + actJoin[numJoin--]->unplug(joinMenu); + break; + } + } +} + +/* + * Parse the information in info for the purposes of the invitation + * submenu + */ +void KBgEngineFIBS::changeJoin(const QString &info) +{ + char name_p[100], name_o[100]; + float rate; + int expi; + + /* + * Extract the name of the player, her opponent, rating and experience. + * It is okay to use latin1(), since the string is coming from FIBS. + */ + sscanf(info.latin1(), "%99s %99s %*s %*s %*s %f %i %*s %*s %*s %*s %*s", + name_p, name_o, &rate, &expi); + + QString name = name_p; + QString oppo = name_o; + + QString rate_s; rate_s.setNum(rate); + QString expi_s; expi_s.setNum(expi); + + QRegExp patt = QRegExp("^" + name + " "); + + /* + * We have essentially two lists of names to check against: the ones + * that have invited us and are not yet in the menu and the ones that + * are already in the menu. + */ + + if (numJoin > -1 && oppo != "-") + cancelJoin(name); + + for (QStringList::Iterator it = invitations.begin(); it != invitations.end(); ++it) { + + if ((*it).contains(patt)) { + + QString text, menu; + + if ((*it).contains(QRegExp(" r$"))) { + menu = i18n("R means resume", "%1 (R)").arg(name); + text = i18n("%1 (experience %2, rating %3) wants to resume a saved match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name); + KNotifyClient::event("invitation", i18n("%1 wants to resume a saved match with you"). + arg(name)); + } else if ((*it).contains(QRegExp(" u$"))) { + menu = i18n("U means unlimited", "%1 (U)").arg(name); + text = i18n("%1 (experience %2, rating %3) wants to play an unlimited match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %4').").arg(name).arg(expi_s).arg(rate_s).arg(name); + KNotifyClient::event("invitation", i18n("%1 has invited you to an unlimited match"). + arg(name)); + } else { + QString len = (*it).right((*it).length() - name.length() - 1); + menu = i18n("If the format of the (U) and (R) strings is changed, it should also be changed here", + "%1 (%2)").arg(name).arg(len); + text = i18n("%1 (experience %2, rating %3) wants to play a %4 point match with you. " + "If you want to play, use the corresponding menu entry to join (or type " + "'join %5').").arg(name).arg(expi_s).arg(rate_s).arg(len).arg(name); + KNotifyClient::event("invitation", i18n("%1 has invited you for a %2 point match"). + arg(name).arg(len)); + } + emit serverString("rawwho " + name); // this avoids a race + if (whoisInvite) { + emit serverString("whois " + name); + emit infoText("<font color=\"red\">" + text + "</font>"); + } else + emit infoText("<font color=\"red\">" + text + "</font><br>"); + + for (int i = 0; i <=numJoin; i++) + actJoin[i]->unplug(joinMenu); + + if (++numJoin > 7) numJoin = 7; + + for (int i = numJoin; i > 0; i--) + actJoin[i]->setText(actJoin[i-1]->text()); + + actJoin[0]->setText(menu); + + for (int i = 0; i <= numJoin; i++) + actJoin[i]->plug(joinMenu); + + invitations.remove(it); + break; + } + } + + /* + * If there are entries in the menu, enable it + */ + menu->setItemEnabled(joinMenuID, numJoin > -1); +} + + +// == various slots and functions ============================================== + +/* + * Keep the connection alive. + */ +void KBgEngineFIBS::keepAlive() +{ + emit serverString("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); +} + +/* + * Several bookkeeping operations that have to be done at the + * end of every game. Some of these may or may not be necessary + * at a particular time, but they don't hurt either. + */ +void KBgEngineFIBS::endGame() +{ + playing = false; + + emit serverString("rawwho " + infoFIBS[FIBSUser]); + + actConti->setEnabled(false); + actLeave->setEnabled(false); + + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit allowCommand(Load, false); + emit allowCommand(Undo, false); + emit allowCommand(Done, false); + emit allowCommand(Cube, false); + emit allowCommand(Roll, false); +} + +/* + * Toggle visibility of the player list + */ +void KBgEngineFIBS::showList() +{ + playerlist->isVisible() ? playerlist->hide() : playerlist->show(); +} + +/* + * Toggle visibility of the chat window + */ +void KBgEngineFIBS::showChat() +{ + chatWindow->isVisible() ? chatWindow->hide() : chatWindow->show(); +} + +/* + * Process the last move coming from the board + */ +void KBgEngineFIBS::handleMove(QString *s) +{ + lastMove = *s; + QString t = lastMove.left(1); + int moves = t.toInt(); + + emit allowCommand(Done, moves == toMove); + emit allowCommand(Undo, moves > 0); + + /* + * Allow undo and possibly start the commit timer + */ + redoPossible &= ((moves < toMove) && (undoCounter > 0)); + emit allowCommand(Redo, redoPossible); + if (moves == toMove && cl >= 0) { + emit allowMoving(false); + ct->start(cl, true); + } +} + +/* + * Done with the move + */ +void KBgEngineFIBS::done() +{ + // prevent the timer from expiring again + ct->stop(); + + // no more moves + emit allowMoving(false); + + // no more commands until it's our turn + emit allowCommand(Load, false); + emit allowCommand(Undo, false); + emit allowCommand(Done, false); + emit allowCommand(Cube, false); + emit allowCommand(Roll, false); + + // Transform the string to FIBS cormat + lastMove.replace(0, 2, "move "); + lastMove.replace(pat[PlsChar], "-"); + + // sent it to the server + emit serverString(lastMove); +} + +/* + * Undo the last move + */ +void KBgEngineFIBS::undo() +{ + ct->stop(); + + redoPossible = true; + ++undoCounter; + + emit allowMoving(true); + emit allowCommand(Done, false); + emit allowCommand(Redo, true); + emit undoMove(); +} + +/* + * Redo the last undone move + */ +void KBgEngineFIBS::redo() +{ + --undoCounter; + emit redoMove(); +} + +/* + * Double the cube - coming from the board + */ +void KBgEngineFIBS::doubleCube(const int w) +{ + if (playing && w == US) cube(); +} + +/* + * Roll the dice - coming from the board + */ +void KBgEngineFIBS::rollDice(const int w) +{ + if (playing && w == US) roll(); +} + +/* + * This engine passes all commands unmodified to the server + */ +void KBgEngineFIBS::handleCommand(QString const &cmd) +{ + emit serverString(cmd); +} + +/* + * If we have a connection, we don't quit right away + */ +bool KBgEngineFIBS::queryClose() +{ + if (connection->state() == QSocket::Idle) + return true; + + switch (KMessageBox::warningYesNoCancel((QWidget *)parent(),i18n("Still connected. Log out first?"),QString::null,i18n("Log Out"), i18n("Stay Connected"))) { + case KMessageBox::Yes : + disconnectFIBS(); + return true; + case KMessageBox::No : + return true; + default: // cancel + return false; + } +} + +/* + * If we have a connection, we don't quit right away + */ +bool KBgEngineFIBS::queryExit() +{ + if( kapp->sessionSaving()) + return true; + if (connection->state() != QSocket::Idle) + disconnectFIBS(); + return true; +} + +/* + * This displays a copy of personal messages in the main window. + * Normally, these only get displayed in the chat window. + */ +void KBgEngineFIBS::personalMessage(const QString &msg) +{ + if (showMsg) + emit infoText(msg); +} + + +// == slots and functions for FIBS commands ==================================== + +/* + * Accept the offer + */ +void KBgEngineFIBS::accept() +{ + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit serverString("accept"); +} + +/* + * Reject the offer + */ +void KBgEngineFIBS::reject() +{ + actAccept->setEnabled(false); + actReject->setEnabled(false); + + emit serverString("reject"); +} + +/* + * Continue a multi game match + */ +void KBgEngineFIBS::match_conti() +{ + actConti->setEnabled(false); + actLeave->setEnabled(false); + + emit serverString("join"); +} + +/* + * Leave a multi game match + */ +void KBgEngineFIBS::match_leave() +{ + actConti->setEnabled(false); + actLeave->setEnabled(false); + + emit serverString("leave"); +} + +/* + * Go away from the server for a little while. Offer the last know away + * message as a default to the user. + */ +void KBgEngineFIBS::away() +{ + bool ret; + QString msg = KLineEditDlg::getText(i18n("Please type the message that should be displayed to other\n" + "users while you are away."), + lastAway, &ret, (QWidget *)parent()); + if (ret) { + lastAway = msg; + emit serverString("away " + msg); + actAway->setEnabled(false); + } +} + +/* + * Toggle being ready for games + */ +void KBgEngineFIBS::toggle_ready() +{ + emit serverString("toggle ready"); +} + +/* + * Toggle the use of greedy bearoffs + */ +void KBgEngineFIBS::toggle_greedy() +{ + emit serverString("toggle greedy"); +} + +/* + * Toggle whether we will be asked to double/roll or not + */ +void KBgEngineFIBS::toggle_double() +{ + emit serverString("toggle double"); +} + +/* + * Toggle whether we want to see details on rating computations + */ +void KBgEngineFIBS::toggle_ratings() +{ + emit serverString("toggle ratings"); +} + +/* + * Come back after being away. + */ +void KBgEngineFIBS::back() +{ + emit serverString("back"); +} + +/* + * Double the cube + */ +void KBgEngineFIBS::cube() +{ + emit serverString("double"); +} + +/* + * Roll the dice + */ +void KBgEngineFIBS::roll() +{ + emit serverString("roll"); +} + +/* + * Reload the board + */ +void KBgEngineFIBS::load() +{ + emit serverString("board"); +} + +/* + * Handle the menu short cuts for joining. This is not as pretty as it + * could or should be, but it works and is easy to understand. + */ +void KBgEngineFIBS::join(const QString &msg) +{ + emit serverString("join " + msg.left(msg.find('('))); +} +void KBgEngineFIBS::join_0() { join(actJoin[0]->text()); } +void KBgEngineFIBS::join_1() { join(actJoin[1]->text()); } +void KBgEngineFIBS::join_2() { join(actJoin[2]->text()); } +void KBgEngineFIBS::join_3() { join(actJoin[3]->text()); } +void KBgEngineFIBS::join_4() { join(actJoin[4]->text()); } +void KBgEngineFIBS::join_5() { join(actJoin[5]->text()); } +void KBgEngineFIBS::join_6() { join(actJoin[6]->text()); } +void KBgEngineFIBS::join_7() { join(actJoin[7]->text()); } + + +// == invitation handling ====================================================== + +/* + * Show the invitation dialog and set the name to player + */ +void KBgEngineFIBS::inviteDialog() +{ + fibsRequestInvitation(""); +} + +/* + * Show the invitation dialog and set the name to player + */ +void KBgEngineFIBS::fibsRequestInvitation(const QString &player) +{ + if (!invitationDlg) { + QString p = player; + invitationDlg = new KBgInvite("invite"); + connect(invitationDlg, SIGNAL(inviteCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(invitationDlg, SIGNAL(dialogDone()), this, SLOT(invitationDone())); + } + invitationDlg->setPlayer(player); + invitationDlg->show(); +} + +/* + * Finish off the invitation dialog + */ +void KBgEngineFIBS::invitationDone() +{ + delete invitationDlg; + invitationDlg = 0; +} + + +// == connection handling ====================================================== + +/* + * Establish a connection to the server and log in if the parameter login + * is true. + */ +void KBgEngineFIBS::connectFIBS() +{ + /* + * Make sure the connection parameter are properly set. + */ + if (!queryConnection(false)) + return; + + conAction->setEnabled(false); + newAction->setEnabled(false); + disAction->setEnabled(false); + + /* + * Connect + */ + emit infoText(i18n("Looking up %1").arg(infoFIBS[FIBSHost])); + connection->connectToHost(infoFIBS[FIBSHost], infoFIBS[FIBSPort].toUShort()); + + return; +} + +/* + * Hostname has been resolved. + */ +void KBgEngineFIBS::hostFound() +{ + emit infoText(i18n("Connecting to %1").arg(infoFIBS[FIBSHost])); +} + +/* + * An error has occurred. Reset and inform the user. + */ +void KBgEngineFIBS::connError(int f) +{ + switch (f) { + case QSocket::ErrConnectionRefused: + emit infoText(i18n("Error, connection has been refused")); + break; + case QSocket::ErrHostNotFound: + emit infoText(i18n("Error, nonexistent host or name server down.")); + break; + case QSocket::ErrSocketRead: + emit infoText(i18n("Error, reading data from socket")); + break; + } + connectionClosed(); + return; +} + +void KBgEngineFIBS::readData() +{ + QString line; + while(connection->canReadLine()) { + line = connection->readLine(); + if (line.length() > 2) { + line.truncate(line.length()-2); + handleServerData(line); + } + } +} + +/* + * Transmit the string s to the server + */ +void KBgEngineFIBS::sendData(const QString &s) +{ + connection->writeBlock((s+"\r\n").latin1(),2+s.length()); +} + +/* + * Connection has been established. Log in and update the menus & actions. + */ +void KBgEngineFIBS::connected() +{ + conAction->setEnabled(false); + newAction->setEnabled(false); + disAction->setEnabled(true); + + menu->setItemEnabled( cmdMenuID, true); + menu->setItemEnabled(respMenuID, true); + menu->setItemEnabled(optsMenuID, true); + + /* + * Initialize the rx state machine + */ + rxStatus = RxConnect; + rxCollect = ""; + + /* + * Depending on whether somebody else wants to handle the login or not + */ + if (login) { + + /* + * Make sure the player list is empty when the whole list comes + * right after login + */ + playerlist->clear(); + + /* + * Login, using the autologin feature of FIBS, before we even receive anything. + */ + QString entry; + entry.setNum(CLIP_VERSION); + emit serverString(QString("login ") + PROG_NAME + "-" + PROG_VERSION + " " + entry + " " + + infoFIBS[FIBSUser] + " " + infoFIBS[FIBSPswd]); + + } else { + + emit serverString("guest"); + login = true; + + } + + /* + * Some visual feedback and done + */ + emit infoText(i18n("Connected") + "<br>"); +} + +/* + * Create a new account on FIBS. Obviously, this will also create + * a connection. The actual login is handled in the message parsing + * state machine. + */ +void KBgEngineFIBS::newAccount() +{ + if (!queryConnection(true)) + return; + + rxStatus = RxNewLogin; + rxCollect = ""; + login = false; + connectFIBS(); +} + +/* + * Send a disconnection request to the server. The server will disconnect + * and we will receive a connectionClosed() signal. + */ +void KBgEngineFIBS::disconnectFIBS() +{ + // send two lines in case we are stuck in the login phase + emit serverString("quit"); + emit serverString("quit"); +} + +/* + * Connection to the server is closed for some (unknown) reason. Delete + * the connection object and get the actions into a proper state. + */ +void KBgEngineFIBS::connectionClosed() +{ + /* + * Read remaining input + */ + readData(); + + /* + * Flush whatever is left in the rxBuffer and send a note + */ + emit infoText(rxCollect + "<br><hr>"); + emit infoText(i18n("Disconnected.") + "<br>"); + + conAction->setEnabled(true); + newAction->setEnabled(true); + disAction->setEnabled(false); + + menu->setItemEnabled(joinMenuID, false); + menu->setItemEnabled( cmdMenuID, false); + menu->setItemEnabled(respMenuID, false); + menu->setItemEnabled(optsMenuID, false); +} + +/* + * To establish a connection, we need to query the server name, the port + * number, the login and the password. + */ +bool KBgEngineFIBS::queryConnection(const bool newlogin) +{ + QString text, msg; + bool first, ret = true; + + /* + * query the connection parameter + */ + if (newlogin || infoFIBS[FIBSHost].isEmpty()) { + + msg = KLineEditDlg::getText(i18n("Enter the name of the server you want to connect to.\n" + "This should almost always be \"fibs.com\"."), + infoFIBS[FIBSHost], &ret, (QWidget *)parent()); + + if (ret) + infoFIBS[FIBSHost] = msg; + else + return false; + + } + if (newlogin || infoFIBS[FIBSPort].isEmpty()) { + + msg = KLineEditDlg::getText(i18n("Enter the port number on the server. " + "It should almost always be \"4321\"."), + infoFIBS[FIBSPort], &ret, (QWidget *)parent()); + + if (ret) + infoFIBS[FIBSPort] = msg; + else + return false; + } + if (newlogin || infoFIBS[FIBSUser].isEmpty()) { + + if (newlogin) + + text = i18n("Enter the login you would like to use on the server %1. The login may not\n" + "contain spaces or colons. If the login you choose is not available, you'll later be\n" + "given the opportunity to pick another one.\n\n").arg(infoFIBS[FIBSHost]); + + else + + text = i18n("Enter your login on the server %1. If you don't have a login, you\n" + "should create one using the corresponding menu option.\n\n").arg(infoFIBS[FIBSHost]); + + + first = true; + do { + msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret, + (QWidget *)parent())).stripWhiteSpace(); + if (first) { + text += i18n("The login may not contain spaces or colons!"); + first = false; + } + + } while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':'))); + + if (ret) + infoFIBS[FIBSUser] = msg; + else + return false; + } + if (newlogin || infoFIBS[FIBSPswd].isEmpty()) { + + if (newlogin) + + text = i18n("Enter the password you would like to use with the login %1\n" + "on the server %2. It may not contain colons.\n\n"). + arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]); + + else + + text = i18n("Enter the password for the login %1 on the server %2.\n\n"). + arg(infoFIBS[FIBSUser]).arg(infoFIBS[FIBSHost]); + + first = true; + do { + QCString password; + if (newlogin) + ret = (KPasswordDialog::getNewPassword(password, text) == KPasswordDialog::Accepted); + else + ret = (KPasswordDialog::getPassword(password, text) == KPasswordDialog::Accepted); + + password.stripWhiteSpace(); + msg = password; + + if (first) { + text += i18n("The password may not contain colons or spaces!"); + first = false; + } + + } while (ret && (msg.isEmpty() || msg.contains(' ') || msg.contains(':'))); + + if (ret) + infoFIBS[FIBSPswd] = msg; + else + return false; + } + + /* + * Made it here, all parameters acquired + */ + return true; +} + + +// == message parsing ========================================================== + +/* + * Pattern setup - rather long and boring + */ +void KBgEngineFIBS::initPattern() +{ + QString pattern; + + /* + * Initialize the search pattern array + */ + pat[Welcome] = QRegExp(pattern.sprintf("^%d ", CLIP_WELCOME)); + pat[OwnInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_OWN_INFO)); + pat[WhoInfo] = QRegExp(pattern.sprintf("^%d ", CLIP_WHO_INFO)); + pat[WhoEnde] = QRegExp(pattern.sprintf("^%d$", CLIP_WHO_END)); + pat[MotdBeg] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_BEGIN)); + pat[MotdEnd] = QRegExp(pattern.sprintf("^%d" , CLIP_MOTD_END)); + pat[MsgPers] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE)); + pat[MsgDeli] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_DELIVERED)); + pat[MsgSave] = QRegExp(pattern.sprintf("^%d ", CLIP_MESSAGE_SAVED)); + pat[ChatSay] = QRegExp(pattern.sprintf("^%d ", CLIP_SAYS)); + pat[ChatSht] = QRegExp(pattern.sprintf("^%d ", CLIP_SHOUTS)); + pat[ChatWis] = QRegExp(pattern.sprintf("^%d ", CLIP_WHISPERS)); + pat[ChatKib] = QRegExp(pattern.sprintf("^%d ", CLIP_KIBITZES)); + pat[SelfSay] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SAY)); + pat[SelfSht] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_SHOUT)); + pat[SelfWis] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_WHISPER)); + pat[SelfKib] = QRegExp(pattern.sprintf("^%d ", CLIP_YOU_KIBITZ)); + pat[UserLin] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGIN)); + pat[UserLot] = QRegExp(pattern.sprintf("^%d ", CLIP_LOGOUT)); + + pat[NoLogin] = QRegExp("\\*\\* Unknown command: 'login'"); + pat[BegRate] = QRegExp("^rating calculation:$"); + pat[EndRate] = QRegExp("^change for "); + pat[HTML_lt] = QRegExp("<"); + pat[HTML_gt] = QRegExp(">"); + pat[BoardSY] = QRegExp("^Value of 'boardstyle' set to 3"); + pat[BoardSN] = QRegExp("^Value of 'boardstyle' set to [^3]"); + pat[WhoisBG] = QRegExp("^Information about "); + pat[WhoisE1] = QRegExp("^ No email address\\.$"); + pat[WhoisE2] = QRegExp("^ Email address: "); + pat[SelfSlf] = QRegExp("^You say to yourself:"); + pat[Goodbye] = QRegExp("^ Goodbye\\."); + pat[GameSav] = QRegExp("The game was saved\\.$"); + pat[RawBord] = QRegExp("^board:"); + pat[YouTurn] = QRegExp("^It's your turn\\. Please roll or double"); + pat[PlsMove] = QRegExp("^Please move [1-6]+ pie"); + pat[EndWtch] = QRegExp("^You stop watching "); + pat[BegWtch] = QRegExp("^You're now watching "); + pat[BegGame] = QRegExp("^Starting a new game with "); + pat[Reload1] = QRegExp("^You are now playing with "); + pat[Reload2] = QRegExp(" has joined you. Your running match was loaded\\.$"); + pat[OneWave] = QRegExp(" waves goodbye.$"); + pat[TwoWave] = QRegExp(" waves goodbye again.$"); + pat[YouWave] = QRegExp("^You wave goodbye.$"); + pat[GameBG1] = QRegExp("start a [0-9]+ point match"); + pat[GameBG2] = QRegExp("start an unlimited match"); + pat[GameRE1] = QRegExp("are resuming their [0-9]+-point match"); + pat[GameRE2] = QRegExp("are resuming their unlimited match"); + pat[GameEnd] = QRegExp("point match against"); + pat[TabChar] = QRegExp("\\t"); + pat[PlsChar] = QRegExp("\\+"); + pat[Invite0] = QRegExp(" wants to play a [0-9]+ point match with you\\.$"); + pat[Invite1] = QRegExp("^.+ wants to play a "); + pat[Invite2] = QRegExp(" wants to resume a saved match with you\\.$"); + pat[Invite3] = QRegExp(" wants to play an unlimited match with you\\.$"); + pat[TypJoin] = QRegExp("^Type 'join "); + pat[OneName] = QRegExp("^ONE USERNAME PER PERSON ONLY!!!"); + pat[YouAway] = QRegExp("^You're away. Please type 'back'"); + pat[YouBack] = QRegExp("^Welcome back\\.$"); + pat[YouMove] = QRegExp("^It's your turn to move\\."); + pat[YouRoll] = QRegExp("^It's your turn to roll or double\\."); + pat[TwoStar] = QRegExp("^\\*\\* "); + pat[OthrNam] = QRegExp("^\\*\\* Please use another name\\. "); + pat[BoxHori] = QRegExp("^ *\\+-*\\+ *$"); + pat[BoxVer1] = QRegExp("^ *\\|"); + pat[BoxVer2] = QRegExp("\\| *$"); + pat[YourNam] = QRegExp("Your name will be "); + pat[GivePwd] = QRegExp("Please give your password:"); + pat[RetypeP] = QRegExp("Please retype your password:"); + pat[HelpTxt] = QRegExp("^NAME$"); + pat[MatchB1] = QRegExp(" has joined you for a [0-9]+ point match\\.$"); + pat[MatchB2] = QRegExp(" has joined you for an unlimited match\\.$"); + pat[EndLose] = QRegExp(" wins the [0-9]+ point match [0-9]+-[0-9]+"); + pat[EndVict] = QRegExp(" win the [0-9]+ point match [0-9]+-[0-9]+"); + pat[RejAcpt] = QRegExp("Type 'accept' or 'reject'\\.$"); + pat[YouAcpt] = QRegExp("^You accept the double\\. The cube shows [0-9]+\\."); + + pat[KeepAlv] = QRegExp("^\\*\\* Unknown command: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'"); + pat[RatingY] = QRegExp("You'll see how the rating changes are calculated\\.$"); + pat[RatingN] = QRegExp("You won't see how the rating changes are calculated\\.$"); + + // FIXME same problem as in previous line + // mpgnu accepts the double.5 arthur_tn - gnu 1 0 1243.32 365 6 983722411 adsl-61-168-141.bna.bellsouth.net - - + + // FIXME: <PLAYER> can't move. -- needs board reload... + + /* + + opponent matchlength score (your points first) + **gnu 1 0 - 0 + *blah 1 0 - 0 + kraut 1 0 - 0 + + logged in and ready ** + logged in * + otherwise " " + + */ + + pat[ConLeav] = QRegExp("^Type 'join' if you want to play the next game, type 'leave' if you don't\\.$"); + pat[GreedyY] = QRegExp("^\\*\\* Will use automatic greedy bearoffs\\."); + pat[GreedyN] = QRegExp("^\\*\\* Won't use automatic greedy bearoffs\\."); + pat[BegBlnd] = QRegExp("^\\*\\* You blind "); + pat[EndBlnd] = QRegExp("^\\*\\* You unblind "); + pat[MatchB3] = QRegExp("^\\*\\* You are now playing a [0-9]+ point match with "); + pat[MatchB4] = QRegExp("^\\*\\* You are now playing an unlimited match with "); + pat[RejCont] = QRegExp("^You reject\\. The game continues\\."); + pat[AcptWin] = QRegExp("^You accept and win "); + pat[YouGive] = QRegExp("^You give up\\."); + pat[DoubleY] = QRegExp("^\\*\\* You will be asked if you want to double\\."); + pat[DoubleN] = QRegExp("^\\*\\* You won't be asked if you want to double\\."); +} + +/* + * Parse an incoming line and notify all interested parties - first match + * decides. + */ +void KBgEngineFIBS::handleServerData(QString &line) +{ + QString rawline = line; // contains the line before it is HTML'fied + + /* + * Fix-up any HTML-like tags in the line + */ + line.replace(pat[HTML_lt], "<"); + line.replace(pat[HTML_gt], ">"); + + /* + * FIBS sometimes sends tabs, where it should send 8 spaces... + */ + line.replace(pat[TabChar], " "); + + switch (rxStatus) { + + case RxConnect: + handleMessageConnect(line, rawline); + break; + + case RxMotd: + handleMessageMotd(line); + return; + + case RxWhois: + handleMessageWhois(line); + break; + + case RxRating: + handleMessageRating(line); + break; + + case RxNewLogin: + handleMessageNewLogin(line); + break; + + case RxIgnore: + /* + * Ignore _ALL_ incoming strings - this is needed during the + * login phase, when the message box is open. + */ + break; + + case RxGoodbye: + /* + * Receive the logout sequence. The string will be flushed by the + * disconnectFIBS() callback + */ + rxCollect += QString("<font color=\"blue\"><pre>") + line + "</pre></font><br>"; + break; + + case RxNormal: + handleMessageNormal(line, rawline); + break; + + default: + /* + * This is a serious problem - latin1() is fine since the line comes from FIBS. + */ + std::cerr << "PROBLEM in KBgEngineFIBS::handleServerData: " << line.latin1() << std::endl; + } +} + +/* + * Handle messages during the RxWhois state + */ +void KBgEngineFIBS::handleMessageWhois(const QString &line) +{ + rxCollect += "<br> " + line; + if (line.contains(pat[WhoisE1]) || line.contains(pat[WhoisE2])) { + rxStatus = RxNormal; + emit infoText("<font color=\"darkgreen\">" + rxCollect + "<br></font>"); + } +} + +/* + * Handle messages during the RxRating state + */ +void KBgEngineFIBS::handleMessageRating(const QString &line) +{ + rxCollect += "<br>" + line; + if (line.contains(pat[EndRate]) && ++rxCount == 2) { + emit infoText("<font color=\"blue\">" + rxCollect + "<br></font>"); + rxStatus = RxNormal; + } +} + +/* + * Handle messages during the RxMotd state + */ +void KBgEngineFIBS::handleMessageMotd(const QString &line) +{ + if (line.contains(pat[MotdEnd])) { + rxStatus = RxNormal; + emit infoText("<font color=\"blue\"><pre>" + rxCollect + "</pre></font>"); + /* + * just to be on the safe side, we set the value of boardstyle. + * we do it here, since this is reasonably late in the login + * procedure + */ + emit serverString("set boardstyle 3"); + } else { + QString tline = line; + tline.replace(pat[BoxHori], "<br><hr>"); + tline.replace(pat[BoxVer1], ""); + tline.replace(pat[BoxVer2], ""); + rxCollect += "<br>" + tline; + } +} + +/* + * Handle messages during the RxConnect state + */ +void KBgEngineFIBS::handleMessageConnect(const QString &line, const QString &rawline) +{ + /* + * Two possibilities: either we are logged in or we sent bad password/login + */ + if (line.contains("login:")) { + /* + * This can only happen if the password/login is wrong. + */ + if (rxCollect.isEmpty()) { + rxStatus = RxIgnore; + int ret = KMessageBox::warningContinueCancel + ((QWidget *)parent(), i18n("There was a problem with " + "your login and password. " + "You can reenter\n" + "your login and password and " + "try to reconnect."), + i18n("Wrong Login/Password"), + i18n("Reconnect")); + if (ret == KMessageBox::Continue) { + infoFIBS[FIBSUser] = ""; + infoFIBS[FIBSPswd] = ""; + login = true; + connectFIBS(); // will reset the rxStatus + } else { + rxStatus = RxConnect; + emit serverString(""); + emit serverString(""); + } + return; + } + emit infoText("<hr><pre>" + rxCollect + "</pre><br>"); + rxCollect = ""; + return; + } + + /* + * Ok, we are logged in! Now receive personal information. These + * are completely useless but what the heck. + */ + if (line.contains(pat[Welcome])) { + char p[3][256]; + time_t tmp; + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%li%255s", p[0], p[1], &tmp, p[2]); + if (words >= 4) { + QDateTime d; d.setTime_t(tmp); + QString text = i18n("%1, last logged in from %2 at %3.").arg(p[1]).arg(p[2]).arg(d.toString()); + emit infoText("<hr><br>" + text); + playerlist->setName(p[1]); + } + return; + } + + /* + * Initial parsing of user options and making sure that settings needed + * by us are at the correct value. We use and ignore values according + * to the following list: + * + * p[ 0] - CLIP_OWN_INFO + * p[ 1] - name -- IGNORE + * OptAllowPip + * n[ 0] - autoboard -- IGNORE + * OptAutoDouble + * OptAutoMove + * n[ 1] - away -- IGNORE + * n[ 2] - bell -- IGNORE + * OptCrawford + * n[ 3] - double -- IGNORE + * n[ 4] - expierience -- IGNORE + * OptGreedy + * n[ 6] - moreboards -- IGNORE and set to YES + * OptMoves + * n[ 8] - notify -- IGNORE and set to YES + * rating - rating -- IGNORE + * OptRatings + * OptReady + * n[10] - redoubles -- IGNORE + * n[11] - report -- IGNORE and set to YES + * OptSilent + * p[3] - timezone + * + */ + if (line.contains(pat[OwnInfo])) { + + rxStatus = RxNormal; + + int fibsOptions[NumFIBSOpt]; + + char p[3][256]; + int n[12]; + double rating; + + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%i%i%i%i%i%i%i%i%i%i%i%i%i%lf%i%i%i%i%i%255s", + p[0], p[1], + &fibsOptions[OptAllowPip], + &n[0], + &fibsOptions[OptDouble], + &fibsOptions[OptAutoMove], // equivalent to OptDouble, can be ignored + &n[1], &n[2], + &fibsOptions[OptCrawford], + &n[3], &n[4], + &fibsOptions[OptGreedy], + &n[6], + &fibsOptions[OptMoves], + &n[8], + &rating, + &fibsOptions[OptRatings], + &fibsOptions[OptReady], + &n[10], &n[11], + &fibsOptions[OptSilent], + p[2]); + + if (words >= 22 && n[6] != 1) { + /* + * need to get boards after new dice have arrived + */ + emit infoText("<font color=\"red\">" + i18n("The moreboards toggle has been set.") + "</font>"); + emit serverString("toggle moreboards"); + } + if (words >= 22 && n[8] != 1) { + /* + * need to know who logs out + */ + emit infoText("<font color=\"red\">" + i18n("The notify toggle has been set.") + "</font>"); + emit serverString("toggle notify"); + } + if (words >= 22 && n[11] != 1) { + /* + * want to know who starts playing games + */ + emit infoText("<font color=\"red\">" + i18n("The report toggle has been set.") + "</font>"); + emit serverString("toggle report"); + } + + /* + * Set the correct toggles in the options menu + */ + fibsOpt[OptReady]->setChecked(fibsOptions[OptReady]); + fibsOpt[OptDouble]->setChecked(!fibsOptions[OptDouble]); + fibsOpt[OptRatings]->setChecked(fibsOptions[OptRatings]); + + return; + } + + /* + * The beginning of a new login procedure starts starts here + */ + if (line.contains(pat[OneName])) { + rxStatus = RxNewLogin; + emit infoText(QString("<font color=\"red\">") + rxCollect + "</font>"); + rxCollect = ""; + QString tmp = rawline; + handleServerData(tmp); + return; + } + + /* + * Still in the middle of the login sequence, still collecting information + */ + rxCollect += "<br>" + line; +} + +/* + * Handle messages during the RxNewLogin state + */ +void KBgEngineFIBS::handleMessageNewLogin(const QString &line) +{ + /* + * Request the new login + */ + if (line.contains(pat[OneName])) { + emit serverString(QString("name ") + infoFIBS[FIBSUser]); + return; + } + /* + * Ooops, user name already exists + */ + if (line.contains(pat[OthrNam])) { + QString text = i18n("The selected login is alreay in use! Please select another one."); + bool ret, first = true; + QString msg; + + do { + msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret, + (QWidget *)parent())).stripWhiteSpace(); + if (first) { + text += i18n("\n\nThe login may not contain spaces or colons!"); + first = false; + } + } while (msg.contains(' ') || msg.contains(':')); + + if (ret) { + infoFIBS[FIBSUser] = msg; + emit serverString("name " + msg); + } else + emit serverString("bye"); + + return; + } + /* + * first time we send the password + */ + if (line.contains(pat[YourNam])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * second time we send the password + */ + if (line.contains(pat[GivePwd])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * at this point we are done creating the account + */ + if (line.contains(pat[RetypeP])) { + + QString text = i18n("Your account has been created. Your new login is <u>%1</u>. To fully activate " + "this account, I will now close the connection. Once you reconnect, you can start " + "playing backgammon on FIBS.").arg(infoFIBS[FIBSUser]); + emit infoText("<br><hr><font color=\"blue\">" + text + "</font><br><hr>"); + emit serverString("bye"); + rxStatus = RxNormal; + rxCollect = ""; + return; + } + return; +} + +/* + * Handle all normal messages - during the RxNormal state + */ +void KBgEngineFIBS::handleMessageNormal(QString &line, QString &rawline) +{ + + // - ignored ---------------------------------------------------------------------- + + /* + * For now, the waves are ignored. They should probably go into + * the chat window -- but only optional + */ + if (line.contains(pat[OneWave]) || line.contains(pat[TwoWave]) || line.contains(pat[YouWave])) { + + return; + } + + /* + * These messages used to go into the games window. If KBackgammon + * ever gets a games window, they should be in there. For now, they + * are ignored. + */ + else if (line.contains(pat[GameBG1]) || line.contains(pat[GameBG2]) || line.contains(pat[GameRE1]) || + line.contains(pat[GameRE2]) || line.contains(pat[GameEnd])) { + + return; + } + + /* + * Artefact caused by the login test procedure utilized. + */ + else if (line.contains(pat[NoLogin])) { + + return; + } + + /* + * Connection keep-alive response + */ + else if (line.contains(pat[KeepAlv])) { + + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Chat and personal messages - note that the chat window sends these messages + * back to us so we can display them if the user wants that. + */ + else if (line.contains(pat[ChatSay]) || line.contains(pat[ChatSht]) || line.contains(pat[ChatWis]) || + line.contains(pat[ChatKib]) || line.contains(pat[SelfSay]) || line.contains(pat[SelfSht]) || + line.contains(pat[SelfWis]) || line.contains(pat[SelfKib]) || line.contains(pat[SelfSlf]) || + line.contains(pat[MsgPers]) || line.contains(pat[MsgDeli]) || line.contains(pat[MsgSave])) { + + emit chatMessage(line); + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Beginning of games. In all these cases we are playing and not watching. + */ + else if (line.contains(pat[MatchB1]) || line.contains(pat[MatchB2])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + } + else if (line.contains(pat[MatchB3]) || line.contains(pat[MatchB4])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * The help should be handled separately. A fairly complete implementation of a + * help parsing can be found in KFibs. + */ + else if (line.contains(pat[HelpTxt])) { + + // do nothing + } + + // -------------------------------------------------------------------------------- + + /* + * Simple cases without the need for many comments... + */ + else if (line.contains(pat[RawBord])) { + + /* + * Save the board string and create a new game state + */ + KBgStatus *st = new KBgStatus(currBoard = rawline); + + /* + * Save important state data and stop the timeout + */ + ct->stop(); + undoCounter = 0; + + pname[US ] = st->player(US); + pname[THEM] = st->player(THEM); + + playing = (QString("You") == pname[US]); + + toMove = st->moves(); + + /* + * Update the caption string + */ + if (st->turn() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - game over").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else if (st->length() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - unlimited match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else + caption = i18n("%1 (%2) vs. %3 (%4) - %5 point match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)). + arg(st->length()); + + emit statText(caption); + + /* + * Emit information and drop the state object + */ + emit allowMoving(playing && (st->turn() == US)); + emit newState(*st); + + delete st; + + /* + * Set the actions correctly + */ + emit allowCommand(Load, true ); + emit allowCommand(Undo, false); + emit allowCommand(Redo, false); + emit allowCommand(Done, false); + + return; + } + else if (line.contains(pat[PlsMove]) || line.contains(pat[YouMove])) { + + KNotifyClient::event("move", i18n("Please make your move")); + + } + + // -------------------------------------------------------------------------------- + + /* + * Being away and coming back + */ + else if (line.contains(pat[YouAway])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, true); + actBack->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu entry to join the match)"); + } + else if (line.contains(pat[YouBack])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, false); + actBack->setEnabled(false); + actAway->setEnabled(true); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the response of the user responding to double or resign + */ + else if (line.contains(pat[YouGive]) || line.contains(pat[RejCont]) || line.contains(pat[AcptWin])) { + + actAccept->setEnabled(false); + actReject->setEnabled(false); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the responses to newly set toggles + */ + else if (line.contains(pat[GreedyY]) || line.contains(pat[GreedyN])) { + + fibsOpt[OptGreedy]->setChecked(line.contains(pat[GreedyY])); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[DoubleY]) || line.contains(pat[DoubleN])) { + + fibsOpt[OptDouble]->setChecked(line.contains(pat[DoubleY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + else if (line.contains(pat[RatingY]) || line.contains(pat[RatingN])) { + + fibsOpt[OptRatings]->setChecked(line.contains(pat[RatingY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * It's our turn to roll or double + */ + else if (line.contains(pat[YouTurn]) || line.contains(pat[YouRoll])) { + + emit allowCommand(Cube, playing); + emit allowCommand(Roll, playing); + + emit statText(caption); // force a pip count recomputation by the board + + KNotifyClient::event("roll or double", i18n("It's your turn to roll the dice or double the cube")); + } + + // -------------------------------------------------------------------------------- + + /* + * Got an invitation for a match + */ + else if (line.contains(pat[Invite0]) || line.contains(pat[Invite2]) || line.contains(pat[Invite3])) { + + rxCollect = rawline.left(rawline.find(' ')); + emit serverString("rawwho " + rxCollect); + + if (line.contains(pat[Invite0])) { + rawline.replace(pat[Invite1], ""); + rawline = rxCollect + " "+ rawline.left(rawline.find(' ')); + } else if (line.contains(pat[Invite2])) { + rawline = rxCollect + " r"; + } else if (line.contains(pat[Invite3])) { + invitations += rxCollect + " u"; + } + invitations += rawline; + return; // will be printed once the rawwho is received + } + + // - rx status changes ------------------------------------------------------------ + + else if (line.contains(pat[WhoisBG])) { + rxStatus = RxWhois; + rxCollect = QString("<br><u>") + line + "</u>"; + return; + } + else if (line.contains(pat[MotdBeg])) { + rxStatus = RxMotd; + rxCollect = ""; + return; + } + else if (line.contains(pat[BegRate])) { + rxStatus = RxRating; + rxCount = 0; + rxCollect = "<br>" + line; + return; + } + else if (line.contains(pat[Goodbye])) { + rxStatus = RxGoodbye; + rxCollect = "<br><hr><br>"; + handleServerData(rawline); // danger: recursion! + return; + } + + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + + /* + * Continue a mutli game match? We have to either leave or continue + */ + else if (line.contains(pat[ConLeav])) { + actConti->setEnabled(true); + actLeave->setEnabled(true); + line.append("<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to leave or continue the match)")); + } + /* + * Beginning and end of user updates + */ + else if (line.contains(pat[WhoInfo])) { + rawline.replace(pat[WhoInfo], ""); + if (rawline.contains(QRegExp("^" + infoFIBS[FIBSUser] + " "))) { + int ready; + // Using latin1() is fine, since the string is coming from FIBS. + sscanf(rawline.latin1(), "%*s %*s %*s %i %*s %*s %*s %*s %*s %*s %*s %*s", &ready); + fibsOpt[OptReady]->setChecked(ready); + } + emit fibsWhoInfo(rawline); + return; + } + else if (line.contains(pat[WhoEnde])) { + emit fibsWhoEnd(); + return; + } + /* + * This message is ignored. The instruction is given elsewhere (and slightly + * delayed in the flow of time). + */ + if (line.contains(pat[TypJoin])) { + return; + } + /* + * Watching other players + */ + else if (line.contains(pat[BegWtch])) { + emit allowCommand(Load, true); + rawline.replace(pat[BegWtch], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + load(); + } + else if (line.contains(pat[EndWtch])) { + emit gameOver(); + } + /* + * Blinding of players, the actual blind is handled by + * the player list + */ + else if (line.contains(pat[BegBlnd])) { + rawline.replace(pat[BegBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, true); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[EndBlnd])) { + rawline.replace(pat[EndBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, false); + line = "<font color=\"red\">" + line + "</font>"; + } + /* + * Starting or reloading games or matches + */ + else if (line.contains(pat[BegGame])) { + rawline.replace(pat[BegGame], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + else if (line.contains(pat[Reload1])) { + rawline.replace(pat[Reload1], ""); + rawline = rawline.left(rawline.find(' ')); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + else if (line.contains(pat[Reload2])) { + rawline.replace(pat[Reload2], ""); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + /* + * Opponent offered resignation or the cube. We have to accept + * or reject the offer. + */ + else if (line.contains(pat[RejAcpt])) { + actAccept->setEnabled(true); + actReject->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to accept or reject the offer)"); + } + /* + * This is strange: FIBS seems to not send a newline at the + * end of this pattern. So we work around that. + */ + else if (line.contains(pat[YouAcpt])) { + actAccept->setEnabled(false); + actReject->setEnabled(false); + rawline.replace(pat[YouAcpt], ""); + line.truncate(line.length()-rawline.length()); + if (!rawline.stripWhiteSpace().isEmpty()) { + handleServerData(rawline); + } + } + /* + * Ending of games + */ + else if (line.contains(pat[EndLose])) { + if (playing) { + KNotifyClient::event("game over l", i18n("Sorry, you lost the game.")); + if (useAutoMsg[MsgLos] && !autoMsg[MsgLos].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgLos]); + } + emit gameOver(); + } + else if (line.contains(pat[EndVict])) { + if (playing) { + KNotifyClient::event("game over w", i18n("Congratulations, you won the game!")); + if (useAutoMsg[MsgWin] && !autoMsg[MsgWin].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgWin]); + } + emit gameOver(); + } + else if (line.contains(pat[GameSav])) { + emit gameOver(); + } + /* + * User logs out. This has to be signalled to the player + * list. Get the true user names by working on the rawline. + */ + else if (line.contains(pat[UserLot])) { + rawline.replace(pat[UserLot], ""); + emit fibsLogout(rawline.left(rawline.find(' '))); + return; + } + /* + * Emit the name of the newly logged in user. + */ + else if (line.contains(pat[UserLin])) { + rawline.replace(pat[UserLin], ""); + emit fibsLogin(rawline.left(rawline.find(' '))); + return; + } + /* + * Special attention has to be paid to the proper setting of + * the 'boardstyle' variable, since we will not be able to display + * the board properly without it. + */ + else if (line.contains(pat[BoardSY])) { + // ignored + return; + } + else if (line.contains(pat[BoardSN])) { + emit serverString("set boardstyle 3"); + emit infoText(QString("<font color=\"red\"><br>") + + i18n("You should never set the 'boardstyle' variable " + "by hand! It is vital for proper functioning of " + "this program that it remains set to 3. It has " + "been reset for you.") + + "<br></font>"); + return; + } + /* + * This is the final fall through: if the line started with ** and + * hasn't been processed, make it red, since it is a server resp. + * to something we just did. + */ + else if (line.contains(pat[TwoStar])) { + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * Print whatever part of the line made it here + */ + emit infoText(line); +} + +// EOF + + +// == constructor, destructor and setup ======================================== + +/* + * Constructor + */ +KBgEngineFIBS::KBgEngineFIBS(QWidget *parent, QString *name, QPopupMenu *pmenu) + : KBgEngine(parent, name, pmenu) +{ + /* + * No connection, not playing, ready for login + */ + connection = new QSocket(parent, "fibs connection"); + playing = false; + login = true; + + connect(connection, SIGNAL(hostFound()), this, SLOT(hostFound())); + connect(connection, SIGNAL(connected()), this, SLOT(connected())); + connect(connection, SIGNAL(error(int)), this, SLOT(connError(int))); + connect(connection, SIGNAL(connectionClosed()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(delayedCloseFinished()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(readyRead()), this, SLOT(readData())); + + connect(this, SIGNAL(serverString(const QString &)), this, SLOT(sendData(const QString &))); + + /* + * No invitation dialog + */ + invitationDlg = 0; + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), this, SLOT(changeJoin(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), this, SLOT(cancelJoin(const QString &))); + connect(this, SIGNAL(gameOver()), this, SLOT(endGame())); + + /* + * Creating, initializing and connecting the player list + */ + playerlist = new KFibsPlayerList(0, "fibs player list"); + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), playerlist, SLOT(changePlayer(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), playerlist, SLOT(deletePlayer(const QString &))); + connect(this, SIGNAL(fibsWhoEnd()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(fibsConnectionClosed()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(changePlayerStatus(const QString &, int, bool)), + playerlist, SLOT(changePlayerStatus(const QString &, int, bool))); + connect(playerlist, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(playerlist, SIGNAL(fibsInvite(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + + /* + * Create, initialize and connect the chat window + */ + chatWindow = new KBgChat(0, "chat window"); + + connect(this, SIGNAL(chatMessage(const QString &)), chatWindow, SLOT(handleData(const QString &))); + connect(this, SIGNAL(fibsStartNewGame(const QString &)), chatWindow, SLOT(startGame(const QString &))); + connect(this, SIGNAL(gameOver()), chatWindow, SLOT(endGame())); + connect(this, SIGNAL(fibsLogout (const QString &)), chatWindow, SLOT(deletePlayer(const QString &))); + connect(chatWindow, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(chatWindow, SIGNAL(fibsRequestInvitation(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + connect(chatWindow, SIGNAL(personalMessage(const QString &)), this, SLOT(personalMessage(const QString &))); + connect(playerlist, SIGNAL(fibsTalk(const QString &)), chatWindow, SLOT(fibsTalk(const QString &))); + + /* + * Creating, initializing and connecting the menu + * ---------------------------------------------- + */ + respMenu = new QPopupMenu(); + joinMenu = new QPopupMenu(); + cmdMenu = new QPopupMenu(); + optsMenu = new QPopupMenu(); + + /* + * Initialize the FIBS submenu - this is also put in the play menu + */ + conAction = new KAction(i18n("&Connect"), 0, this, SLOT( connectFIBS()), this); + newAction = new KAction(i18n("New Account"), 0, this, SLOT( newAccount()), this); + disAction = new KAction(i18n("&Disconnect"), 0, this, SLOT(disconnectFIBS()), this); + + conAction->setEnabled(true ); conAction->plug(menu); + disAction->setEnabled(false); disAction->plug(menu); + newAction->setEnabled(true ); newAction->plug(menu); + + menu->insertSeparator(); + + (invAction = new KAction(i18n("&Invite..."), 0, this, SLOT(inviteDialog()), this))->plug(menu); + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + cmdMenuID = menu->insertItem(i18n("&Commands"), cmdMenu); { + + (actAway = new KAction(i18n("Away"), 0, this, SLOT(away()), this))->plug(cmdMenu); + (actBack = new KAction(i18n("Back"), 0, this, SLOT(back()), this))->plug(cmdMenu); + + actAway->setEnabled(true); + actBack->setEnabled(false); + } + + /* + * Create the server side options. This is preliminary and needs more work. + * The available options are skewed, since they refelect the needs of the + * author. Contact jens@hoefkens.com if your favorite option is missing. + */ + optsMenuID = menu->insertItem(i18n("&Options"), optsMenu); { + + for (int i = 0; i < NumFIBSOpt; i++) + fibsOpt[i] = 0; + + fibsOpt[OptReady] = new KToggleAction(i18n("Ready to Play"), + 0, this, SLOT(toggle_ready()), this); + fibsOpt[OptRatings] = new KToggleAction(i18n("Show Rating Computations"), + 0, this, SLOT(toggle_ratings()), this); + fibsOpt[OptRatings]->setCheckedState(i18n("Hide Rating Computations")); + fibsOpt[OptGreedy] = new KToggleAction(i18n("Greedy Bearoffs"), + 0, this, SLOT(toggle_greedy()), this); + fibsOpt[OptDouble] = new KToggleAction(i18n("Ask for Doubles"), + 0, this, SLOT(toggle_double()), this); + + for (int i = 0; i < NumFIBSOpt; i++) + if (fibsOpt[i]) + fibsOpt[i]->plug(optsMenu); + + } + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + respMenuID = menu->insertItem(i18n("&Response"), respMenu); { + + (actAccept = new KAction(i18n("Accept"), 0, this, SLOT(accept()), this))->plug(respMenu); + (actReject = new KAction(i18n("Reject"), 0, this, SLOT(reject()), this))->plug(respMenu); + + actAccept->setEnabled(false); + actReject->setEnabled(false); + + respMenu->insertSeparator(); + + (actConti = new KAction(i18n("Join"), 0, this, SLOT(match_conti()), this))->plug(respMenu); + (actLeave = new KAction(i18n("Leave"), 0, this, SLOT(match_leave()), this))->plug(respMenu); + + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + + /* + * Create the join menu and do not fill it (this happens at first + * action setup. + */ + joinMenuID = menu->insertItem(i18n("&Join"), joinMenu); { + numJoin = -1; + + actJoin[0] = new KAction("", 0, this, SLOT(join_0()), this); + actJoin[1] = new KAction("", 0, this, SLOT(join_1()), this); + actJoin[2] = new KAction("", 0, this, SLOT(join_2()), this); + actJoin[3] = new KAction("", 0, this, SLOT(join_3()), this); + actJoin[4] = new KAction("", 0, this, SLOT(join_4()), this); + actJoin[5] = new KAction("", 0, this, SLOT(join_5()), this); + actJoin[6] = new KAction("", 0, this, SLOT(join_6()), this); + actJoin[7] = new KAction("", 0, this, SLOT(join_7()), this); + } + + menu->setItemEnabled(joinMenuID, false); + menu->setItemEnabled( cmdMenuID, false); + menu->setItemEnabled(respMenuID, false); + menu->setItemEnabled(optsMenuID, false); + + /* + * Continue with the FIBS menu + */ + menu->insertSeparator(); + + (listAct = new KToggleAction(i18n("&Player List"), 0, this, SLOT(showList()), this))->plug(menu); + (chatAct = new KToggleAction(i18n("&Chat"), 0, this, SLOT(showChat()), this))->plug(menu); + + connect(playerlist, SIGNAL(windowVisible(bool)), listAct, SLOT(setChecked(bool))); + connect(chatWindow, SIGNAL(windowVisible(bool)), chatAct, SLOT(setChecked(bool))); + + /* + * Create message IDs. This sets up a lot of regular expressions. + */ + initPattern(); + + /* + * Restore old settings + */ + readConfig(); + + // FIXME: open the child windows in start() and not here + + /* + * Update the menu actions + */ + listAct->setChecked(playerlist->isVisible()); + chatAct->setChecked(chatWindow->isVisible()); + + /* + * Initialize the keepalive timer FIXME: make this a setting + */ + keepalive = true; + + // FIXME: move the start to connect... + + keepaliveTimer = new QTimer(this); + connect(keepaliveTimer, SIGNAL(timeout()), this, SLOT(keepAlive())); + keepaliveTimer->start(1200000); +} + +/* + * Destructor deletes child objects if necessary + */ +KBgEngineFIBS::~KBgEngineFIBS() +{ + delete joinMenu; + delete respMenu; + delete cmdMenu; + delete optsMenu; + + delete connection; + delete invitationDlg; + + delete playerlist; + delete chatWindow; +} + + |