summaryrefslogtreecommitdiffstats
path: root/kbackgammon
diff options
context:
space:
mode:
authortoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
committertoma <toma@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2009-11-25 17:56:58 +0000
commitc90c389a8a8d9d8661e9772ec4144c5cf2039f23 (patch)
tree6d8391395bce9eaea4ad78958617edb20c6a7573 /kbackgammon
downloadtdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.tar.gz
tdegames-c90c389a8a8d9d8661e9772ec4144c5cf2039f23.zip
Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features.
BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegames@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kbackgammon')
-rw-r--r--kbackgammon/AUTHORS1
-rw-r--r--kbackgammon/Makefile.am21
-rw-r--r--kbackgammon/README43
-rw-r--r--kbackgammon/TODO11
-rw-r--r--kbackgammon/engines/Makefile.am16
-rw-r--r--kbackgammon/engines/fibs/Makefile.am9
-rw-r--r--kbackgammon/engines/fibs/clip.h39
-rw-r--r--kbackgammon/engines/fibs/kbgfibs.cpp2314
-rw-r--r--kbackgammon/engines/fibs/kbgfibs.h479
-rw-r--r--kbackgammon/engines/fibs/kbgfibschat.cpp828
-rw-r--r--kbackgammon/engines/fibs/kbgfibschat.h273
-rw-r--r--kbackgammon/engines/fibs/kbginvite.cpp185
-rw-r--r--kbackgammon/engines/fibs/kbginvite.h113
-rw-r--r--kbackgammon/engines/fibs/kplayerlist.cpp902
-rw-r--r--kbackgammon/engines/fibs/kplayerlist.h298
-rw-r--r--kbackgammon/engines/generic/Makefile.am8
-rw-r--r--kbackgammon/engines/generic/kbgengine.cpp62
-rw-r--r--kbackgammon/engines/generic/kbgengine.h298
-rw-r--r--kbackgammon/engines/gnubg/Makefile.am9
-rw-r--r--kbackgammon/engines/gnubg/kbggnubg.cpp710
-rw-r--r--kbackgammon/engines/gnubg/kbggnubg.h223
-rw-r--r--kbackgammon/engines/nextgen/Makefile.am9
-rw-r--r--kbackgammon/engines/nextgen/kbggame.cpp47
-rw-r--r--kbackgammon/engines/nextgen/kbggame.h57
-rw-r--r--kbackgammon/engines/nextgen/kbgng.cpp622
-rw-r--r--kbackgammon/engines/nextgen/kbgng.h263
-rw-r--r--kbackgammon/engines/nextgen/kbgplayer.cpp62
-rw-r--r--kbackgammon/engines/nextgen/kbgplayer.h58
-rw-r--r--kbackgammon/engines/offline/Makefile.am9
-rw-r--r--kbackgammon/engines/offline/kbgoffline.cpp810
-rw-r--r--kbackgammon/engines/offline/kbgoffline.h213
-rw-r--r--kbackgammon/eventsrc802
-rw-r--r--kbackgammon/icons/Makefile.am3
-rw-r--r--kbackgammon/icons/hi128-app-kbackgammon.pngbin0 -> 7621 bytes
-rw-r--r--kbackgammon/icons/hi16-app-kbackgammon.pngbin0 -> 841 bytes
-rw-r--r--kbackgammon/icons/hi16-app-kbackgammon_engine.pngbin0 -> 1131 bytes
-rw-r--r--kbackgammon/icons/hi22-app-kbackgammon.pngbin0 -> 3914 bytes
-rw-r--r--kbackgammon/icons/hi32-app-kbackgammon.pngbin0 -> 1875 bytes
-rw-r--r--kbackgammon/icons/hi32-app-kbackgammon_engine.pngbin0 -> 2192 bytes
-rw-r--r--kbackgammon/icons/hi48-app-kbackgammon.pngbin0 -> 3359 bytes
-rw-r--r--kbackgammon/icons/hi48-app-kbackgammon_engine.pngbin0 -> 4046 bytes
-rw-r--r--kbackgammon/icons/hi64-app-kbackgammon.pngbin0 -> 3704 bytes
-rw-r--r--kbackgammon/icons/hi64-app-kbackgammon_engine.pngbin0 -> 6166 bytes
-rw-r--r--kbackgammon/kbackgammon.desktop75
-rw-r--r--kbackgammon/kbackgammonui.rc51
-rw-r--r--kbackgammon/kbg.cpp830
-rw-r--r--kbackgammon/kbg.h228
-rw-r--r--kbackgammon/kbgboard.cpp2918
-rw-r--r--kbackgammon/kbgboard.h967
-rw-r--r--kbackgammon/kbgstatus.cpp544
-rw-r--r--kbackgammon/kbgstatus.h310
-rw-r--r--kbackgammon/kbgtextview.cpp104
-rw-r--r--kbackgammon/kbgtextview.h81
-rw-r--r--kbackgammon/main.cpp67
-rw-r--r--kbackgammon/pics/Makefile.am6
-rw-r--r--kbackgammon/pics/kbackgammon-chat.pngbin0 -> 292 bytes
-rw-r--r--kbackgammon/pics/kbackgammon-double.xpm29
-rw-r--r--kbackgammon/sounds/Makefile.am8
-rw-r--r--kbackgammon/sounds/kbackgammon-lost.wavbin0 -> 134188 bytes
-rw-r--r--kbackgammon/sounds/kbackgammon-move.wavbin0 -> 62364 bytes
-rw-r--r--kbackgammon/sounds/kbackgammon-roll.wavbin0 -> 7522 bytes
-rw-r--r--kbackgammon/sounds/kbackgammon-won.wavbin0 -> 82904 bytes
-rw-r--r--kbackgammon/version.h32
63 files changed, 16047 insertions, 0 deletions
diff --git a/kbackgammon/AUTHORS b/kbackgammon/AUTHORS
new file mode 100644
index 00000000..65b967eb
--- /dev/null
+++ b/kbackgammon/AUTHORS
@@ -0,0 +1 @@
+Jens Hoefkens <jens@hoefkens.com>
diff --git a/kbackgammon/Makefile.am b/kbackgammon/Makefile.am
new file mode 100644
index 00000000..6f21f2ca
--- /dev/null
+++ b/kbackgammon/Makefile.am
@@ -0,0 +1,21 @@
+SUBDIRS = pics sounds icons engines
+
+INCLUDES = -I$(top_srcdir)/libkdegames -I$(top_srcdir)/libkdegames/kgame/ -I$(srcdir)/engines $(all_includes)
+METASOURCES = AUTO
+
+bin_PROGRAMS = kbackgammon
+kbackgammon_SOURCES = main.cpp kbg.cpp kbgboard.cpp kbgtextview.cpp \
+ kbgstatus.cpp
+kbackgammon_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+kbackgammon_LDADD = $(LIB_KDEGAMES) $(LIB_KDEPRINT) ./engines/libkbgengines.la
+kbackgammon_DEPENDENCIES = $(LIB_KDEGAMES_DEP)
+
+xdg_apps_DATA = kbackgammon.desktop
+
+rcdir = $(kde_datadir)/kbackgammon
+rc_DATA = kbackgammonui.rc eventsrc
+
+messages: rc.cpp
+ LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.c -o -name \*.ecpp -o -name \*.C`; \
+ $(XGETTEXT) $$LIST -o $(podir)/kbackgammon.pot
+
diff --git a/kbackgammon/README b/kbackgammon/README
new file mode 100644
index 00000000..6d818f2b
--- /dev/null
+++ b/kbackgammon/README
@@ -0,0 +1,43 @@
+This file describes KBackgammon
+-------------------------------
+
+
+KBackgammon is a backgammon program for KDE2. It is based on the code,
+ideas and concepts of KFibs (which is a FIBS client for KDE1). For a
+short time, KBackgammon was called bacKgammon (if you know somebody
+who is still using bacKgammon, please force them to upgrade :-)).
+
+
+KBackgammon is a backgammon program built around a graphical backgammon
+board. Since it uses moular backgammon engines, it can easily be extended
+beyond the current set of engines. At the moment, the following types of
+backgammon games are supported:
+
+ FIBS - online games on the First Internet Backgammon Server.
+ the computer handles the network connection and
+ translates the textual messages from the server into
+ graphical representations. this engine offers separate
+ chat window and player list window to simplify the
+ interaction with the server
+
+ Offline - play against yourself or a freind that is sitting next
+ to you. the most important role of the computer is rolling
+ the dice.
+
+In the near to mid future, the program will be extended with the following
+two kinds of engines:
+
+ GNUBg - the GNU backgammon program is a powerful neural network
+ and KBackgammon will soon allow you to play against it
+ from the convinience of your KDE desktop.
+
+ NextGen - this extension of the offline engine will allow you to play
+ against other players on the network. the computer makes
+ sure that the dice are fair and it handles the netwok
+ communication.
+
+If you lust for other kinds of engines (besides FIBS, there are other
+internet servers out there and it somebody might want to have access
+to these as well), please contact the Jens Hoefkens <jens@hoefkens.com>
+and make your wishes heard.
+
diff --git a/kbackgammon/TODO b/kbackgammon/TODO
new file mode 100644
index 00000000..616fe58d
--- /dev/null
+++ b/kbackgammon/TODO
@@ -0,0 +1,11 @@
+FIBS engine
+-----------
+
+- add the accumulated online time in the lower right corner of the
+ main window status bar
+- add user profiles with different username, etc.
+- port the FIBS help system from KFibs
+- add buttons for accept and reject ?
+- automatically translate messages --> replace \"a by ae, etc ?
+- toggle double is NOT automatically set at the beginning of 1 point games!
+- clean the header file
diff --git a/kbackgammon/engines/Makefile.am b/kbackgammon/engines/Makefile.am
new file mode 100644
index 00000000..e599d9b2
--- /dev/null
+++ b/kbackgammon/engines/Makefile.am
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libkbgengines.la
+
+libkbgengines_la_SOURCES = dummy.cpp
+libkbgengines_la_LIBADD = offline/libkbgoffline.la gnubg/libkbggnubg.la \
+ generic/libkbggeneric.la fibs/libkbgfibs.la \
+ nextgen/libkbgnextgen.la
+
+INCLUDES= $(all_includes)
+
+METASOURCES = AUTO
+
+SUBDIRS = offline generic fibs gnubg nextgen
+
+dummy.cpp:
+ echo > dummy.cpp
+
diff --git a/kbackgammon/engines/fibs/Makefile.am b/kbackgammon/engines/fibs/Makefile.am
new file mode 100644
index 00000000..e32522de
--- /dev/null
+++ b/kbackgammon/engines/fibs/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libkbgfibs.la
+
+libkbgfibs_la_SOURCES = kbgfibs.cpp kplayerlist.cpp kbginvite.cpp kbgfibschat.cpp
+
+INCLUDES= -I$(top_srcdir)/kbackgammon/engines -I$(top_srcdir)/libkdegames \
+ -I$(top_srcdir)/kbackgammon $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kbackgammon/engines/fibs/clip.h b/kbackgammon/engines/fibs/clip.h
new file mode 100644
index 00000000..e016eb5b
--- /dev/null
+++ b/kbackgammon/engines/fibs/clip.h
@@ -0,0 +1,39 @@
+/*
+
+ This file defines constants of the "CLIent Protocol" of FIBS.
+ It comes directly from Marvin and I guess it is copyrighted
+ by him. If you have questions regarding this file, try to
+ visit
+
+ http://fibs.demon.co.uk/clip.html
+
+*/
+
+#ifndef KFIBS_CLIP_H
+#define KFIBS_CLIP_H
+
+
+#define CLIP_VERSION 1008
+
+#define CLIP_WELCOME 1
+#define CLIP_OWN_INFO 2
+#define CLIP_MOTD_BEGIN 3
+#define CLIP_MOTD_END 4
+#define CLIP_WHO_INFO 5
+#define CLIP_WHO_END 6
+#define CLIP_LOGIN 7
+#define CLIP_LOGOUT 8
+#define CLIP_MESSAGE 9
+#define CLIP_MESSAGE_DELIVERED 10
+#define CLIP_MESSAGE_SAVED 11
+#define CLIP_SAYS 12
+#define CLIP_SHOUTS 13
+#define CLIP_WHISPERS 14
+#define CLIP_KIBITZES 15
+#define CLIP_YOU_SAY 16
+#define CLIP_YOU_SHOUT 17
+#define CLIP_YOU_WHISPER 18
+#define CLIP_YOU_KIBITZ 19
+
+
+#endif // KFIBS_CLIP_H
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], "&lt;");
+ line.replace(pat[HTML_gt], "&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>&nbsp;&nbsp;&nbsp;&nbsp;" + 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;
+}
+
+
diff --git a/kbackgammon/engines/fibs/kbgfibs.h b/kbackgammon/engines/fibs/kbgfibs.h
new file mode 100644
index 00000000..1c14e0f3
--- /dev/null
+++ b/kbackgammon/engines/fibs/kbgfibs.h
@@ -0,0 +1,479 @@
+/* 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$
+
+*/
+
+
+#ifndef __KBGFIBS_H
+#define __KBGFIBS_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <generic/kbgengine.h>
+
+#include "kplayerlist.h"
+#include "kbgfibschat.h"
+#include "kbginvite.h" // TODO
+
+#include <qstring.h>
+#include <qstringlist.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+
+class QTimer;
+class QSocket;
+class QPopupMenu;
+class QCheckBox;
+
+class KAction;
+class KToggleAction;
+
+/**
+ *
+ * Special backgammon engine for games on the First Internet Backgammon Server
+ *
+ * @short The FIBS backgammon engine
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KBgEngineFIBS : public KBgEngine
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgEngineFIBS(QWidget *parent = 0, QString *name = 0, QPopupMenu *pmenu = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgEngineFIBS();
+
+ /**
+ * Fills the engine-specific page into the notebook
+ */
+ virtual void getSetupPages(KDialogBase *nb);
+
+ virtual void setupOk();
+ virtual void setupDefault();
+ virtual void setupCancel();
+
+ /*
+ * Check with the engine if we can quit. This may require user
+ * interaction.
+ */
+ virtual bool queryClose();
+
+ /**
+ * About to be closed. Let the engine exit properly.
+ */
+ virtual bool queryExit();
+
+ virtual void start();
+
+
+public slots:
+
+ /**
+ * Read and save user settings to the config file
+ */
+ virtual void readConfig();
+ virtual void saveConfig();
+
+ /**
+ * Roll dice for the player w
+ */
+ virtual void rollDice(const int w);
+
+ /**
+ * Double the cube of player w
+ */
+ virtual void doubleCube(const int w);
+
+ /**
+ * A move has been made on the board - see the board class
+ * for the format of the string s
+ */
+ virtual void handleMove(QString *s);
+
+ /**
+ * Undo the last move
+ */
+ virtual void undo();
+
+ /**
+ * Redo the last move
+ */
+ virtual void redo();
+
+ /**
+ * Commit a move
+ */
+ virtual void done();
+
+
+ // ###########################################################################
+ //
+ //
+ //
+ // TODO TODO TODO TODO TODO TODO TODO
+ //
+ //
+ //
+ // ###########################################################################
+
+ /*
+ * Process the string cmd
+ */
+ void handleCommand(const QString &cmd);
+
+ void fibsRequestInvitation(const QString &player);
+
+ void personalMessage(const QString &msg);
+
+
+
+ /*
+ * Local configuration handling
+ */
+
+ void keepAlive();
+
+signals:
+
+ void serverString(const QString &s);
+
+ void fibsWhoInfo(const QString &line);
+ void fibsWhoEnd();
+ void fibsLogout(const QString &p);
+ void fibsLogin(const QString &p);
+
+ void fibsConnectionClosed();
+
+ void changePlayerStatus(const QString &, int, bool);
+
+ void chatMessage(const QString &msg);
+
+ void fibsStartNewGame(const QString &msg);
+ void gameOver();
+
+protected slots:
+
+ void invitationDone();
+ void inviteDialog();
+ void showList();
+ void showChat();
+
+ void endGame();
+
+private:
+
+ QTimer *keepaliveTimer;
+
+ QString pname[2];
+
+ QString currBoard, caption;
+
+ //KBgStatus *currBoard
+ //KBgFIBSBoard *boardHandler;
+
+ QStringList invitations;
+
+ /*
+ * special menu entries
+ */
+ int respMenuID, cmdMenuID, joinMenuID, optsMenuID;
+ QPopupMenu *respMenu, *cmdMenu, *joinMenu, *optsMenu;
+
+ /*
+ * child windows
+ */
+ KFibsPlayerList *playerlist;
+ KBgChat *chatWindow;
+ KBgInvite *invitationDlg;
+
+ /*
+ * Other stuff
+ */
+ QString lastMove;
+ int toMove;
+
+ QString lastAway;
+ bool playing;
+ bool redoPossible;
+ int undoCounter;
+
+ KAction *conAction, *disAction, *newAction, *invAction;
+
+ KAction *actAccept, *actReject, *actConti, *actLeave, *actAway, *actBack;
+
+ KToggleAction *chatAct, *listAct;
+
+
+ // ###########################################################################
+ //
+ //
+ //
+ // DONE DONE DONE DONE DONE DONE DONE
+ //
+ //
+ //
+ // ###########################################################################
+
+private:
+
+ /**
+ * Actions for responding to invitations. numJoin is he current
+ * number of active actions. The max. number of pending invitations
+ * is eight and this is hardcoded in a lot of places (not the least
+ * of which are the slots join_N().
+ */
+ KAction *actJoin[8];
+ int numJoin;
+
+protected slots:
+
+ /**
+ * Handle rawwho information for the purposes of the invitation
+ * submenu and the join entries
+ */
+ void changeJoin(const QString &info);
+
+ /**
+ * A player will be removed from the menu of pending invitations
+ * if necessary.
+ */
+ void cancelJoin(const QString &info);
+
+ /**
+ * We have up to 8 names in the join menu. They are the
+ * players that invited us to play games. Each action
+ * has its own slot and all slots call the common backend
+ * join().
+ */
+ void join(const QString &msg);
+
+ void join_0();
+ void join_1();
+ void join_2();
+ void join_3();
+ void join_4();
+ void join_5();
+ void join_6();
+ void join_7();
+
+ /**
+ * Simple slots that toggle FIBS server-side settings. The
+ * names of the functions reflect the name of the toggle on
+ * FIBS.
+ */
+ void toggle_greedy();
+ void toggle_ready();
+ void toggle_double();
+ void toggle_ratings();
+
+private:
+
+ /**
+ * Toggle actions for the FIBS server-side settings
+ */
+ enum FIBSOpt {OptReady, OptGreedy, OptDouble,
+ OptAllowPip, OptAutoMove, OptCrawford, OptSilent, OptRatings, OptMoves, NumFIBSOpt};
+ KToggleAction *fibsOpt[NumFIBSOpt];
+
+public slots:
+
+ /*
+ * Connection handling
+ * -------------------
+ */
+
+ // initiate asynchronous connection establishment
+ void connectFIBS();
+
+ // take the connection down
+ void disconnectFIBS();
+
+ // create a new account and connect
+ void newAccount();
+
+ // called when the connection is down
+ void connectionClosed();
+
+ // the hostname has been resolved
+ void hostFound();
+
+ // a connection error occurred
+ void connError(int f);
+
+ // connection has been established
+ void connected();
+
+ // data can be read from the socket
+ void readData();
+
+ // send the string s to the server
+ void sendData(const QString &s);
+
+protected:
+
+ // get the connection parameters
+ bool queryConnection(const bool newlogin);
+
+private:
+
+ // actual connection object
+ QSocket *connection;
+
+ // flag if we have login information or new account
+ bool login;
+
+protected slots:
+
+ /*
+ * FIBS command slots
+ * ------------------
+ */
+
+ // go away and leave a message
+ void away();
+
+ // come back after being away
+ void back();
+
+ // roll dice
+ virtual void roll();
+
+ // double the cube
+ virtual void cube();
+
+ // reload the board to the last known sane state
+ virtual void load();
+
+ // accept an offer
+ void accept();
+
+ // reject an offer
+ void reject();
+
+ // continue a multi game match
+ void match_conti();
+
+ // leave a multi game match
+ void match_leave();
+
+protected slots:
+
+ /*
+ * All strings received from the server are given to handleServerData() for
+ * identification and processing. It implements a limited state machine to
+ * handle the incoming data correctly. The whole function could probably be
+ * made more efficient, but it is not time critical (and it appears to be
+ * easier to understand this way).
+ */
+ void handleServerData(QString &line);
+
+protected:
+
+ enum RxStatus {RxIgnore, RxConnect, RxWhois, RxMotd, RxRating,
+ RxNewLogin, RxGoodbye, RxNormal};
+
+ int rxStatus, rxCount;
+
+ QString rxCollect;
+
+ /*
+ * The following functions handle the individual states
+ * of the handleServerData() state machine,
+ */
+ void handleMessageWhois(const QString &line);
+ void handleMessageRating(const QString &line);
+ void handleMessageMotd(const QString &line);
+ void handleMessageNewLogin(const QString &line);
+ void handleMessageConnect(const QString &line, const QString &rawline);
+ void handleMessageNormal(QString &line, QString &rawline);
+
+ /*
+ * The next enumeration and the array of regular expressions is needed for the
+ * message identification in handleServerData().
+ */
+ enum Pattern {Welcome, OwnInfo, NoLogin, BegRate, EndRate, HTML_lt, HTML_gt,
+ BoardSY, BoardSN, WhoisBG, WhoisE1, WhoisE2, WhoEnde, WhoInfo,
+ MotdBeg, MotdEnd, MsgPers, MsgDeli, MsgSave, ChatSay, ChatSht,
+ ChatWis, ChatKib, SelfSay, SelfSlf, SelfSht, SelfWis, SelfKib,
+ UserLin, UserLot, Goodbye, GameSav, RawBord, YouTurn, PlsMove,
+ BegWtch, EndWtch, BegBlnd, EndBlnd, BegGame, OneWave, TwoWave,
+ YouWave, Reload1, Reload2, GameBG1, GameBG2, GameRE1, GameRE2,
+ GameEnd, EndLose, EndVict, MatchB1, MatchB2, MatchB3, MatchB4,
+ RejAcpt, YouAway, YouAcpt, HelpTxt, Invite0, Invite1, Invite2,
+ Invite3, ConLeav, TabChar, PlsChar, OneName, TypJoin, YouBack,
+ YouMove, YouRoll, TwoStar, BoxHori, BoxVer1, BoxVer2, OthrNam,
+ YourNam, GivePwd, RetypeP, GreedyY, GreedyN, RejCont, AcptWin,
+ YouGive, DoubleY, DoubleN, KeepAlv, RatingY, RatingN,
+ NumPattern};
+
+ QRegExp pat[NumPattern];
+
+ /*
+ * This function is simply filling the pat[] array with the proper values.
+ */
+ void initPattern();
+
+private:
+
+ /*
+ * Local setup and config variables
+ * ================================
+ */
+
+ /*
+ * Various options
+ */
+ bool showMsg, whoisInvite;
+ QCheckBox *cbp, *cbi;
+
+ QCheckBox *cbk;
+ bool keepalive;
+
+ /*
+ * Connection setup
+ */
+ enum FIBSInfo {FIBSHost, FIBSPort, FIBSUser, FIBSPswd, NumFIBS};
+ QString infoFIBS[NumFIBS];
+ QLineEdit *lec[NumFIBS];
+
+ /*
+ * Auto messages
+ */
+ enum AutoMessages {MsgBeg, MsgLos, MsgWin, NumMsg};
+ QLineEdit *lem[NumMsg];
+ QCheckBox *cbm[NumMsg];
+ bool useAutoMsg[NumMsg];
+ QString autoMsg[NumMsg];
+};
+
+#endif // __KBGFIBS_H
diff --git a/kbackgammon/engines/fibs/kbgfibschat.cpp b/kbackgammon/engines/fibs/kbgfibschat.cpp
new file mode 100644
index 00000000..45ba2bb7
--- /dev/null
+++ b/kbackgammon/engines/fibs/kbgfibschat.cpp
@@ -0,0 +1,828 @@
+
+/* 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 "kbgfibschat.h"
+#include "kbgfibschat.moc"
+
+#include <qstring.h>
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qpopupmenu.h>
+#include <qregexp.h>
+#include <qfont.h>
+#include <qwhatsthis.h>
+#include <qdatetime.h>
+#include <qclipboard.h>
+#include <qsimplerichtext.h>
+#include <qregion.h>
+#include <qpalette.h>
+#include <qpainter.h>
+#include <qpoint.h>
+#include <qlistbox.h>
+#include <qiconset.h>
+#include <qstringlist.h>
+#include <qdict.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kstdaction.h>
+#include <ktabctl.h>
+#include <kaction.h>
+#include <kiconloader.h>
+
+#include "clip.h"
+#include "version.h"
+
+
+/*
+ * Private utility class that might become more generally useful in
+ * the future. Basically, it implements rich text QListBox items.
+ */
+class KLBT : public QListBoxText
+{
+
+public:
+
+ /*
+ * Constructor
+ */
+ KLBT(QWidget *parent, const QString &text = QString::null, const QString &player = QString::null)
+ : QListBoxText(text)
+ {
+ w = parent;
+ n = new QString(player);
+ t = new QSimpleRichText(text, w->font());
+
+ // FIXME: this is not yet perfect
+ t->setWidth(w->width()-20);
+ }
+
+ /*
+ * Destructor
+ */
+ virtual ~KLBT()
+ {
+ delete t;
+ delete n;
+ }
+
+ /*
+ * Overloaded required members returning height
+ */
+ virtual int height(const QListBox *) const
+ {
+ return (1+t->height());
+ }
+
+ /*
+ * Overloaded required members returning width
+ */
+ virtual int width(const QListBox *) const
+ {
+ return t->width();
+ }
+
+ /*
+ * The context menu needs the name of the player. It's easier
+ * than extracting it from the text.
+ */
+ QString player() const
+ {
+ return *n;
+ }
+
+protected:
+
+ /*
+ * Required overloaded member to paint the text on the painter p.
+ */
+ virtual void paint(QPainter *p)
+ {
+ t->draw(p, 1, 1, QRegion(p->viewport()), w->colorGroup());
+ }
+
+private:
+
+ QSimpleRichText *t;
+ QWidget *w;
+ QString *n;
+
+};
+
+
+class KBgChatPrivate
+{
+public:
+
+ /*
+ * Name of the users
+ */
+ QString mName[2];
+
+ /*
+ * Hold and assemble info text
+ */
+ QString mText;
+
+ /*
+ * Numbers of the private action list.
+ */
+ enum Privact {Inquire, InviteD, Invite1, Invite2, Invite3, Invite4,
+ Invite5, Invite6, Invite7, InviteR, InviteU, Silent,
+ Talk, Gag, Ungag, Cleargag, Copy, Clear, Close, MaxAction};
+
+ /*
+ * Available actions
+ */
+ KAction *mAct[MaxAction];
+
+ /*
+ * Context menu and invitation menu
+ */
+ QPopupMenu *mChat, *mInvt;
+
+ /*
+ * list of users we do not want to hear shouting
+ */
+ QStringList mGag;
+
+ /*
+ * Listbox needed by the setup dialog
+ */
+ QListBox *mLb;
+
+ /*
+ * Internal ID to name mapping
+ */
+ QDict<int> *mName2ID;
+
+};
+
+
+// == constructor, destructor ==================================================
+
+/*
+ * Constructor of the chat window.
+ */
+KBgChat::KBgChat(QWidget *parent, const char *name)
+ : KChat(parent, false)
+{
+ d = new KBgChatPrivate();
+ KActionCollection* actions = new KActionCollection(this);
+
+ d->mName[0] = QString::null;
+ d->mChat = 0;
+ d->mInvt = new QPopupMenu();
+
+ setAutoAddMessages(false); // we get an echo from FIBS
+ setFromNickname(i18n("%1 user").arg(PROG_NAME));
+
+ if (!addSendingEntry(i18n("Kibitz to watchers and players"), CLIP_YOU_KIBITZ))
+ kdDebug(10500) << "adding kibitz" << endl;
+ if (!addSendingEntry(i18n("Whisper to watchers only"), CLIP_YOU_WHISPER))
+ kdDebug(10500) << "adding whisper" << endl;
+
+ connect(this, SIGNAL(rightButtonClicked(QListBoxItem *, const QPoint &)),
+ this, SLOT(contextMenu(QListBoxItem *, const QPoint &)));
+ connect(this, SIGNAL(signalSendMessage(int, const QString &)),
+ this, SLOT(handleCommand(int, const QString &)));
+
+ d->mName2ID = new QDict<int>(17, true);
+ d->mName2ID->setAutoDelete(true);
+
+ /*
+ * some eye candy :)
+ */
+ setIcon(kapp->miniIcon());
+ setCaption(i18n("Chat Window"));
+
+ QWhatsThis::add(this, i18n("This is the chat window.\n\n"
+ "The text in this window is colored depending on whether "
+ "it is directed at you personally, shouted to the general "
+ "FIBS population, has been said by you, or is of general "
+ "interest. If you select the name of a player, the context "
+ "contains entries specifically geared towards that player."));
+ /*
+ * Define set of available actions
+ */
+ d->mAct[KBgChatPrivate::Inquire] = new KAction(i18n("Info On"),
+ QIconSet(kapp->iconLoader()->loadIcon(
+ "help.xpm", KIcon::Small)),
+ 0, this, SLOT(slotInquire()), actions);
+ d->mAct[KBgChatPrivate::Talk] = new KAction(i18n("Talk To"),
+ QIconSet(kapp->iconLoader()->loadIcon(
+ PROG_NAME "-chat.png", KIcon::Small)),
+ 0, this, SLOT(slotTalk()), actions);
+
+ d->mAct[KBgChatPrivate::InviteD] = new KAction(i18n("Use Dialog"), 0, this,
+ SLOT(slotInviteD()), actions);
+ d->mAct[KBgChatPrivate::Invite1] = new KAction(i18n("1 Point Match"), 0, this,
+ SLOT(slotInvite1()), actions);
+ d->mAct[KBgChatPrivate::Invite2] = new KAction(i18n("2 Point Match"), 0, this,
+ SLOT(slotInvite2()), actions);
+ d->mAct[KBgChatPrivate::Invite3] = new KAction(i18n("3 Point Match"), 0, this,
+ SLOT(slotInvite3()), actions);
+ d->mAct[KBgChatPrivate::Invite4] = new KAction(i18n("4 Point Match"), 0, this,
+ SLOT(slotInvite4()), actions);
+ d->mAct[KBgChatPrivate::Invite5] = new KAction(i18n("5 Point Match"), 0, this,
+ SLOT(slotInvite5()), actions);
+ d->mAct[KBgChatPrivate::Invite6] = new KAction(i18n("6 Point Match"), 0, this,
+ SLOT(slotInvite6()), actions);
+ d->mAct[KBgChatPrivate::Invite7] = new KAction(i18n("7 Point Match"), 0, this,
+ SLOT(slotInvite7()), actions);
+ d->mAct[KBgChatPrivate::InviteU] = new KAction(i18n("Unlimited"), 0, this,
+ SLOT(slotInviteU()), actions);
+ d->mAct[KBgChatPrivate::InviteR] = new KAction(i18n("Resume"), 0, this,
+ SLOT(slotInviteR()), actions);
+
+ d->mAct[KBgChatPrivate::InviteD]->plug(d->mInvt);
+
+ d->mInvt->insertSeparator();
+
+ d->mAct[KBgChatPrivate::Invite1]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite2]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite3]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite4]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite5]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite6]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::Invite7]->plug(d->mInvt);
+
+ d->mInvt->insertSeparator();
+
+ d->mAct[KBgChatPrivate::InviteU]->plug(d->mInvt);
+ d->mAct[KBgChatPrivate::InviteR]->plug(d->mInvt);
+
+ d->mAct[KBgChatPrivate::Gag] = new KAction(i18n("Gag"), 0, this, SLOT(slotGag()), actions);
+ d->mAct[KBgChatPrivate::Ungag] = new KAction(i18n("Ungag"), 0, this, SLOT(slotUngag()), actions);
+ d->mAct[KBgChatPrivate::Cleargag] = new KAction(i18n("Clear Gag List"), 0, this, SLOT(slotCleargag()), actions);
+ d->mAct[KBgChatPrivate::Copy] = KStdAction::copy(this, SLOT(slotCopy()), actions);
+ d->mAct[KBgChatPrivate::Clear] = new KAction(i18n("Clear"), 0, this, SLOT(slotClear()), actions);
+ d->mAct[KBgChatPrivate::Close] = KStdAction::close(this, SLOT(hide()), actions);
+ d->mAct[KBgChatPrivate::Silent] = new KToggleAction(i18n("Silent"), 0, this, SLOT(slotSilent()), actions);
+}
+
+
+/*
+ * Destructor
+ */
+KBgChat::~KBgChat()
+{
+ delete d->mName2ID;
+ delete d->mChat; // save to delete NULL pointers
+ delete d->mInvt;
+ delete d;
+}
+
+
+// == configuration handling ===================================================
+
+/*
+ * Restore the previously stored settings
+ */
+void KBgChat::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("chat window");
+
+ QPoint pos(10, 10);
+
+ pos = config->readPointEntry("ori", &pos);
+ setGeometry(pos.x(), pos.y(), config->readNumEntry("wdt",460), config->readNumEntry("hgt",200));
+
+ config->readBoolEntry("vis", false) ? show() : hide();
+
+ ((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->setChecked(config->readBoolEntry("sil", false));
+
+ d->mGag = config->readListEntry("gag");
+}
+
+/*
+ * Save the current settings to disk
+ */
+void KBgChat::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("chat window");
+
+ config->writeEntry("ori", pos());
+ config->writeEntry("hgt", height());
+ config->writeEntry("wdt", width());
+ config->writeEntry("vis", isVisible());
+
+ config->writeEntry("sil", ((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked());
+
+ config->writeEntry("gag", d->mGag);
+}
+
+
+/*
+ * Setup dialog page of the player list - allow the user to select the
+ * columns to show
+ *
+ * FIXME: need to be able to set font here KChatBase::setBothFont(const QFont& font)
+ */
+void KBgChat::getSetupPages(KTabCtl *nb, int space)
+{
+ /*
+ * Main Widget
+ * ===========
+ */
+ QWidget *w = new QWidget(nb);
+ QGridLayout *gl = new QGridLayout(w, 2, 1, space);
+
+ d->mLb = new QListBox(w);
+ d->mLb->setMultiSelection(true);
+
+ d->mLb->insertStringList(d->mGag);
+
+ QLabel *info = new QLabel(w);
+ info->setText(i18n("Select users to be removed from the gag list."));
+
+ QWhatsThis::add(w, i18n("Select all the users you want "
+ "to remove from the gag list "
+ "and then click OK. Afterwards "
+ "you will again hear what they shout."));
+
+ gl->addWidget(d->mLb, 0, 0);
+ gl->addWidget(info, 1, 0);
+
+ /*
+ * put in the page
+ * ===============
+ */
+ gl->activate();
+ w->adjustSize();
+ w->setMinimumSize(w->size());
+ nb->addTab(w, i18n("&Gag List"));
+}
+
+/*
+ * Remove all the selected entries from the gag list
+ */
+void KBgChat::setupOk()
+{
+ for (uint i = 0; i < d->mLb->count(); ++i) {
+ if (d->mLb->isSelected(i))
+ d->mGag.remove(d->mLb->text(i));
+ }
+ d->mLb->clear();
+ d->mLb->insertStringList(d->mGag);
+}
+
+/*
+ * Don't do anything
+ */
+void KBgChat::setupCancel()
+{
+ // empty
+}
+
+/*
+ * By default, all players stay in the gag list
+ */
+void KBgChat::setupDefault()
+{
+ d->mLb->clearSelection();
+}
+
+
+// == various slots and functions ==============================================
+
+/*
+ * Overloaded member to create a QListBoxItem for the chat window.
+ */
+QListBoxItem* KBgChat::layoutMessage(const QString& fromName, const QString& text)
+{
+ QListBoxText* message = new KLBT(this, text, fromName);
+ return message;
+}
+
+/*
+ * Catch hide events, so the engine's menu can be update.
+ */
+void KBgChat::showEvent(QShowEvent *e)
+{
+ QFrame::showEvent(e);
+ emit windowVisible(true);
+}
+
+/*
+ * Catch hide events, so the engine's menu can be update.
+ */
+void KBgChat::hideEvent(QHideEvent *e)
+{
+ emit windowVisible(false);
+ QFrame::hideEvent(e);
+}
+
+/*
+ * At the beginning of a game, add the name to the list and switch to
+ * kibitz mode.
+ */
+void KBgChat::startGame(const QString &name)
+{
+ int *id = d->mName2ID->find(d->mName[1] = name);
+ if (!id) {
+ id = new int(nextId());
+ d->mName2ID->insert(name, id);
+ addSendingEntry(i18n("Talk to %1").arg(name), *id);
+ }
+ setSendingEntry(CLIP_YOU_KIBITZ);
+}
+
+/*
+ * At the end of a game, we switch to talk mode.
+ */
+void KBgChat::endGame()
+{
+ int *id = d->mName2ID->find(d->mName[1]);
+ if (id)
+ setSendingEntry(*id);
+ else
+ setSendingEntry(SendToAll);
+}
+
+/*
+ * Set the chat window ready to talk to name
+ */
+void KBgChat::fibsTalk(const QString &name)
+{
+ int *id = d->mName2ID->find(name);
+ if (!id) {
+ id = new int(nextId());
+ d->mName2ID->insert(name, id);
+ addSendingEntry(i18n("Talk to %1").arg(name), *id);
+ }
+ setSendingEntry(*id);
+}
+
+/*
+ * Remove the player from the combo box when he/she logs out.
+ */
+void KBgChat::deletePlayer(const QString &name)
+{
+ int *id = d->mName2ID->find(name);
+ if (id) {
+ removeSendingEntry(*id);
+ d->mName2ID->remove(name);
+ }
+}
+
+/*
+ * Take action when the user presses return in the line edit control.
+ */
+void KBgChat::handleCommand(int id, const QString& msg)
+{
+ int realID = sendingEntry();
+
+ switch (realID) {
+ case SendToAll:
+ emit fibsCommand("shout " + msg);
+ break;
+ case CLIP_YOU_KIBITZ:
+ emit fibsCommand("kibitz " + msg);
+ break;
+ case CLIP_YOU_WHISPER:
+ emit fibsCommand("whisper " + msg);
+ break;
+ default:
+ QDictIterator<int> it(*d->mName2ID);
+ while (it.current()) {
+ if (*it.current() == realID) {
+ emit fibsCommand("tell " + it.currentKey() + " " + msg);
+ return;
+ }
+ ++it;
+ }
+ kdDebug(10500) << "unrecognized ID in KBgChat::handleCommand" << endl;
+ }
+}
+
+
+// == handle strings from the server ===========================================
+
+/*
+ * A message from the server that should be handled by us. If necessary,
+ * we replace the CLIP number by a string and put the line into the window.
+ *
+ * This function emits the string in rich text format with the signal
+ * personalMessage - again: the string contains rich text!
+ */
+void KBgChat::handleData(const QString &msg)
+{
+ QString clip = msg.left(msg.find(' ')), user, cMsg = msg;
+ QDateTime date;
+
+ bool flag = false;
+ int cmd = clip.toInt(&flag);
+
+ if (flag) {
+ cMsg.replace(0, cMsg.find(' ')+1, "");
+
+ user = cMsg.left(cMsg.find(' '));
+
+ switch (cmd) {
+ case CLIP_SAYS:
+ if (!d->mGag.contains(user)) {
+ cMsg = i18n("<u>%1 tells you:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), ""));
+ cMsg = "<font color=\"red\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ } else
+ cMsg = "";
+ break;
+
+ case CLIP_SHOUTS:
+ if ((!((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked()) && (!d->mGag.contains(user))) {
+ cMsg = i18n("<u>%1 shouts:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), ""));
+ cMsg = "<font color=\"black\">" + cMsg + "</font>";
+ } else
+ cMsg = "";
+ break;
+
+ case CLIP_WHISPERS:
+ if (!d->mGag.contains(user)) {
+ cMsg = i18n("<u>%1 whispers:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), ""));
+ cMsg = "<font color=\"red\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ } else
+ cMsg = "";
+ break;
+
+ case CLIP_KIBITZES:
+ if (!d->mGag.contains(user)) {
+ cMsg = i18n("<u>%1 kibitzes:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), ""));
+ cMsg = "<font color=\"red\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ } else
+ cMsg = "";
+ break;
+
+ case CLIP_YOU_SAY:
+ cMsg = i18n("<u>You tell %1:</u> %2").arg(user).arg(cMsg.replace(QRegExp("^" + user), ""));
+ cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_YOU_SHOUT:
+ cMsg = i18n("<u>You shout:</u> %1").arg(cMsg);
+ cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_YOU_WHISPER:
+ cMsg = i18n("<u>You whisper:</u> %1").arg(cMsg);
+ cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_YOU_KIBITZ:
+ cMsg = i18n("<u>You kibitz:</u> %1").arg(cMsg);
+ cMsg = "<font color=\"darkgreen\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_MESSAGE:
+ user = cMsg.left(cMsg.find(' ')+1);
+ cMsg.remove(0, cMsg.find(' ')+1);
+ date.setTime_t(cMsg.left(cMsg.find(' ')+1).toUInt());
+ cMsg.remove(0, cMsg.find(' '));
+ cMsg = i18n("<u>User %1 left a message at %2</u>: %3").arg(user).arg(date.toString()).arg(cMsg);
+ cMsg = "<font color=\"red\">" + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_MESSAGE_DELIVERED:
+ cMsg = i18n("Your message for %1 has been delivered.").arg(user);
+ cMsg = QString("<font color=\"darkgreen\">") + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ case CLIP_MESSAGE_SAVED:
+ cMsg = i18n("Your message for %1 has been saved.").arg(user);
+ cMsg = QString("<font color=\"darkgreen\">") + cMsg + "</font>";
+ emit personalMessage(cMsg);
+ user = QString::null;
+ break;
+
+ default: // ignore the message
+ return;
+ }
+
+ } else {
+
+ /*
+ * Special treatment for non-CLIP messages
+ */
+ if (cMsg.contains(QRegExp("^You say to yourself: "))) {
+ cMsg.replace(QRegExp("^You say to yourself: "),
+ i18n("<u>You say to yourself:</u> "));
+ } else {
+ kdDebug(user.isNull(), 10500) << "KBgChat::handleData unhandled message: "
+ << cMsg.latin1() << endl;
+ return;
+ }
+ }
+
+ if (!cMsg.isEmpty())
+ addMessage(user, cMsg);
+}
+
+
+// == context menu and related slots ===========================================
+
+/*
+ * RMB opens a context menu.
+ */
+void KBgChat::contextMenu(QListBoxItem *i, const QPoint &p)
+{
+ /*
+ * Even if i is non-null, user might still be QString::null
+ */
+ d->mName[0] = (i == 0) ? QString::null : ((KLBT *)i)->player();
+ d->mText = (i == 0) ? QString::null : ((KLBT *)i)->text();
+
+ /*
+ * Get a new context menu every time. Safe to delete the 0
+ * pointer.
+ */
+ delete d->mChat; d->mChat = new QPopupMenu();
+
+ /*
+ * Fill the context menu with actions
+ */
+ if (!d->mName[0].isNull()) {
+
+ d->mAct[KBgChatPrivate::Talk]->setText(i18n("Talk to %1").arg(d->mName[0]));
+ d->mAct[KBgChatPrivate::Talk]->plug(d->mChat);
+
+ d->mAct[KBgChatPrivate::Inquire]->setText(i18n("Info on %1").arg(d->mName[0]));
+ d->mAct[KBgChatPrivate::Inquire]->plug(d->mChat);
+
+ // invite menu is always the same
+ d->mChat->insertItem(i18n("Invite %1").arg(d->mName[0]), d->mInvt);
+
+ d->mChat->insertSeparator();
+
+ if (d->mGag.contains(d->mName[0]) <= 0) {
+ d->mAct[KBgChatPrivate::Gag]->setText(i18n("Gag %1").arg(d->mName[0]));
+ d->mAct[KBgChatPrivate::Gag]->plug(d->mChat);
+ } else {
+ d->mAct[KBgChatPrivate::Ungag]->setText(i18n("Ungag %1").arg(d->mName[0]));
+ d->mAct[KBgChatPrivate::Ungag]->plug(d->mChat);
+ }
+ }
+ if (d->mGag.count() > 0)
+ d->mAct[KBgChatPrivate::Cleargag]->plug(d->mChat);
+
+ if ((d->mGag.count() > 0) || (!d->mName[0].isNull()))
+ d->mChat->insertSeparator();
+
+ d->mAct[KBgChatPrivate::Silent]->plug(d->mChat);
+
+ d->mChat->insertSeparator();
+
+ d->mAct[KBgChatPrivate::Copy ]->plug(d->mChat);
+ d->mAct[KBgChatPrivate::Clear]->plug(d->mChat);
+ d->mAct[KBgChatPrivate::Close]->plug(d->mChat);
+
+ d->mChat->popup(p);
+}
+
+/*
+ * Clear the gag list
+ */
+void KBgChat::slotCleargag()
+{
+ d->mGag.clear();
+
+ QString msg("<font color=\"blue\">");
+ msg += i18n("The gag list is now empty.");
+ msg += "</font>";
+
+ addMessage(QString::null, msg);
+}
+
+/*
+ * Gag the selected user
+ */
+void KBgChat::slotGag()
+{
+ d->mGag.append(d->mName[0]);
+
+ QString msg("<font color=\"blue\">");
+ msg += i18n("You won't hear what %1 says and shouts.").arg(d->mName[0]);
+ msg += "</font>";
+
+ addMessage(QString::null, msg);
+}
+
+/*
+ * Simple interface to the actual talk slot
+ */
+void KBgChat::slotTalk()
+{
+ fibsTalk(d->mName[0]);
+}
+
+/*
+ * Remove selected user from gag list
+ */
+void KBgChat::slotUngag()
+{
+ d->mGag.remove(d->mName[0]);
+
+ QString msg("<font color=\"blue\">");
+ msg += i18n("You will again hear what %1 says and shouts.").arg(d->mName[0]);
+ msg += "</font>";
+
+ addMessage(QString::null, msg);
+}
+
+/*
+ * Get information on selected user
+ */
+void KBgChat::slotInquire()
+{
+ kdDebug(d->mName[0].isNull(), 10500) << "KBgChat::slotInquire: user == null" << endl;
+ emit fibsCommand("whois " + d->mName[0]);
+}
+
+/*
+ * Block all shouts from the chat window
+ */
+void KBgChat::slotSilent()
+{
+ QString msg;
+ if (((KToggleAction *)d->mAct[KBgChatPrivate::Silent])->isChecked())
+ msg = "<font color=\"blue\">" + i18n("You will not hear what people shout.") + "</font>";
+ else
+ msg = "<font color=\"blue\">" + i18n("You will hear what people shout.") + "</font>";
+ addMessage(QString::null, msg);
+}
+
+/*
+ * Copy the selected line to the clipboard. Strip the additional HTML
+ * from the text before copying.
+ */
+void KBgChat::slotCopy()
+{
+ d->mText.replace(QRegExp("<u>"), "");
+ d->mText.replace(QRegExp("</u>"), "");
+ d->mText.replace(QRegExp("</font>"), "");
+ d->mText.replace(QRegExp("^.*\">"), "");
+
+ kapp->clipboard()->setText(d->mText);
+}
+
+/*
+ * Invite the selected player.
+ */
+void KBgChat::slotInviteD()
+{
+ kdDebug(d->mName[0].isNull(), 10500) << "KBgChat::slotInvite: user == null" << endl;
+ emit fibsRequestInvitation(d->mName[0]);
+}
+void KBgChat::slotInvite1() { emit fibsCommand("invite " + d->mName[0] + " 1"); }
+void KBgChat::slotInvite2() { emit fibsCommand("invite " + d->mName[0] + " 2"); }
+void KBgChat::slotInvite3() { emit fibsCommand("invite " + d->mName[0] + " 3"); }
+void KBgChat::slotInvite4() { emit fibsCommand("invite " + d->mName[0] + " 4"); }
+void KBgChat::slotInvite5() { emit fibsCommand("invite " + d->mName[0] + " 5"); }
+void KBgChat::slotInvite6() { emit fibsCommand("invite " + d->mName[0] + " 6"); }
+void KBgChat::slotInvite7() { emit fibsCommand("invite " + d->mName[0] + " 7"); }
+
+void KBgChat::slotInviteU() { emit fibsCommand("invite " + d->mName[0] + " unlimited"); }
+void KBgChat::slotInviteR() { emit fibsCommand("invite " + d->mName[0]); }
+
+
+// EOF
diff --git a/kbackgammon/engines/fibs/kbgfibschat.h b/kbackgammon/engines/fibs/kbgfibschat.h
new file mode 100644
index 00000000..c3a1d670
--- /dev/null
+++ b/kbackgammon/engines/fibs/kbgfibschat.h
@@ -0,0 +1,273 @@
+/* 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$
+
+*/
+
+#ifndef __KBGCHAT_H
+#define __KBGCHAT_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kchat.h>
+
+class QString;
+class QPoint;
+class QListBox;
+class QListBoxItem;
+class QPopupMenu;
+
+class KTabCtl;
+class KAction;
+
+class KBgChatPrivate;
+
+/**
+ * Class of the FIBS Chat Windows
+ *
+ * This class inherits from KChat and represents a widget for a chat
+ * window. It has rich text entries and supports a powerful context
+ * menu.
+ *
+ * @short An extension of the KGame chat window for the FIBS engine
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KBgChat : public KChat
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgChat(QWidget *parent = 0, const char *name = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgChat();
+
+public slots:
+
+ /**
+ * Catch the RMB signal to display a context menu at p. The
+ * menu shows entries specific to the selected item i.
+ */
+ void contextMenu(QListBoxItem *i, const QPoint &p);
+
+ /**
+ * Add chat window specific pages to the setup dialog
+ */
+ void getSetupPages(KTabCtl *nb, int space);
+
+ /**
+ * Save and apply the changes made in the setup dialog
+ */
+ void setupOk();
+
+ /**
+ * Do not save any of the changes made in the setup dialog
+ */
+ void setupCancel();
+
+ /**
+ * Load default values from the setup dialog
+ */
+ void setupDefault();
+
+ /**
+ * Player name has logges out. Remove name from the chat
+ * window combo box if necessary.
+ */
+ void deletePlayer(const QString &name);
+
+ /**
+ * Process and append msg to the text.
+ */
+ void handleData(const QString &msg);
+
+ /**
+ * Restore previously saved setting or provides defaults
+ */
+ void readConfig();
+
+ /**
+ * Save current settings
+ */
+ void saveConfig();
+
+ /**
+ * Set the opponents name and select whisper
+ */
+ void startGame(const QString &name);
+
+ /**
+ * Game is over. We won (or not) and have been playing (or not)
+ */
+ void endGame();
+
+ /**
+ * Start talking to name
+ */
+ void fibsTalk(const QString &name);
+
+signals:
+
+ /**
+ * Emits a string that can be sent to the server
+ */
+ void fibsCommand(const QString &cmd);
+
+ /**
+ * Request an invitation of player
+ */
+ void fibsRequestInvitation(const QString &player);
+
+ /**
+ * Text of a personal message
+ */
+ void personalMessage(const QString &msg);
+
+ /**
+ * Dialog is visible or not
+ */
+ void windowVisible(bool v);
+
+protected:
+
+ /**
+ * Catch show events, so the engine's menu can be updated.
+ */
+ virtual void showEvent(QShowEvent *e);
+
+ /**
+ * Catch hide events, so the engine's menu can be updated.
+ */
+ virtual void hideEvent(QHideEvent *e);
+
+ /**
+ * Create a custom ListBoxItem that contains a formated string
+ * for the chat window.
+ */
+ virtual QListBoxItem* layoutMessage(const QString& fromName, const QString& text);
+
+protected slots:
+
+ /**
+ * Invite the selected player using the dialog
+ */
+ void slotInviteD();
+
+ /**
+ * Invite the selected player to resume a match
+ */
+ void slotInviteR();
+
+ /**
+ * Invite the selected player to an unlimited match
+ */
+ void slotInviteU();
+
+ /**
+ * Invite the selected player to a 1 point match
+ */
+ void slotInvite1();
+
+ /**
+ * Invite the selected player to a 2 point match
+ */
+ void slotInvite2();
+
+ /**
+ * Invite the selected player to a 3 point match
+ */
+ void slotInvite3();
+
+ /**
+ * Invite the selected player to a 4 point match
+ */
+ void slotInvite4();
+
+ /**
+ * Invite the selected player to a 5 point match
+ */
+ void slotInvite5();
+
+ /**
+ * Invite the selected player to a 6 point match
+ */
+ void slotInvite6();
+
+ /**
+ * Invite the selected player to a 7 point match
+ */
+ void slotInvite7();
+
+ /**
+ * Request information on the selected player
+ */
+ void slotInquire();
+
+ /**
+ * Copy the selected line to the clipboard
+ */
+ void slotCopy();
+
+ /**
+ * Talk to the selected player
+ */
+ void slotTalk();
+
+ /**
+ * Add the selected player to the gag list
+ */
+ void slotGag();
+
+ /**
+ * Remove the selected player from the gag list
+ */
+ void slotUngag();
+
+ /**
+ * Clear the gag list
+ */
+ void slotCleargag();
+
+ /**
+ * Toggle everybody silent
+ */
+ void slotSilent();
+
+ /**
+ * Slot for return pressed. Time to send the text to FIBS.
+ */
+ void handleCommand(int id, const QString& msg);
+
+private:
+
+ KBgChatPrivate *d;
+
+};
+
+#endif // __KBGCHAT_H
diff --git a/kbackgammon/engines/fibs/kbginvite.cpp b/kbackgammon/engines/fibs/kbginvite.cpp
new file mode 100644
index 00000000..cb455f0a
--- /dev/null
+++ b/kbackgammon/engines/fibs/kbginvite.cpp
@@ -0,0 +1,185 @@
+/* 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 "kbginvite.h"
+#include "kbginvite.moc"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qframe.h>
+#include <qspinbox.h>
+#include <qstring.h>
+
+#include <klocale.h>
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+
+class KBgInvitePrivate {
+
+public:
+
+ KLineEdit *mLe;
+ QSpinBox *mSb;
+ QPushButton *mInvite, *mResume, *mUnlimited, *mCancel, *mClose;
+
+};
+
+/*
+ * Constructor is quite simple - most positioning is left to
+ * the toolkit.
+ */
+KBgInvite::KBgInvite(const char *name)
+ : KDialog(0, name, false)
+{
+ setCaption(i18n("Invite Players"));
+
+ d = new KBgInvitePrivate();
+
+ QLabel *info = new QLabel(this);
+
+ d->mLe = new KLineEdit(this, "invitation dialog");
+ d->mSb = new QSpinBox(1, 999, 1, this, "spin box");
+
+ d->mInvite = new QPushButton(i18n("&Invite"), this);
+ d->mResume = new QPushButton(i18n("&Resume"), this);
+ d->mUnlimited = new QPushButton(i18n("&Unlimited"), this);
+
+ d->mClose = new KPushButton(KStdGuiItem::close(), this);
+ d->mCancel = new KPushButton(KStdGuiItem::clear(), this);
+
+ info->setText(i18n("Type the name of the player you want to invite in the first entry\n"
+ "field and select the desired match length in the spin box."));
+
+ QFrame *hLine = new QFrame(this);
+ hLine->setFrameStyle(QFrame::Sunken|QFrame::HLine);
+
+ /*
+ * Set up layouts
+ */
+ QBoxLayout *vbox = new QVBoxLayout(this);
+
+ QBoxLayout *hbox_1 = new QHBoxLayout(vbox);
+ QBoxLayout *hbox_2 = new QHBoxLayout(vbox);
+ QBoxLayout *hbox_3 = new QHBoxLayout(vbox);
+ QBoxLayout *hbox_4 = new QHBoxLayout(vbox);
+ QBoxLayout *hbox_5 = new QHBoxLayout(vbox);
+
+ hbox_1->addWidget(info);
+
+ hbox_2->addWidget(d->mLe);
+ hbox_2->addWidget(d->mSb);
+
+ hbox_3->addWidget(hLine);
+
+ hbox_4->addWidget(d->mInvite);
+ hbox_4->addWidget(d->mResume);
+ hbox_4->addWidget(d->mUnlimited);
+
+ hbox_5->addWidget(d->mClose);
+ hbox_5->addWidget(d->mCancel);
+
+ /*
+ * Adjust widget sizes and resize the dialog
+ */
+ KDialog::resizeLayout(this, marginHint(), spacingHint());
+ setMinimumSize(childrenRect().size());
+ vbox->activate();
+ resize(minimumSize());
+
+ /*
+ * Set focus and default buttons
+ */
+ d->mInvite->setDefault(true);
+ d->mInvite->setAutoDefault(true);
+ d->mLe->setFocus();
+
+ /*
+ * Connect the buttons
+ */
+ connect(d->mUnlimited, SIGNAL(clicked()), SLOT(unlimitedClicked()));
+ connect(d->mResume, SIGNAL(clicked()), SLOT(resumeClicked()));
+ connect(d->mInvite, SIGNAL(clicked()), SLOT(inviteClicked()));
+ connect(d->mClose, SIGNAL(clicked()), SLOT(hide()));
+ connect(d->mCancel, SIGNAL(clicked()), SLOT(cancelClicked()));
+}
+
+/*
+ * Destructor
+ */
+KBgInvite::~KBgInvite()
+{
+ delete d;
+}
+
+/*
+ * After hiding, we tell our creator that we are ready to die.
+ */
+void KBgInvite::hide()
+{
+ emit dialogDone();
+}
+
+/*
+ * Set player name
+ */
+void KBgInvite::setPlayer(const QString &player)
+{
+ d->mLe->setText(player);
+}
+
+/*
+ * Invitation with number
+ */
+void KBgInvite::inviteClicked()
+{
+ QString tmp;
+ emit inviteCommand(QString("invite ") + d->mLe->text() + " " + tmp.setNum(d->mSb->value()));
+}
+
+/*
+ * Invitation for unlimited match
+ */
+void KBgInvite::unlimitedClicked()
+{
+ emit inviteCommand(QString("invite ") + d->mLe->text() + " unlimited");
+}
+
+/*
+ * Resume a game
+ */
+void KBgInvite::resumeClicked()
+{
+ emit inviteCommand(QString("invite ") + d->mLe->text());
+}
+
+/*
+ * Slot for Cancel. clear everything to default.
+ */
+void KBgInvite::cancelClicked()
+{
+ d->mSb->setValue(1);
+ d->mLe->clear();
+}
+
+// EOF
diff --git a/kbackgammon/engines/fibs/kbginvite.h b/kbackgammon/engines/fibs/kbginvite.h
new file mode 100644
index 00000000..992ee445
--- /dev/null
+++ b/kbackgammon/engines/fibs/kbginvite.h
@@ -0,0 +1,113 @@
+/* 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$
+
+*/
+
+#ifndef __KBGINVITE_H
+#define __KBGINVITE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kdialog.h>
+
+class KBgInvitePrivate;
+
+/**
+ *
+ * This class implements a dialog for inviting players for games. It
+ * is quite simple (but follows the default style guide). The dialog
+ * offers specific numbers, unlimited and resume as invitation
+ * options.
+ *
+ * @short Simple dialog that allows to invite somebody on FIBS
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KBgInvite : public KDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgInvite(const char *name = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgInvite();
+
+public slots:
+
+ /**
+ * After hiding, we tell our creator that we are ready to die.
+ */
+ virtual void hide();
+
+ /**
+ * Set the name of the player in the line editor
+ */
+ void setPlayer(const QString &name);
+
+protected slots:
+
+ /**
+ * Emits the FIBS invitation command if the Ok button was clicked.
+ */
+ void inviteClicked();
+
+ /**
+ * Ask FIBS to resume a match
+ */
+ void resumeClicked();
+
+ /**
+ * Ask FIBS for an unlimited match
+ */
+ void unlimitedClicked();
+
+ /**
+ * Clear the entry field
+ */
+ void cancelClicked();
+
+signals:
+
+ /**
+ * Emits the text of an invitation
+ */
+ void inviteCommand(const QString &cmd);
+
+ /**
+ * Delete the dialog after it is closed.
+ */
+ void dialogDone();
+
+private:
+
+ KBgInvitePrivate *d;
+};
+
+#endif // __KBGINVITE_H
diff --git a/kbackgammon/engines/fibs/kplayerlist.cpp b/kbackgammon/engines/fibs/kplayerlist.cpp
new file mode 100644
index 00000000..102c354d
--- /dev/null
+++ b/kbackgammon/engines/fibs/kplayerlist.cpp
@@ -0,0 +1,902 @@
+/* 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 "kplayerlist.moc"
+#include "kplayerlist.h"
+
+#include <qlayout.h>
+#include <qiconset.h>
+#include <qgroupbox.h>
+#include <qpopupmenu.h>
+#include <qcheckbox.h>
+#include <qlistview.h>
+#include <qwhatsthis.h>
+#include <qdatetime.h>
+#include <qlabel.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kaction.h>
+#include <kstdaction.h>
+#include <ktabctl.h>
+#include <kiconloader.h>
+#include <kdebug.h>
+
+#include <string.h>
+#include <stdio.h>
+
+#include "kbgfibs.h"
+#include "version.h"
+
+
+/*
+ * Simple container for information on columns of the list view.
+ *
+ * index : the internal index in the list
+ * width : width of the column in pixel
+ * show : whether the column is visible
+ * cb : check box for the setup dialog
+ */
+class KFibsPlayerListCI {
+
+public:
+
+ int index, width;
+ bool show;
+ QCheckBox *cb;
+ QString key, name;
+
+};
+
+/*
+ * Extension of the QListViewItem class that has a custom key function
+ * that can deal with the different items of the player list.
+ */
+class KFibsPlayerListLVI : public KListViewItem {
+
+public:
+
+ /*
+ * Constructor
+ */
+ KFibsPlayerListLVI(KFibsPlayerList *parent) : KListViewItem(parent) { _plist = parent; }
+
+ /*
+ * Destructor
+ */
+ virtual ~KFibsPlayerListLVI() {}
+
+ /*
+ * Overloaded key function for sorting
+ */
+ virtual QString key(int col, bool) const
+ {
+ int real_col = _plist->cIndex(col);
+
+ QString s = text(col);
+
+ switch (real_col) {
+ case KFibsPlayerList::Player:
+ case KFibsPlayerList::Opponent:
+ case KFibsPlayerList::Watches:
+ case KFibsPlayerList::Client:
+ case KFibsPlayerList::Email:
+ case KFibsPlayerList::Status:
+ case KFibsPlayerList::Host:
+ s = s.lower();
+ break;
+ case KFibsPlayerList::Idle:
+ case KFibsPlayerList::Experience:
+ s.sprintf("%08d", s.toInt());
+ break;
+ case KFibsPlayerList::Rating:
+ s.sprintf("%08d", (int)(1000*s.toDouble()));
+ break;
+ case KFibsPlayerList::Time:
+ s = s.lower();
+ break;
+ default:
+ kdDebug(10500) << "KFibsPlayerListLVI::key(): illegal column" << endl;
+ break;
+ }
+ return s;
+ }
+
+private:
+
+ KFibsPlayerList *_plist;
+
+};
+
+/*
+ * Private data of the player list
+ */
+class KFibsPlayerListPrivate {
+
+public:
+
+ /*
+ * Named constants for the popup menu actions
+ */
+ enum MenuID {Info, Talk, Mail, InviteD, Invite1, Invite2, Invite3, Invite4,
+ Invite5, Invite6, Invite7, InviteR, InviteU,
+ Look, Watch, Unwatch, BlindAct, Update, Reload, Close, ActionEnd};
+
+ /*
+ * Various actions for the context menu
+ */
+ KAction *mAct[ActionEnd];
+
+ /*
+ * All relevant information on the columns
+ */
+ KFibsPlayerListCI *mCol[KFibsPlayerList::LVEnd];
+
+ /*
+ * Context menus for player related commands
+ */
+ QPopupMenu *mPm[2];
+
+ /*
+ * ID of the invite menu in the context menu
+ */
+ int mInID;
+
+ /*
+ * Are we watching?
+ */
+ bool mWatch;
+
+ /*
+ * count similar clients - KFibs & kbackgammon
+ */
+ int mCount[2];
+
+ /*
+ * Short abbreviations for Blind, Ready, and Away.
+ */
+ QString mAbrv[KFibsPlayerList::MaxStatus];
+
+ /*
+ * Name of the last selected player - for internal purposes
+ */
+ QString mUser;
+
+ /*
+ * Our own name
+ */
+ QString mName;
+
+ /*
+ * Email address of the last selected player - for internal purposes
+ */
+ QString mMail;
+
+};
+
+
+// == constructor, destructor and setup ========================================
+
+/*
+ * Construct the playerlist and do some initial setup
+ */
+KFibsPlayerList::KFibsPlayerList(QWidget *parent, const char *name)
+ : KListView(parent, name)
+{
+ d = new KFibsPlayerListPrivate();
+ KActionCollection* actions = new KActionCollection(this);
+
+ /*
+ * Allocate the column information
+ */
+ for (int i = 0; i < LVEnd; i++)
+ d->mCol[i] = new KFibsPlayerListCI();
+
+ /*
+ * Initialize variables
+ */
+ d->mCol[Player]->name = i18n("Player");
+ d->mCol[Opponent]->name = i18n("Opponent");
+ d->mCol[Watches]->name = i18n("Watches");
+ d->mCol[Status]->name = i18n("Status");
+ d->mCol[Rating]->name = i18n("Rating");
+ d->mCol[Experience]->name = i18n("Exp.");
+ d->mCol[Idle]->name = i18n("Idle");
+ d->mCol[Time]->name = i18n("Time");
+ d->mCol[Host]->name = i18n("Host name");
+ d->mCol[Client]->name = i18n("Client");
+ d->mCol[Email]->name = i18n("Email");
+
+ // These strings shouldn't be translated!!
+ d->mCol[Player]->key = "player";
+ d->mCol[Opponent]->key = "opponent";
+ d->mCol[Watches]->key = "watches";
+ d->mCol[Status]->key = "status";
+ d->mCol[Rating]->key = "rating";
+ d->mCol[Experience]->key = "experience";
+ d->mCol[Idle]->key = "idle";
+ d->mCol[Time]->key = "time";
+ d->mCol[Host]->key = "hostname";
+ d->mCol[Client]->key = "client";
+ d->mCol[Email]->key = "email";
+
+ d->mCount[0] = d->mCount[1] = 0;
+
+ d->mAbrv[Blind] = i18n("abreviate blind", "B");
+ d->mAbrv[Away ] = i18n("abreviate away", "A");
+ d->mAbrv[Ready] = i18n("abreviate ready", "R");
+
+ d->mName = QString::null;
+
+ d->mWatch = false;
+
+ /*
+ * Get a sane caption, initialize some eye candy and read the
+ * configuration - needed for the column information.
+ */
+ updateCaption();
+ setIcon(kapp->miniIcon());
+ QWhatsThis::add(this, i18n("This window contains the player list. It shows "
+ "all players that are currently logged into FIBS."
+ "Use the right mouse button to get a context "
+ "menu with helpful information and commands."));
+
+ readColumns();
+
+ /*
+ * Put the columns into the list view
+ */
+ for (int i = 0; i < LVEnd; i++) {
+ if (d->mCol[i]->show) {
+ d->mCol[i]->index = addColumn(d->mCol[i]->name, d->mCol[i]->width);
+ if (i == Experience || i == Rating || i == Time || i == Idle)
+ setColumnAlignment(d->mCol[i]->index, AlignRight);
+ } else {
+ d->mCol[i]->index = -1;
+ }
+ }
+ setAllColumnsShowFocus(true);
+
+ /*
+ * Create context menus
+ */
+ d->mPm[0] = new QPopupMenu();
+ d->mPm[1] = new QPopupMenu();
+
+ /*
+ * Create the whole set of actions
+ */
+ d->mAct[KFibsPlayerListPrivate::Info] = new KAction(i18n("Info"),
+ QIconSet(kapp->iconLoader()->loadIcon
+ ("help.xpm", KIcon::Small)),
+ 0, this, SLOT(slotInfo()), actions);
+ d->mAct[KFibsPlayerListPrivate::Talk] = new KAction(i18n("Talk"),
+ QIconSet(kapp->iconLoader()->loadIcon
+ (PROG_NAME "-chat.png", KIcon::Small)),
+ 0, this, SLOT(slotTalk()), actions);
+
+ d->mAct[KFibsPlayerListPrivate::Look] = new KAction(i18n("Look"), 0, this, SLOT(slotLook()), actions);
+ d->mAct[KFibsPlayerListPrivate::Watch] = new KAction(i18n("Watch"), 0, this, SLOT(slotWatch()), actions);
+ d->mAct[KFibsPlayerListPrivate::Unwatch] = new KAction(i18n("Unwatch"), 0, this, SLOT(slotUnwatch()),actions);
+ d->mAct[KFibsPlayerListPrivate::BlindAct] = new KAction(i18n("Blind"), 0, this, SLOT(slotBlind()), actions);
+ d->mAct[KFibsPlayerListPrivate::Update] = new KAction(i18n("Update"), 0, this, SLOT(slotUpdate()), actions);
+
+ d->mAct[KFibsPlayerListPrivate::Reload] = KStdAction::redisplay(this, SLOT(slotReload()), actions);
+ d->mAct[KFibsPlayerListPrivate::Mail] = KStdAction::mail(this, SLOT(slotMail()), actions);
+ d->mAct[KFibsPlayerListPrivate::Close] = KStdAction::close(this, SLOT(hide()), actions);
+
+ d->mAct[KFibsPlayerListPrivate::InviteD] = new KAction(i18n("Use Dialog"), 0, this,
+ SLOT(slotInviteD()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite1] = new KAction(i18n("1 Point Match"), 0, this,
+ SLOT(slotInvite1()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite2] = new KAction(i18n("2 Point Match"), 0, this,
+ SLOT(slotInvite2()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite3] = new KAction(i18n("3 Point Match"), 0, this,
+ SLOT(slotInvite3()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite4] = new KAction(i18n("4 Point Match"), 0, this,
+ SLOT(slotInvite4()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite5] = new KAction(i18n("5 Point Match"), 0, this,
+ SLOT(slotInvite5()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite6] = new KAction(i18n("6 Point Match"), 0, this,
+ SLOT(slotInvite6()), actions);
+ d->mAct[KFibsPlayerListPrivate::Invite7] = new KAction(i18n("7 Point Match"), 0, this,
+ SLOT(slotInvite7()), actions);
+ d->mAct[KFibsPlayerListPrivate::InviteU] = new KAction(i18n("Unlimited"), 0, this,
+ SLOT(slotInviteU()), actions);
+ d->mAct[KFibsPlayerListPrivate::InviteR] = new KAction(i18n("Resume"), 0, this,
+ SLOT(slotInviteR()), actions);
+
+ /*
+ * Fill normal context menu
+ */
+ d->mAct[KFibsPlayerListPrivate::Info]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::Talk]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::Mail]->plug(d->mPm[0]);
+ d->mPm[0]->insertSeparator();
+ d->mInID = d->mPm[0]->insertItem(i18n("Invite"), d->mPm[1]); // save ID for later
+ d->mAct[KFibsPlayerListPrivate::Look ]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::Watch ]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::Unwatch ]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::BlindAct]->plug(d->mPm[0]);
+ d->mPm[0]->insertSeparator();
+ d->mAct[KFibsPlayerListPrivate::Update]->plug(d->mPm[0]);
+ d->mAct[KFibsPlayerListPrivate::Reload]->plug(d->mPm[0]);
+ d->mPm[0]->insertSeparator();
+ d->mAct[KFibsPlayerListPrivate::Close]->plug(d->mPm[0]);
+
+ /*
+ * Fill the invitation menu
+ */
+ d->mAct[KFibsPlayerListPrivate::InviteD]->plug(d->mPm[1]);
+ d->mPm[1]->insertSeparator();
+ d->mAct[KFibsPlayerListPrivate::Invite1]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite2]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite3]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite4]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite5]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite6]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::Invite7]->plug(d->mPm[1]);
+ d->mPm[1]->insertSeparator();
+ d->mAct[KFibsPlayerListPrivate::InviteU]->plug(d->mPm[1]);
+ d->mAct[KFibsPlayerListPrivate::InviteR]->plug(d->mPm[1]);
+
+ /*
+ * Right mouse button gets context menu, double click gets player info
+ */
+ connect(this, SIGNAL(contextMenu(KListView *, QListViewItem *, const QPoint &)),
+ this, SLOT(showContextMenu(KListView *, QListViewItem *, const QPoint &)));
+ connect(this, SIGNAL(doubleClicked(QListViewItem *, const QPoint &, int)),
+ this, SLOT(getPlayerInfo(QListViewItem *, const QPoint &, int)));
+}
+
+/*
+ * Destructor deletes members
+ */
+KFibsPlayerList::~KFibsPlayerList()
+{
+ for (int i = 0; i < LVEnd; i++)
+ delete d->mCol[i];
+ delete d->mPm[0];
+ delete d->mPm[1];
+ delete d;
+}
+
+
+// == settings and config ======================================================
+
+/*
+ * Called when the setup dialog is positively closed
+ */
+void KFibsPlayerList::setupOk()
+{
+ int i;
+ bool change = false;
+
+ for (i = 1; i < LVEnd; i++)
+ change |= (d->mCol[i]->cb->isChecked() != d->mCol[i]->show);
+
+ /*
+ * Only juggle with the columns if something changed
+ */
+ if (change) {
+
+ /*
+ * It's important to remove the columns in reverse order
+ */
+ for (i = LVEnd-1; i > 0; i--)
+ if (d->mCol[i]->show)
+ removeColumn(d->mCol[i]->index);
+
+ /*
+ * Now add all columns that are selected
+ */
+ for (i = 1; i < LVEnd; i++) {
+ if ((d->mCol[i]->show = d->mCol[i]->cb->isChecked())) {
+ d->mCol[i]->index = addColumn(d->mCol[i]->name, d->mCol[i]->width);
+ if (i == Experience || i == Rating || i == Time || i == Idle)
+ setColumnAlignment(d->mCol[i]->index, AlignRight);
+ } else {
+ d->mCol[i]->index = -1;
+ }
+ }
+
+ /*
+ * Reload the list
+ */
+ slotReload();
+ }
+
+ /*
+ * store the new settings
+ */
+ saveConfig();
+
+}
+
+/*
+ * Setup dialog page of the player list - allow the user to select the
+ * columns to show
+ */
+void KFibsPlayerList::getSetupPages(KTabCtl *nb, int space)
+{
+ int i;
+
+ /*
+ * Main Widget
+ */
+ QWidget *w = new QWidget(nb);
+ QGridLayout *gl = new QGridLayout(w, 2, 1, space);
+
+ /*
+ * Label
+ */
+ QGroupBox *gbl = new QGroupBox(w);
+ gbl->setTitle(i18n("Column Selection"));
+
+ gl->addWidget(gbl, 0, 0);
+
+ /*
+ * Note that the first column (Player == 0) is always there
+ */
+ QLabel *lb = new QLabel(i18n("Select all the columns that you would\n"
+ "like to be shown in the player list."), gbl);
+
+ for (i = 1; i < LVEnd; i++) {
+ d->mCol[i]->cb = new QCheckBox(d->mCol[i]->name, gbl);
+ d->mCol[i]->cb->setChecked(d->mCol[i]->show);
+ }
+
+ gl = new QGridLayout(gbl, LVEnd, 2, 20);
+ gl->addWidget(lb, 0, 0);
+
+ // two column layout....
+ for (i = 1; i < LVEnd/2; i++) {
+ gl->addWidget(d->mCol[2*i-1]->cb, i, 0);
+ gl->addWidget(d->mCol[2*i ]->cb, i, 1);
+ }
+ gl->addWidget(d->mCol[2*i-1]->cb, i, 0);
+ if (2*i < LVEnd)
+ gl->addWidget(d->mCol[2*i]->cb, i, 1);
+
+ /*
+ * put in the page and connect
+ */
+ nb->addTab(w, i18n("&Playerlist"));
+
+ connect(nb, SIGNAL(applyButtonPressed()), this, SLOT(setupOk()));
+}
+
+/*
+ * Nothing to cancel/undo
+ */
+void KFibsPlayerList::setupCancel()
+{
+ // do nothing
+}
+
+/*
+ * By default all entries are checked
+ */
+void KFibsPlayerList::setupDefault()
+{
+ for (int i = 0; i < LVEnd; i++)
+ d->mCol[i]->cb->setChecked(true);
+}
+
+/*
+ * Restore the columns
+ */
+void KFibsPlayerList::readColumns()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ for (int i = 0; i < LVEnd; i++) {
+ d->mCol[i]->show = config->readBoolEntry("col-" + d->mCol[i]->key, true);
+ d->mCol[i]->width = config->readNumEntry("col-w-" + d->mCol[i]->key, -1);
+ }
+}
+
+/*
+ * Restore the saved settings
+ */
+void KFibsPlayerList::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ QPoint pos, defpos(10, 10);
+ pos = config->readPointEntry("ori", &defpos);
+ setGeometry(pos.x(), pos.y(), config->readNumEntry("wdt",460),
+ config->readNumEntry("hgt",190));
+
+ (config->readBoolEntry("vis", false)) ? show() : hide();
+
+ readColumns();
+}
+
+/*
+ * Save current settings
+ */
+void KFibsPlayerList::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ config->writeEntry("ori", pos());
+ config->writeEntry("hgt", height());
+ config->writeEntry("wdt", width());
+
+ config->writeEntry("vis", isVisible());
+
+ for (int i = 0; i < LVEnd; i++) {
+ config->writeEntry("col-" + d->mCol[i]->key, d->mCol[i]->show);
+ config->writeEntry("col-w-" + d->mCol[i]->key,
+ (d->mCol[i]->show) ? columnWidth(d->mCol[i]->index) : -1);
+ }
+}
+
+
+// == popup menu slots and functions ===========================================
+
+/*
+ * Save selected player, update the menu entries and show the popup menu
+ */
+void KFibsPlayerList::showContextMenu(KListView *, QListViewItem *i, const QPoint &p)
+{
+ /*
+ * Get the name of the selected player
+ */
+ d->mUser = (i ? i->text(Player) : QString::null);
+
+ d->mAct[KFibsPlayerListPrivate::Info ]->setText(i18n("Info on %1" ).arg(d->mUser));
+ d->mAct[KFibsPlayerListPrivate::Talk ]->setText(i18n("Talk to %1" ).arg(d->mUser));
+ d->mAct[KFibsPlayerListPrivate::Mail ]->setText(i18n("Email to %1").arg(d->mUser));
+ d->mAct[KFibsPlayerListPrivate::Look ]->setText(i18n("Look at %1" ).arg(d->mUser));
+ d->mAct[KFibsPlayerListPrivate::Watch ]->setText(i18n("Watch %1" ).arg(d->mUser));
+ d->mAct[KFibsPlayerListPrivate::Update]->setText(i18n("Update %1" ).arg(d->mUser));
+
+ d->mAct[KFibsPlayerListPrivate::Info ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::Talk ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::Mail ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::Look ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::Watch ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::Update ]->setEnabled(i);
+ d->mAct[KFibsPlayerListPrivate::BlindAct]->setEnabled(i);
+
+ d->mAct[KFibsPlayerListPrivate::Unwatch]->setEnabled(d->mWatch);
+
+ d->mPm[0]->setItemEnabled(d->mInID, i && d->mName != d->mUser);
+ d->mPm[0]->changeItem(d->mInID, i18n("Invite %1").arg(d->mUser));
+
+ d->mMail = (i && d->mCol[Email]->show ? i->text(d->mCol[Email]->index) : QString::null);
+ d->mAct[KFibsPlayerListPrivate::Mail]->setEnabled(!d->mMail.isEmpty());
+
+ if (i && d->mCol[Status]->show)
+ d->mAct[KFibsPlayerListPrivate::BlindAct]->setText
+ ((i->text(d->mCol[Status]->index).contains(d->mAbrv[Blind])) ?
+ i18n("Unblind %1").arg(d->mUser) : i18n("Blind %1").arg(d->mUser));
+ else
+ d->mAct[KFibsPlayerListPrivate::BlindAct]->setText(i18n("Blind"));
+
+ // show the menu
+ d->mPm[0]->popup(p);
+}
+
+/*
+ * Reload the entire list
+ */
+void KFibsPlayerList::slotReload()
+{
+ emit fibsCommand("rawwho");
+ clear();
+}
+
+/*
+ * Stop watching
+ */
+void KFibsPlayerList::slotUnwatch()
+{
+ emit fibsCommand("unwatch");
+}
+
+/*
+ * Blind/Unblind user
+ */
+void KFibsPlayerList::slotBlind()
+{
+ emit fibsCommand("blind " + d->mUser);
+}
+
+/*
+ * Start talking to user
+ */
+void KFibsPlayerList::slotTalk()
+{
+ emit fibsTalk(d->mUser);
+}
+
+/*
+ * Request information on user
+ */
+void KFibsPlayerList::slotInfo()
+{
+ emit fibsCommand("whois " + d->mUser);
+}
+
+/*
+ * Look at user
+ */
+void KFibsPlayerList::slotLook()
+{
+ emit fibsCommand("look " + d->mUser);
+}
+
+/*
+ * Send an email to player user at address email
+ */
+void KFibsPlayerList::slotMail()
+{
+ kapp->invokeMailer(d->mMail, QString::null);
+}
+
+/*
+ * Request a new entry for user
+ */
+void KFibsPlayerList::slotUpdate()
+{
+ emit fibsCommand("rawwho " + d->mUser);
+}
+
+/*
+ * Watch user and get an updated board
+ */
+void KFibsPlayerList::slotWatch()
+{
+ emit fibsCommand("watch " + d->mUser);
+ emit fibsCommand("board");
+}
+
+/*
+ * Request information about the selected user
+ */
+void KFibsPlayerList::getPlayerInfo(QListViewItem *i, const QPoint &, int col)
+{
+ int num = cIndex(col);
+ if (col < 0 || num < 0 || num > 2 || i->text(num).isEmpty())
+ num = 0;
+ emit fibsCommand("whois " + i->text(num));
+}
+
+/*
+ * Invite the selected user.
+ */
+void KFibsPlayerList::slotInviteD()
+{
+ emit fibsInvite(d->mUser);
+}
+void KFibsPlayerList::slotInvite1() { emit fibsCommand("invite " + d->mUser + " 1"); }
+void KFibsPlayerList::slotInvite2() { emit fibsCommand("invite " + d->mUser + " 2"); }
+void KFibsPlayerList::slotInvite3() { emit fibsCommand("invite " + d->mUser + " 3"); }
+void KFibsPlayerList::slotInvite4() { emit fibsCommand("invite " + d->mUser + " 4"); }
+void KFibsPlayerList::slotInvite5() { emit fibsCommand("invite " + d->mUser + " 5"); }
+void KFibsPlayerList::slotInvite6() { emit fibsCommand("invite " + d->mUser + " 6"); }
+void KFibsPlayerList::slotInvite7() { emit fibsCommand("invite " + d->mUser + " 7"); }
+
+void KFibsPlayerList::slotInviteU() { emit fibsCommand("invite " + d->mUser + " unlimited"); }
+void KFibsPlayerList::slotInviteR() { emit fibsCommand("invite " + d->mUser); }
+
+
+// == inserting and updating the list ==========================================
+
+/*
+ * Add or change the entry of player with the corresponding string
+ * from the server - rawwho
+ */
+void KFibsPlayerList::changePlayer(const QString &line)
+{
+ char entry[LVEnd][100];
+ char ready[2], away[2];
+ QListViewItem *i;
+ QDateTime fromEpoch;
+ QString str_entry[LVEnd], tmp;
+
+ entry[Status][0] = '\0';
+
+ // the line comes from FIBS and is 7 bit ASCII
+ sscanf(line.latin1(), "%99s %99s %99s %1s %1s %99s %99s %99s %99s %99s %99s %99s", entry[Player], entry[Opponent],
+ entry[Watches], ready, away, entry[Rating], entry[Experience], entry[Idle], entry[Time],
+ entry[Host], entry[Client], entry[Email]);
+
+ // convert time
+ tmp = entry[Time];
+ fromEpoch.setTime_t(tmp.toUInt());
+ strcpy(entry[Time], fromEpoch.toString().latin1());
+
+ // clear empty strings and copy
+ for (int j = 0; j < LVEnd; j++) {
+ if ((str_entry[j] = entry[j]) == "-")
+ str_entry[j] = "";
+ }
+ str_entry[Status].replace(Ready, 1, ready[0] == '0' ? "-" : d->mAbrv[Ready]);
+ str_entry[Status].replace(Away, 1, away [0] == '0' ? "-" : d->mAbrv[Away ]);
+ str_entry[Status].replace(Blind, 1, "-");
+
+ // disable drawing until the end of update
+ setUpdatesEnabled(false);
+
+ // try to find the item in the list
+ QListViewItemIterator it(this);
+ for ( ; it.current(); ++it) {
+ if (it.current()->text(0) == str_entry[Player]) {
+ i = it.current();
+ goto found;
+ }
+ }
+
+ // getting here means we have to create a new entry
+ i = new KFibsPlayerListLVI(this);
+
+ // count the KFibs and KBackgammon clients
+ if (str_entry[Client].contains("KFibs"))
+ d->mCount[0]++;
+ else if (str_entry[Client].contains(PROG_NAME))
+ d->mCount[1]++;
+
+ // new entry requires an update to the player count
+ updateCaption();
+
+ goto update;
+
+ found:
+
+ // getting here means the player is in the list - update private status
+ str_entry[Status].replace(Blind,1,i->text(Status).contains
+ (d->mAbrv[Blind]) ? d->mAbrv[Blind] : "-");
+
+ update:
+
+ for (int j = 0; j < LVEnd; j++) {
+ if (d->mCol[j]->show)
+ i->setText(d->mCol[j]->index, str_entry[j]);
+ }
+
+ // find out if we are watching somebody
+ if (d->mName == str_entry[Player])
+ d->mWatch = !str_entry[Watches].isEmpty();
+}
+
+/*
+ * Remove player from the list
+ */
+void KFibsPlayerList::deletePlayer(const QString &player)
+{
+ QListViewItemIterator it(this);
+ for ( ; it.current(); ++it) {
+ if (it.current()->text(0) == player) {
+ if (it.current()->text(Client).contains(PROG_NAME))
+ --d->mCount[1];
+ else if (it.current()->text(Client).contains("KFibs"))
+ --d->mCount[0];
+ delete it.current();
+ updateCaption();
+ return;
+ }
+ }
+}
+
+/*
+ * Set/Unset the status stat in the corresponding column of the list
+ */
+void KFibsPlayerList::changePlayerStatus(const QString &player, int stat, bool flag)
+{
+ QListViewItem *i = 0;
+
+ /*
+ * Find the correct line
+ */
+ QListViewItemIterator it(this);
+ for ( ; it.current(); ++it) {
+ if (it.current()->text(Player) == player) {
+ i = it.current();
+ break;
+ }
+ }
+ if (!i) return;
+
+ /*
+ * Update the status flag
+ */
+ i->setText(Status, i->text(Status).replace(stat, 1, (flag) ? d->mAbrv[stat] : "-"));
+}
+
+
+// == various slots and functions ==============================================
+
+/*
+ * Reverse column to index mapping. Return negative on error.
+ */
+int KFibsPlayerList::cIndex(int col)
+{
+ for (int i = 0; i < LVEnd; i++)
+ if (d->mCol[i]->index == col)
+ return i;
+ return -1;
+}
+
+/*
+ * Catch hide events, so the engine's menu can be update.
+ */
+void KFibsPlayerList::showEvent(QShowEvent *e)
+{
+ KListView::showEvent(e);
+ emit windowVisible(true);
+}
+
+/*
+ * Catch hide events, so the engine's menu can be update.
+ */
+void KFibsPlayerList::hideEvent(QHideEvent *e)
+{
+ emit windowVisible(false);
+ KListView::hideEvent(e);
+}
+
+/*
+ * Called at the end of updates to re-enable the UI
+ */
+void KFibsPlayerList::stopUpdate()
+{
+ setUpdatesEnabled(true);
+ triggerUpdate();
+}
+
+/*
+ * Knowing our own name allows us to disable certain menu entries for
+ * ourselves.
+ */
+void KFibsPlayerList::setName(const QString &name)
+{
+ d->mName = name;
+}
+
+/*
+ * Update the caption of the list by including the current client
+ * count
+ */
+void KFibsPlayerList::updateCaption()
+{
+ setCaption(i18n("Player List - %1 - %2/%3").arg(childCount()).arg(d->mCount[0]).arg(d->mCount[1]));
+}
+
+/*
+ * Clear the list and reset the client counters
+ */
+void KFibsPlayerList::clear()
+{
+ d->mCount[0] = 0;
+ d->mCount[1] = 0;
+ QListView::clear();
+}
+
+// EOF
diff --git a/kbackgammon/engines/fibs/kplayerlist.h b/kbackgammon/engines/fibs/kplayerlist.h
new file mode 100644
index 00000000..701f9ace
--- /dev/null
+++ b/kbackgammon/engines/fibs/kplayerlist.h
@@ -0,0 +1,298 @@
+/* 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$
+
+*/
+
+#ifndef __KPLAYERLIST_H
+#define __KPLAYERLIST_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <klistview.h>
+
+class KTabCtl;
+class KFibsPlayerListPrivate;
+
+/**
+ *
+ * A class that keeps track of players on the server. The server is flooding
+ * us with user information. At any given time we are able to have an current
+ * list of all loged-in players and their status.
+ *
+ * @short The FIBS backgammon engine player list
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KFibsPlayerList : public KListView
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Enumerate player status
+ */
+ enum PStatus {Ready, Away, Blind, MaxStatus};
+
+ /**
+ * Enumerate the different columns of the list
+ */
+ enum {Player, Opponent, Watches, Status, Rating, Experience,
+ Idle, Time, Host, Client, Email, LVEnd};
+
+ /**
+ * Constructor
+ */
+ KFibsPlayerList(QWidget *parent = 0, const char *name = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KFibsPlayerList();
+
+ /**
+ * Clear the list and reset the client counter
+ */
+ virtual void clear();
+
+public slots:
+
+ /**
+ * Remove the player with the name given by the first word
+ */
+ void deletePlayer(const QString &player);
+
+ /**
+ * Change/Add the entry for the given player
+ */
+ void changePlayer(const QString &line);
+
+ /**
+ * Enables list redraws after an update
+ */
+ void stopUpdate();
+
+ /**
+ * Read the UI settings from disk
+ */
+ void readConfig();
+
+ /**
+ * Read the column info from disk
+ */
+ void readColumns();
+
+ /**
+ * Restore settings from previously stored settings
+ */
+ void saveConfig();
+
+ /**
+ * Change the status of a player
+ */
+ void changePlayerStatus(const QString &player, int stat, bool flag);
+
+ /**
+ * Fills the playerlist page into the notebook
+ */
+ virtual void getSetupPages(KTabCtl *nb, int space);
+
+ /**
+ * Save setting changes
+ */
+ void setupOk();
+
+ /**
+ * Setup changes have been cancelled
+ */
+ void setupCancel();
+
+ /**
+ * Set default settings
+ */
+ void setupDefault();
+
+ /**
+ * Set our own name. This allows us to special case the context
+ * menu.
+ */
+ void setName(const QString &name);
+
+ /**
+ * Return the column index
+ */
+ int cIndex(int col);
+
+protected:
+
+ /**
+ * Catch show events, so the engine's menu can be update.
+ */
+ virtual void showEvent(QShowEvent *e);
+
+ /**
+ * Catch hide events, so the engine's menu can be update.
+ */
+ virtual void hideEvent(QHideEvent *e);
+
+protected slots:
+
+ /**
+ * Double click handler, requests information on a player
+ */
+ void getPlayerInfo(QListViewItem *i, const QPoint &p, int col);
+
+ /**
+ * Display a popup menu for the current player
+ */
+ void showContextMenu(KListView *, QListViewItem *, const QPoint &);
+
+ /**
+ * Reload the whole list
+ */
+ void slotReload();
+
+ /**
+ * Upate the caption
+ */
+ void updateCaption();
+
+ /**
+ * Watch user
+ */
+ void slotWatch();
+
+ /**
+ * Update line of user
+ */
+ void slotUpdate();
+
+ /**
+ * Request information on user
+ */
+ void slotInfo();
+
+ /**
+ * Look at user
+ */
+ void slotLook();
+
+ /**
+ * Send an email to user
+ */
+ void slotMail();
+
+ /**
+ * Stop watching
+ */
+ void slotUnwatch();
+
+ /**
+ * Blind user
+ */
+ void slotBlind();
+
+ /**
+ * Talk to user
+ */
+ void slotTalk();
+
+ /**
+ * Invite using the dialog
+ */
+ void slotInviteD();
+
+ /**
+ * Invite to a 1 point match
+ */
+ void slotInvite1();
+
+ /**
+ * Invite to a 2 point match
+ */
+ void slotInvite2();
+
+ /**
+ * Invite to a 3 point match
+ */
+ void slotInvite3();
+
+ /**
+ * Invite to a 4 point match
+ */
+ void slotInvite4();
+
+ /**
+ * Invite to a 5 point match
+ */
+ void slotInvite5();
+
+ /**
+ * Invite to a 6 point match
+ */
+ void slotInvite6();
+
+ /**
+ * Invite to a 7 point match
+ */
+ void slotInvite7();
+
+ /**
+ * Invite to resume a saved match
+ */
+ void slotInviteR();
+
+ /**
+ * Invite to an unlimited match
+ */
+ void slotInviteU();
+
+signals:
+
+ /**
+ * Send a command to the server
+ */
+ void fibsCommand(const QString &);
+
+ /**
+ * Initiate an invitation of a player
+ */
+ void fibsInvite(const QString &);
+
+ /**
+ * Request talking to player user
+ */
+ void fibsTalk(const QString &);
+
+ /**
+ * Allow the engine's menu to be updated
+ */
+ void windowVisible(bool);
+
+private:
+
+ KFibsPlayerListPrivate *d;
+
+};
+
+#endif // __KPLAYERLIST_H
diff --git a/kbackgammon/engines/generic/Makefile.am b/kbackgammon/engines/generic/Makefile.am
new file mode 100644
index 00000000..da0f2fdb
--- /dev/null
+++ b/kbackgammon/engines/generic/Makefile.am
@@ -0,0 +1,8 @@
+noinst_LTLIBRARIES = libkbggeneric.la
+
+libkbggeneric_la_SOURCES = kbgengine.cpp
+
+INCLUDES= -I$(top_srcdir)/kbackgammon $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kbackgammon/engines/generic/kbgengine.cpp b/kbackgammon/engines/generic/kbgengine.cpp
new file mode 100644
index 00000000..bbe528a6
--- /dev/null
+++ b/kbackgammon/engines/generic/kbgengine.cpp
@@ -0,0 +1,62 @@
+/* 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 <qtimer.h>
+#include <qpopupmenu.h>
+
+#include <kdialogbase.h>
+
+#include <kbgengine.moc>
+#include "kbgengine.h"
+
+
+/*
+ * Constructor initializes the QObject
+ */
+KBgEngine::KBgEngine(QWidget *parent, QString *name, QPopupMenu *pmenu)
+ : QObject(parent, name->local8Bit())
+{
+ menu = pmenu;
+ cl = -1;
+ ct = new QTimer(this);
+ connect(ct, SIGNAL(timeout()), this, SLOT(done()));
+}
+
+/*
+ * Destructor is empty
+ */
+KBgEngine::~KBgEngine()
+{
+ // empty
+}
+
+/*
+ * Set the length of the commit timeout. Negative values disable the
+ * feature.
+ */
+void KBgEngine::setCommit(const double com)
+{
+ cl = int(1000*com);
+}
+
+// EOF
diff --git a/kbackgammon/engines/generic/kbgengine.h b/kbackgammon/engines/generic/kbgengine.h
new file mode 100644
index 00000000..ee672f40
--- /dev/null
+++ b/kbackgammon/engines/generic/kbgengine.h
@@ -0,0 +1,298 @@
+/* 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$
+
+*/
+
+#ifndef __KBGENGINE_H
+#define __KBGENGINE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qobject.h>
+
+class QTimer;
+class QPopupMenu;
+
+class KDialogBase;
+
+class KBgStatus;
+
+/**
+ *
+ * Abstract class for a generic backgammon engine. Real engines have
+ * to inherit this and implement the interfaces.
+ *
+ * Engines can and will use the following global events described in
+ * the file eventsrc:
+ *
+ * "game over l"
+ * "game over w"
+ *
+ * "roll"
+ * "roll or double"
+ *
+ * "move"
+ * "invitation"
+ *
+ * @short Abstract base class for backgammon engines
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KBgEngine:public QObject
+{
+ Q_OBJECT public:
+
+ enum Command { Redo, Undo, Roll, Cube, Done, Load };
+
+ /**
+ * Constructor
+ */
+ KBgEngine (QWidget * parent = 0, QString * name = 0, QPopupMenu * pmenu = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgEngine ();
+
+ /**
+ * Fills the engine-specific page into the notebook
+ */
+ virtual void getSetupPages (KDialogBase * nb) = 0;
+
+ /**
+ * Called after the user clicked ok in the setup dialog. Time
+ * to save settings.
+ */
+ virtual void setupOk () = 0;
+
+ /**
+ * The user cancelled the setup
+ */
+ virtual void setupCancel () = 0;
+
+ /**
+ * Set engine defaults
+ */
+ virtual void setupDefault () = 0;
+
+ /**
+ * Called when the windows are about to be hidden. Engines
+ * should hide all their child windows.
+ *
+ * The default implementation does nothing.
+ */
+ virtual void hideEvent ()
+ {
+ }
+
+ /**
+ * Called when the windows are about to be shown. Engines
+ * should show all visible child windows.
+ *
+ * The default implementation does nothing.
+ */
+ virtual void showEvent ()
+ {
+ }
+
+ /**
+ * Start the engine. This is called pretty much right after
+ * the constructor. While the constructor may not have any
+ * user interaction, it is possible to display dialogs in
+ * start.
+ *
+ * The default implementation does nothing.
+ */
+ virtual void start ()
+ {
+ }
+
+ /**
+ * Check with the engine if we can quit. This may require user
+ * interaction.
+ *
+ * The default implementation returns true.
+ */
+ virtual bool queryClose ()
+ {
+ return true;
+ }
+
+ /**
+ * About to be closed. Let the engine exit properly.
+ *
+ * The default implementation returns true.
+ */
+ virtual bool queryExit ()
+ {
+ return true;
+ }
+
+ /**
+ * Set the length of the commit timeout. Negative values
+ * disable the feature.
+ */
+ void setCommit (const double com = 2.5);
+
+public slots:
+ /**
+ * Read user settings from the config file
+ */
+ virtual void readConfig () = 0;
+
+ /**
+ * Save user settings to the config file
+ */
+ virtual void saveConfig () = 0;
+
+ /**
+ * Roll dice for the player w
+ */
+ virtual void rollDice (const int w) = 0;
+
+ /**
+ * Double the cube of player w
+ */
+ virtual void doubleCube (const int w) = 0;
+
+ /**
+ * A move has been made on the board - see the board class
+ * for the format of the string s
+ */
+ virtual void handleMove (QString * s) = 0;
+
+ /**
+ * Undo the last move
+ */
+ virtual void undo () = 0;
+
+ /**
+ * Redo the last move
+ */
+ virtual void redo () = 0;
+
+ /**
+ * Roll dice for whoevers turn it is
+ */
+ virtual void roll () = 0;
+
+ /**
+ * Double the cube for whoevers can double right now
+ */
+ virtual void cube () = 0;
+
+ /**
+ * Reload the board to the last known sane state
+ */
+ virtual void load () = 0;
+
+ /**
+ * Commit a move
+ */
+ virtual void done () = 0;
+
+ /**
+ * Process the string cmd
+ */
+ virtual void handleCommand (const QString & cmd) = 0;
+
+ /**
+ * Start a new game
+ */
+ virtual void newGame ()
+ {
+ }
+
+ /**
+ * Can we start a new game?
+ */
+ virtual bool haveNewGame ()
+ {
+ return false;
+ }
+
+signals:
+
+ /**
+ * The text identifies the current game status - could be put
+ * into the main window caption
+ */
+ void statText (const QString & msg);
+
+ /**
+ * Text that should be displayed in the ongoing message window
+ */
+ void infoText (const QString & msg);
+
+ /**
+ * Emit the most recent game state
+ */
+ void newState (const KBgStatus &);
+
+ /**
+ * Tell the board that we need the current state of the board.
+ */
+ void getState (KBgStatus *);
+
+ /**
+ * Starts/ends the edit mode of the board
+ */
+ void setEditMode (const bool f);
+
+ /**
+ * Toggle RO/RW flag of the board
+ */
+ void allowMoving (const bool fl);
+
+ /**
+ * Announce that we will accept/reject the command cmd from
+ * now on
+ */
+ void allowCommand (int cmd, bool f);
+
+ /**
+ * Tell the board to undo the last move
+ */
+ void undoMove ();
+
+ /**
+ * Tell the board to redo the last undone move
+ */
+ void redoMove ();
+
+protected:
+
+ /**
+ * Context menu for the board
+ */
+ QPopupMenu * menu;
+
+ /**
+ * Commit timer
+ */
+ QTimer *ct;
+ int cl;
+
+};
+
+#endif // __KBGENGINE_H
diff --git a/kbackgammon/engines/gnubg/Makefile.am b/kbackgammon/engines/gnubg/Makefile.am
new file mode 100644
index 00000000..7c0c9e1e
--- /dev/null
+++ b/kbackgammon/engines/gnubg/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libkbggnubg.la
+
+libkbggnubg_la_SOURCES = kbggnubg.cpp
+
+INCLUDES= -I$(top_srcdir)/kbackgammon -I$(top_srcdir)/kbackgammon/engines \
+ $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kbackgammon/engines/gnubg/kbggnubg.cpp b/kbackgammon/engines/gnubg/kbggnubg.cpp
new file mode 100644
index 00000000..eaaa4bdf
--- /dev/null
+++ b/kbackgammon/engines/gnubg/kbggnubg.cpp
@@ -0,0 +1,710 @@
+/* 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 <qlayout.h>
+#include <kiconloader.h>
+#include <kstdaction.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <kconfig.h>
+#include <iostream>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <klineeditdlg.h>
+#include <qregexp.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 QString &l)
+{
+ if (l.isEmpty())
+ return;
+
+ QString line(l);
+
+ /*
+ * Start of a new game/match
+ */
+ if (line.contains(QRegExp("^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(QRegExp("^.* cannot move\\..+$"))) {
+ KRegExp e("(^.* cannot move.)(.*$)");
+ e.match(line.latin1());
+ handleLine(e.group(1));
+ handleLine(e.group(2));
+ return;
+ }
+ if (line.contains(QRegExp("^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(QRegExp("^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(QRegExp("^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(QRegExp("^TTY boards will be given in raw format"))) {
+ line = " ";
+ }
+
+ /*
+ * Board messages
+ */
+ if (line.contains(QRegExp("^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(QRegExp(" "), "&nbsp;");
+ if (!line.isEmpty())
+ emit infoText(line);
+}
+
+
+/*
+ * Handle textual commands. All commands are passed to gnubg.
+ */
+void KBgEngineGNU::handleCommand(const QString& 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((QWidget *)parent(),
+ i18n("A game is currently in progress. "
+ "Starting a new one will terminate it."),
+ QString::null, 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
+ */
+ QVBox *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(QWidget *parent, QString *name, QPopupMenu *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, SIGNAL(allowCommand(int, bool)), this, SLOT(setAllowed(int, bool)));
+
+ /*
+ * Setup of menu
+ */
+ resAction = new KAction(i18n("&Restart GNU Backgammon"), 0, this, 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 QTimer(this);
+ connect(cmdTimer, SIGNAL(timeout()), 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, SIGNAL(processExited(KProcess *)), this, SLOT(gnubgExit(KProcess *)));
+ connect(&gnubg, SIGNAL(receivedStderr(KProcess *, char *, int)),
+ this, SLOT(receiveData(KProcess *, char *, int)));
+ connect(&gnubg, SIGNAL(receivedStdout(KProcess *, char *, int)),
+ this, SLOT(receiveData(KProcess *, char *, int)));
+ connect(&gnubg, SIGNAL(wroteStdin(KProcess *)), this, SLOT(wroteStdin(KProcess *)));
+
+ startGNU();
+}
+
+/*
+ * Actually start the background process.
+ */
+void KBgEngineGNU::startGNU()
+{
+
+ resAction->setEnabled(false);
+
+ if (!gnubg.start(KProcess::NotifyOnExit, KProcess::All))
+ KMessageBox::information((QWidget *)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(QString("<br/><font color=\"red\">") + i18n("The GNU Backgammon process (%1) has exited. ")
+ .arg(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 (QStringList::Iterator it = cmdList.begin(); it != cmdList.end(); ++it) {
+ QString s = (*it) + "\n";
+ if (!gnubg.writeStdin(s.latin1(), strlen(s.latin1()))) {
+ cmdTimer->start(250, true);
+ cmdList.remove(QString::null);
+ return;
+ }
+ (*it) = QString::null;
+ }
+ cmdList.remove(QString::null);
+ 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';
+
+ QStringList l(QStringList::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 (QStringList::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(QRegExp("\\+"), " ");
+ lastmove.replace(QRegExp("\\-"), " ");
+
+ // 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(QString *s)
+{
+ lastmove = *s;
+
+ int index = 0;
+ QString 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
diff --git a/kbackgammon/engines/gnubg/kbggnubg.h b/kbackgammon/engines/gnubg/kbggnubg.h
new file mode 100644
index 00000000..3240b8b1
--- /dev/null
+++ b/kbackgammon/engines/gnubg/kbggnubg.h
@@ -0,0 +1,223 @@
+/* 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$
+
+*/
+
+#ifndef __KBGGNU_H
+#define __KBGGNU_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <generic/kbgengine.h>
+
+#include <qtimer.h>
+#include <qspinbox.h>
+#include <kaction.h>
+#include <krandomsequence.h>
+#include <kprocess.h>
+#include <qstringlist.h>
+
+/**
+ *
+ *
+ */
+class KBgEngineGNU : public KBgEngine
+{
+ Q_OBJECT
+
+public:
+
+ /*
+ * Constructor and destructor
+ */
+ KBgEngineGNU(QWidget *parent = 0, QString *name = 0, QPopupMenu *pmenu = 0);
+ virtual ~KBgEngineGNU();
+
+ /**
+ * Fills the engine-specific page into the notebook
+ */
+ virtual void getSetupPages(KDialogBase *nb);
+
+ virtual void setupOk();
+ virtual void setupDefault();
+ virtual void setupCancel();
+
+ /*
+ * Check with the engine if we can quit. This may require user
+ * interaction.
+ */
+ virtual bool queryClose();
+
+ /**
+ * About to be closed. Let the engine exit properly.
+ */
+ virtual bool queryExit();
+
+ virtual void start();
+
+public slots:
+
+ /**
+ * Read user settings from the config file
+ */
+ virtual void readConfig();
+
+ /**
+ * Save user settings to the config file
+ */
+ virtual void saveConfig();
+
+ /**
+ * Double the cube of player w
+ */
+ virtual void doubleCube(const int w);
+
+ /**
+ * A move has been made on the board - see the board class
+ * for the format of the string s
+ */
+ virtual void handleMove(QString *s);
+
+ /**
+ * Undo the last move
+ */
+ virtual void undo();
+
+ /**
+ * Redo the last move
+ */
+ virtual void redo();
+
+ /**
+ * Roll dice for whoevers turn it is
+ */
+ virtual void roll();
+
+ /**
+ * Double the cube for whoevers can double right now
+ */
+ virtual void cube();
+
+ /**
+ * Reload the board to the last known sane state
+ */
+ virtual void load();
+
+ /**
+ * Commit a move
+ */
+ virtual void done();
+
+ /*
+ * Roll dice for the player w
+ */
+ virtual void rollDice(const int w);
+
+ /**
+ * Process the string cmd
+ */
+ virtual void handleCommand(const QString& cmd);
+
+ /**
+ * Start a new game.
+ */
+ virtual void newGame();
+ virtual bool haveNewGame() {return true;}
+
+protected slots:
+
+ /**
+ * Store if cmd is allowed or not
+ */
+ void setAllowed(int cmd, bool f);
+
+ void startGNU();
+
+private:
+
+ /**
+ * Use the standard method of obtaining random numbers
+ */
+ KRandomSequence random;
+
+ /**
+ * Player's names
+ */
+ QString nameUS, nameTHEM;
+
+ /**
+ * Who did the last roll
+ */
+ int lastRoll;
+
+ /**
+ * How many checkers to move
+ */
+ int toMove;
+
+ /**
+ * Various flags, representing the current status of the game
+ */
+ bool rollingAllowed, undoPossible, donePossible;
+ bool gameRunning, redoPossible, doublePossible;
+
+ /**
+ * Count the number of available undos
+ */
+ int dummy, undoCounter;
+
+private:
+
+ enum Turn {uRoll, uMove, tRoll, tMove, maxTurn};
+
+ KProcess gnubg;
+
+ QStringList cmdList;
+
+ QTimer *cmdTimer;
+
+ QString partline;
+
+ QString board;
+
+ QString lastmove;
+
+ int turn;
+
+ KAction *resAction;
+
+protected slots:
+
+ void wroteStdin(KProcess *);
+
+ void receiveData(KProcess *, char *buffer, int buflen);
+
+ void handleLine(const QString &l);
+
+ void gnubgExit(KProcess *proc);
+
+ void nextCommand();
+
+};
+
+#endif // __KBGGNU_H
diff --git a/kbackgammon/engines/nextgen/Makefile.am b/kbackgammon/engines/nextgen/Makefile.am
new file mode 100644
index 00000000..ed58d2f4
--- /dev/null
+++ b/kbackgammon/engines/nextgen/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libkbgnextgen.la
+
+libkbgnextgen_la_SOURCES = kbgng.cpp kbgplayer.cpp kbggame.cpp
+
+INCLUDES= -I$(top_srcdir)/kbackgammon -I$(top_srcdir)/kbackgammon/engines \
+ -I$(top_srcdir)/libkdegames/kgame $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kbackgammon/engines/nextgen/kbggame.cpp b/kbackgammon/engines/nextgen/kbggame.cpp
new file mode 100644
index 00000000..6ee709e1
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbggame.cpp
@@ -0,0 +1,47 @@
+/* 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 "kbggame.moc"
+#include "kbggame.h"
+
+#include <kplayer.h>
+
+#include <iostream>
+
+/*
+ * Constructor
+ */
+KBgGame::KBgGame(int cookie, QObject *parent)
+ : KGame(cookie, parent)
+{
+ // do nothing...
+}
+
+bool KBgGame::playerInput(QDataStream &msg,KPlayer *player)
+{
+ Q_INT32 move;
+ msg >> move;
+ cerr << " Player " << player->id() << " moved to " << move << endl;
+ return true;
+}
+
diff --git a/kbackgammon/engines/nextgen/kbggame.h b/kbackgammon/engines/nextgen/kbggame.h
new file mode 100644
index 00000000..fea5f516
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbggame.h
@@ -0,0 +1,57 @@
+/* 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$
+
+*/
+
+#ifndef __KBGGAME_H
+#define __KBGGAME_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kgame.h>
+#include <kdemacros.h>
+class QObject;
+class KPlayer;
+
+/**
+ *
+ *
+ */
+class KDE_EXPORT KBgGame : public KGame
+{
+ Q_OBJECT
+
+public:
+
+ enum MsgID {Text, Cmd, MaxMsg};
+
+ KBgGame(int cookie = 42, QObject *parent = 0);
+
+protected:
+
+ virtual bool playerInput(QDataStream &msg,KPlayer *player);
+
+};
+
+#endif // __KBGGAME_H
+
diff --git a/kbackgammon/engines/nextgen/kbgng.cpp b/kbackgammon/engines/nextgen/kbgng.cpp
new file mode 100644
index 00000000..6518147c
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbgng.cpp
@@ -0,0 +1,622 @@
+/* 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 "kbgng.moc"
+#include "kbgng.h"
+
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <stdlib.h>
+#include <time.h>
+#include <unistd.h>
+#include <qlayout.h>
+#include <kiconloader.h>
+#include <kstdaction.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <klineeditdlg.h>
+#include <krandomsequence.h>
+#include <qstringlist.h>
+#include <qcstring.h>
+#include <qtextstream.h>
+
+#include <version.h>
+
+#include <iostream>
+
+
+/*
+ * Constructor
+ */
+KBgEngineNg::KBgEngineNg(QWidget *parent, QString *name, QPopupMenu *pmenu)
+ : KBgEngine(parent, name, pmenu)
+{
+ // get a new game
+ initGame();
+
+ // create actions and menus
+ QString label[MaxTypes];
+
+ label[Local ] = i18n("Local Games");
+ label[NetServer] = i18n("Offer Network Games");
+ label[NetClient] = i18n("Join Network Games");
+
+ QStringList list;
+ for (int i = 0; i < MaxTypes; i++)
+ list.append(label[i]);
+
+ _gameSelect = new KSelectAction(i18n("&Types"), 0, this, SLOT(setGame()), this);
+ _gameSelect->setItems(list);
+ _gameSelect->plug(menu);
+
+ menu->insertSeparator();
+
+ _connectAction = new KAction(i18n("&Names..."), 0, this, SLOT(changeName()), this);
+ _connectAction->plug(menu);
+
+ // Restore last settings
+ readConfig();
+
+ // initialize to local games
+ _player[0] = _player[1] = 0;
+ _currGame = None;
+ _gameSelect->setCurrentItem(Local);
+ setGame();
+}
+
+
+/*
+ * Switch the local game type. This is called by the menu...
+ *
+ * TODO: lots of work and testing needed...
+ */
+void KBgEngineNg::setGame()
+{
+ // shutdown old game
+ switch (_currGame) {
+
+ case Local:
+ // nothing to do...
+ break;
+
+ case NetServer:
+ _game->stopServerConnection();
+ break;
+
+ case NetClient:
+ _game->disconnect();
+ break;
+
+ default:
+ // ignore
+ break;
+ }
+
+ // reset the game and delete the players
+ delete _game;
+ initGame();
+
+ emit infoText("<br/>");
+
+ // initialize a new game
+ bool ret = false;
+ QString label, port_s, host_s;
+ Q_UINT16 port;
+
+ switch (_currGame = _gameSelect->currentItem()) {
+
+ case Local:
+
+ _game->addPlayer(createPlayer(0, _name[0]));
+ _game->addPlayer(createPlayer(1, _name[1]));
+ break;
+
+ case NetServer:
+ label = i18n("Type the port number on which you want to listen to "
+ "connections.\nThe number should be between 1024 and "
+ "65535.");
+ port_s.setNum(_port);
+ do {
+ port_s = KLineEditDlg::getText(label, port_s, &ret, (QWidget *)parent());
+ if (!ret)
+ return;
+ port = port_s.toUShort(&ret);
+ } while (port_s.isEmpty() && !ret);
+
+ if (_game->offerConnections(port))
+ emit infoText(i18n("Now waiting for incoming connections on port %1.").
+ arg(_port = port));
+ else
+ emit infoText(i18n("Failed to offer connections on port %1.").arg(port));
+
+ _game->addPlayer(createPlayer(0, _name[0]));
+ break;
+
+ case NetClient:
+ label = i18n("Type the name of the server you want to connect to:");
+ host_s = _host;
+ do {
+ host_s = KLineEditDlg::getText(label, host_s, &ret, (QWidget *)parent());
+ if (!ret)
+ return;
+ } while (host_s.isEmpty());
+
+ label = i18n("Type the port number on %1 you want to connect to.\nThe "
+ "number should be between 1024 and 65535.").arg(host_s);
+ port_s.setNum(_port);
+ do {
+ port_s = KLineEditDlg::getText(label, port_s, &ret, (QWidget *)parent());
+ if (!ret)
+ return;
+ port = port_s.toUShort(&ret);
+ } while (port_s.isEmpty() && !ret);
+
+ /*
+ * Hi Martin: another thing you night want to try is to move this to the
+ * place marked by <HERE> (about 10 lines further down. If you do that, the
+ * players are created properly on the server, but a total of three players
+ * is created on the client.
+ */
+ _game->addPlayer(createPlayer(0, _name[0]));
+
+ if (_game->connectToServer(host_s, port))
+ emit infoText(i18n("Now connected to %1:%2.").arg(_host = host_s).
+ arg(_port = port));
+ else
+ emit infoText(i18n("Failed to connect to %1:%2.").arg(_host = host_s).
+ arg(_port = port));
+
+ // <HERE>
+
+ break;
+
+ default:
+ kdDebug(true, PROG_COOKIE) << "setGame parameter invalid: "
+ << _currGame << endl;
+ _currGame = None;
+ return;
+ }
+
+ // we are still having problems with player creation...
+
+ // FIXME - which status _game->setGameStatus(KGame::End);
+}
+
+void KBgEngineNg::slotPlayerJoinedGame(KPlayer *p)
+{
+ emit infoText(i18n("Player %1 (%2) has joined the game.").arg(p->name()).arg(p->id()));
+ cerr << i18n("Player %1 (%2) has joined the game.").arg(p->name()).arg(p->id()).latin1() << endl;
+}
+
+void KBgEngineNg::slotCreatePlayer(KPlayer *&p, int rtti, int io, bool v, KGame *g)
+{
+ Q_UNUSED(rtti)
+ Q_UNUSED(g)
+ Q_UNUSED(io)
+ emit infoText(i18n("creating player. virtual=%1").arg(v));
+ p = createPlayer(1);
+}
+
+void KBgEngineNg::slotClientConnected(Q_UINT32)
+{
+ cerr << "client has joint the game..." << endl;
+}
+
+void KBgEngineNg::slotClientDisconnected(Q_UINT32, bool)
+{
+ cerr << "KBgEngineNg::slotClientDisconnected" << endl;
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+// == start and init games =====================================================
+
+/*
+ * Start a new game. ...
+ */
+void KBgEngineNg::newGame()
+{
+ // TODO
+ cerr << "games are not yet working..." << endl;
+}
+
+/*
+ * Finish the last move - called by the timer and directly by the used
+ */
+void KBgEngineNg::done()
+{
+ // empty
+}
+
+/*
+ * Undo the last move
+ */
+void KBgEngineNg::undo()
+{
+ // TODO
+}
+
+/*
+ * Redo the last move
+ */
+void KBgEngineNg::redo()
+{
+ // TODO
+}
+
+/*
+ * Take the move string and make the changes on the working copy
+ * of the state.
+ */
+void KBgEngineNg::handleMove(QString *s)
+{
+ Q_UNUSED(s)
+ // TODO
+}
+
+/*
+ * Roll random dice for the player whose turn it is
+ */
+void KBgEngineNg::roll()
+{
+ // empty
+}
+
+/*
+ * If possible, roll random dice for player w
+ */
+void KBgEngineNg::rollDice(const int w)
+{
+ Q_UNUSED(w)
+ // empty
+}
+
+/*
+ * Double the cube for the player that can double - asks player
+ */
+void KBgEngineNg::cube()
+{
+ // TODO
+}
+
+/*
+ * Double the cube for player w
+ */
+void KBgEngineNg::doubleCube(const int)
+{
+ cube();
+}
+
+/*
+ * Put the engine specific details in the setup dialog
+ */
+void KBgEngineNg::getSetupPages(KDialogBase *)
+{
+ // FIXME: do nothing...
+}
+
+/*
+ * Called when the setup dialog is positively closed
+ */
+void KBgEngineNg::setupOk()
+{
+ // FIXME: do nothing...
+}
+void KBgEngineNg::setupDefault()
+{
+ // FIXME: do nothing...
+}
+void KBgEngineNg::setupCancel()
+{
+ // FIXME: do nothing...
+}
+
+
+// == various slots & functions ================================================
+
+/*
+ * Check with the user if we should really quit in the middle of a
+ * game.
+ */
+bool KBgEngineNg::queryClose()
+{
+ return true;
+}
+
+/*
+ * Quitting is fine at any time
+ */
+bool KBgEngineNg::queryExit()
+{
+ return true;
+}
+
+/*
+ * Load the last known sane state of the board
+ */
+void KBgEngineNg::load()
+{
+ // TODO
+}
+
+/*
+ * Store if cmd is allowed or not
+ */
+void KBgEngineNg::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;
+ }
+}
+
+
+// ********************************************************************************
+// ********************************************************************************
+
+// DONE
+
+// ********************************************************************************
+// ********************************************************************************
+
+
+/*
+ * Destructor.
+ */
+KBgEngineNg::~KBgEngineNg()
+{
+ saveConfig();
+ delete _game;
+}
+
+/*
+ * Restore settings
+ */
+void KBgEngineNg::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("next generation engine");
+
+ _port = config->readNumEntry("port", PROG_COOKIE);
+ _host = config->readEntry("host", "localhost");
+
+ _name[0] = config->readEntry("name_0", i18n("one"));
+ _name[1] = config->readEntry("name_1", i18n("two"));
+}
+
+/*
+ * Save the engine specific settings
+ */
+void KBgEngineNg::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("next generation engine");
+
+ config->writeEntry("port", _port);
+ config->writeEntry("host", _host);
+
+ config->writeEntry("name_0", _name[0]);
+ config->writeEntry("name_1", _name[1]);
+}
+
+/*
+ * Read the users input from the command line and send it to all
+ * players. Although the message gets the Cmd ID, it is currently
+ * handled as a regular text message.
+ */
+void KBgEngineNg::handleCommand(const QString& text)
+{
+ QByteArray msg;
+ QTextStream ts(msg, IO_WriteOnly);
+ ts << text;
+ if (!_game->sendMessage(msg, KBgGame::Cmd))
+ kdDebug(true, PROG_COOKIE) << "couldn't send message: "
+ << text.latin1() << endl;
+}
+
+/*
+ * Return a random integer between 1 and 6. Use the KGame random
+ * number generator.
+ */
+int KBgEngineNg::getRandom()
+{
+ return 1+_game->random()->getLong(6);
+}
+
+/*
+ * A player propert has changed - check if we care
+ */
+void KBgEngineNg::slotPropertyChanged(KGamePropertyBase *p, KPlayer *me)
+{
+ int player = (me->id() == _player[1]->id());
+
+ switch (p->id()) {
+
+ case KGamePropertyBase::IdName:
+ emit infoText(i18n("Player %1 has changed the name to %2.")
+ .arg(_name[player]).arg(me->name()));
+ _name[player] = me->name();
+ break;
+
+ default:
+ kdDebug(true, PROG_COOKIE) << "KBgPlayer (" << me << ") property change ("
+ << p->id() << ") ignored" << endl;
+ break;
+ }
+}
+
+/*
+ * A game property has changed
+ */
+void KBgEngineNg::slotPropertyChanged(KGamePropertyBase *p, KGame *me)
+{
+ Q_UNUSED(me)
+ switch (p->id()) {
+
+ default:
+ kdDebug(true, PROG_COOKIE) << "Change in GameProperty " << p->id()
+ << " has been ignored." << endl;
+ break;
+ }
+}
+
+/*
+ * Change the names of all local players
+ */
+void KBgEngineNg::changeName()
+{
+ bool ok = false;
+ QString name;
+
+ for (int i = 0; i < 2; i++) {
+ name = QString::null;
+ while (!_player[i]->isVirtual() && name.isEmpty()) {
+ if (i == 0)
+ name = KLineEditDlg::getText(i18n("Type the name of the first player:"),
+ _name[i], &ok, (QWidget *)parent());
+ else
+ name = KLineEditDlg::getText(i18n("Type the name of the second player:"),
+ _name[i], &ok, (QWidget *)parent());
+ if (!ok) return;
+ _player[i]->setName(name);
+ }
+ }
+}
+
+/*
+ * Receive data sent via KBgGame::sendMessage(...)
+ */
+void KBgEngineNg::slotNetworkData(int msgid, const QByteArray &msg, Q_UINT32 r, Q_UINT32 s)
+{
+ Q_UNUSED(r);
+ Q_UNUSED(s);
+ switch (msgid) {
+
+ case KBgGame::Cmd:
+ emit infoText(msg);
+ emit infoText(i18n("Players are %1 and %2").arg(_player[0]->name())
+ .arg(_player[1]->name()));
+ break;
+
+ default:
+ kdDebug(true, PROG_COOKIE) << "Ignored message ID: " << msgid << endl;
+ break;
+ }
+}
+
+/*
+ * Create the i-th player
+ */
+KBgPlayer * KBgEngineNg::createPlayer(int i, QString name)
+{
+ KBgPlayer *p = new KBgPlayer();
+
+ if (!name.isNull())
+ p->setName(name);
+
+ p->findProperty(KGamePropertyBase::IdName)->setEmittingSignal(true);
+
+ connect(p, SIGNAL(signalPropertyChanged(KGamePropertyBase *, KPlayer *)),
+ this, SLOT(slotPropertyChanged(KGamePropertyBase *, KPlayer *)));
+
+ return (_player[i] = p);
+}
+
+/*
+ * Create and connect the game object
+ */
+void KBgEngineNg::initGame()
+{
+ _game = new KBgGame(PROG_COOKIE);
+ _game->random()->setSeed(getpid()*time(NULL));
+
+ connect(_game, SIGNAL(signalPlayerJoinedGame(KPlayer *)),
+ this, SLOT(slotPlayerJoinedGame(KPlayer *)));
+ connect(_game, SIGNAL(signalCreatePlayer(KPlayer *&, int, int, bool, KGame *)),
+ this, SLOT(slotCreatePlayer(KPlayer *&, int, int, bool, KGame *)));
+
+ connect(_game, SIGNAL(signalClientConnected(Q_UINT32)),
+ this, SLOT(slotClientConnected(Q_UINT32)));
+ connect(_game, SIGNAL(signalClientDisconnected(Q_UINT32, bool)),
+ this, SLOT(slotClientDisconnected(Q_UINT32, bool)));
+
+ connect(_game, SIGNAL(signalPropertyChanged(KGamePropertyBase *, KGame *)),
+ this, SLOT(slotPropertyChanged(KGamePropertyBase *, KGame *)));
+ connect(_game, SIGNAL(signalNetworkData(int,const QByteArray &, Q_UINT32, Q_UINT32)),
+ this, SLOT(slotNetworkData(int,const QByteArray &, Q_UINT32, Q_UINT32)));
+}
+
+// EOF
diff --git a/kbackgammon/engines/nextgen/kbgng.h b/kbackgammon/engines/nextgen/kbgng.h
new file mode 100644
index 00000000..149f3bf6
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbgng.h
@@ -0,0 +1,263 @@
+/* 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$
+
+*/
+
+#ifndef __KBGNG_H
+#define __KBGNG_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+
+#include <qtimer.h>
+#include <qspinbox.h>
+#include <kaction.h>
+#include <qdatastream.h>
+#include <kgameproperty.h>
+
+#include <generic/kbgengine.h>
+
+#include "kbgboard.h"
+#include "kbgstatus.h"
+#include "kbgplayer.h"
+#include "kbggame.h"
+
+
+/**
+ *
+ * The interface of the next generation backgammon engine.
+ *
+ */
+class KBgEngineNg : public KBgEngine
+{
+ Q_OBJECT
+
+public:
+
+ /*
+ * Constructor and destructor
+ */
+ KBgEngineNg( QWidget *parent = 0, QString *name = 0, QPopupMenu *pmenu = 0);
+ virtual ~KBgEngineNg();
+
+ /**
+ * Fills the engine-specific page into the notebook
+ */
+ virtual void getSetupPages(KDialogBase *nb);
+
+ virtual void setupOk();
+ virtual void setupDefault();
+ virtual void setupCancel();
+
+ /*
+ * Check with the engine if we can quit. This may require user
+ * interaction.
+ */
+ virtual bool queryClose();
+
+ /**
+ * About to be closed. Let the engine exit properly.
+ */
+ virtual bool queryExit();
+
+
+public slots:
+
+ /**
+ * Read user settings from the config file
+ */
+ virtual void readConfig();
+
+ /**
+ * Save user settings to the config file
+ */
+ virtual void saveConfig();
+
+ /**
+ * Roll dice for the player w
+ */
+ virtual void rollDice(const int w);
+
+ /**
+ * Double the cube of player w
+ */
+ virtual void doubleCube(const int w);
+
+ /**
+ * A move has been made on the board - see the board class
+ * for the format of the string s
+ */
+ virtual void handleMove(QString *s);
+
+ /**
+ * Undo the last move
+ */
+ virtual void undo();
+
+ /**
+ * Redo the last move
+ */
+ virtual void redo();
+
+ /**
+ * Roll dice for whoevers turn it is
+ */
+ virtual void roll();
+
+ /**
+ * Double the cube for whoevers can double right now
+ */
+ virtual void cube();
+
+ /**
+ * Reload the board to the last known sane state
+ */
+ virtual void load();
+
+ /**
+ * Commit a move
+ */
+ virtual void done();
+
+ /**
+ * Process the string text
+ */
+ virtual void handleCommand(const QString& text);
+
+ /**
+ * Start a new game.
+ */
+ virtual void newGame();
+ virtual bool haveNewGame() {return true;}
+
+
+ void slotPlayerJoinedGame(KPlayer *p);
+ void slotNetworkData(int msgid, const QByteArray &msg, Q_UINT32 receiver, Q_UINT32 sender);
+ void slotCreatePlayer(KPlayer *&, int, int, bool, KGame *);
+
+ void slotClientDisconnected(Q_UINT32, bool);
+ void slotClientConnected(Q_UINT32);
+
+ void slotPropertyChanged(KGamePropertyBase *p, KGame *me);
+ void slotPropertyChanged(KGamePropertyBase *p, KPlayer *me);
+
+protected slots:
+
+ void initGame();
+
+ void setGame();
+
+ void changeName();
+
+protected:
+
+ void setAllowed(int cmd, bool f);
+
+private:
+
+
+ /**
+ * Who did the last roll
+ */
+ int lastRoll;
+
+ /**
+ * How many checkers to move
+ */
+ int toMove;
+
+ /**
+ * Various flags, representing the current status of the game
+ */
+ bool rollingAllowed, undoPossible, donePossible;
+ bool gameRunning, redoPossible, doublePossible;
+
+ /**
+ * Count the number of available undos
+ */
+ int dummy, undoCounter;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ enum GameTypes {None = -1, Local, NetServer, NetClient, MaxTypes};
+ KSelectAction * _gameSelect;
+ KAction* _connectAction;
+ KAction* _nameAction;
+ int _currGame;
+ int _nLocalPlayers;
+
+ int _nplayers;
+
+ QString _host;
+ Q_UINT16 _port;
+
+ // ************************************************************
+ // ************************************************************
+
+ // DONE
+
+ // ************************************************************
+ // ************************************************************
+
+
+protected:
+
+ /**
+ * Return a random integer between 1 and 6. The random numer
+ * is based on the @ref KRandomSequence of @ref KGame. Thus,
+ * the numbers should be synchronized across the network.
+ */
+ int getRandom();
+
+private:
+
+ /**
+ * Create the i-th player. Legal values for i are 0 and 1. The
+ * name of the player is taken from @ref _name and the parent of
+ * the player is @ref _player. That means that the players are
+ * automatically deleted.
+ */
+ KBgPlayer * createPlayer(int i, QString name = QString::null);
+
+private:
+
+ KBgGame* _game;
+
+ QString _name[2];
+
+ KBgPlayer* _player[2];
+
+};
+
+#endif // __KBGNG_H
diff --git a/kbackgammon/engines/nextgen/kbgplayer.cpp b/kbackgammon/engines/nextgen/kbgplayer.cpp
new file mode 100644
index 00000000..f0b3a7ed
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbgplayer.cpp
@@ -0,0 +1,62 @@
+/* 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 "kbgplayer.moc"
+#include "kbgplayer.h"
+
+#include <kgame.h>
+
+#include <iostream>
+
+
+/*
+ * Constructors
+ */
+KBgPlayer::KBgPlayer()
+ : KPlayer()
+{
+ // do nothing...
+}
+KBgPlayer::KBgPlayer(KGame *game)
+ : KPlayer(game)
+{
+ // do nothing...
+}
+
+int KBgPlayer::rtti() const
+{
+ return 10500;
+}
+
+bool KBgPlayer::load(QDataStream &stream)
+{
+ KPlayer::load(stream);
+ cerr << "-------- KBgPlayer::load" << endl;
+ return false;
+}
+bool KBgPlayer::save(QDataStream &stream)
+{
+ KPlayer::save(stream);
+ cerr << "-------- KBgPlayer::save" << endl;
+ return false;
+}
diff --git a/kbackgammon/engines/nextgen/kbgplayer.h b/kbackgammon/engines/nextgen/kbgplayer.h
new file mode 100644
index 00000000..7c11d83c
--- /dev/null
+++ b/kbackgammon/engines/nextgen/kbgplayer.h
@@ -0,0 +1,58 @@
+/* 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$
+
+*/
+
+#ifndef __KBGPLAYER_H
+#define __KBGPLAYER_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kplayer.h>
+#include <qdatastream.h>
+
+class KGame;
+
+
+/**
+ *
+ *
+ */
+class KBgPlayer : public KPlayer
+{
+ Q_OBJECT
+
+public:
+
+ KBgPlayer();
+ KBgPlayer(KGame* game);
+
+ virtual int rtti() const;
+
+ virtual bool load(QDataStream &stream);
+ virtual bool save(QDataStream &stream);
+
+};
+
+#endif // __KBGPLAYER_H
+
diff --git a/kbackgammon/engines/offline/Makefile.am b/kbackgammon/engines/offline/Makefile.am
new file mode 100644
index 00000000..82d7a681
--- /dev/null
+++ b/kbackgammon/engines/offline/Makefile.am
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libkbgoffline.la
+
+libkbgoffline_la_SOURCES = kbgoffline.cpp
+
+INCLUDES= -I$(top_srcdir)/kbackgammon -I$(top_srcdir)/kbackgammon/engines \
+ $(all_includes)
+
+METASOURCES = AUTO
+
diff --git a/kbackgammon/engines/offline/kbgoffline.cpp b/kbackgammon/engines/offline/kbgoffline.cpp
new file mode 100644
index 00000000..920dc741
--- /dev/null
+++ b/kbackgammon/engines/offline/kbgoffline.cpp
@@ -0,0 +1,810 @@
+/* Yo Emacs, this -*- C++ -*-
+
+ Copyright (C) 1999-2001 Jens Hoefkens
+ jens@hoefkens.com
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ $Id$
+
+*/
+
+#include "kbgoffline.moc"
+#include "kbgoffline.h"
+
+#include <qlayout.h>
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qtimer.h>
+#include <qspinbox.h>
+#include <qwhatsthis.h>
+#include <qlineedit.h>
+#include <qvbox.h>
+
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kiconloader.h>
+#include <kstdaction.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kmainwindow.h>
+#include <klineeditdlg.h>
+#include <kaction.h>
+#include <krandomsequence.h>
+#include <ktabctl.h>
+#include <stdlib.h>
+
+#include "version.h"
+
+class KBgEngineOfflinePrivate
+{
+public:
+
+ /*
+ * Various flags, representing the current status of the game
+ */
+ bool mRollFlag, mUndoFlag, mDoneFlag, mCubeFlag, mGameFlag, mRedoFlag;
+
+ /*
+ * Store two copies of the game: one backup and a working copy
+ */
+ KBgStatus mGame[2];
+
+ /*
+ * Use the standard method of obtaining random numbers
+ */
+ KRandomSequence *mRandom;
+
+ /*
+ * Game actions
+ */
+ KAction *mNew, *mSwap;
+ KToggleAction *mEdit;
+
+ /*
+ * Player's names
+ */
+ QString mName[2];
+
+ /*
+ * Who did the last roll
+ */
+ int mRoll;
+
+ /*
+ * How many checkers to move
+ */
+ int mMove;
+
+ /*
+ * Count the number of available undos
+ */
+ int mUndo;
+
+ /*
+ * Entry fields for the names
+ */
+ QLineEdit *mLe[2];
+
+};
+
+
+// == constructor, destructor and other ========================================
+
+/*
+ * Constructor
+ */
+KBgEngineOffline::KBgEngineOffline(QWidget *parent, QString *name, QPopupMenu *pmenu)
+ : KBgEngine(parent, name, pmenu)
+{
+ d = new KBgEngineOfflinePrivate();
+
+ /*
+ * get some entropy for the dice
+ */
+ d->mRandom = new KRandomSequence;
+ d->mRandom->setSeed(0);
+
+ /*
+ * Create engine specific actions
+ */
+ d->mNew = new KAction(i18n("&New Game..."), 0, this, SLOT(newGame()), this);
+ d->mSwap = new KAction(i18n("&Swap Colors"), 0, this, SLOT(swapColors()), this);
+
+ d->mEdit = new KToggleAction(i18n("&Edit Mode"), 0, this,
+ SLOT(toggleEditMode()), this);
+ d->mEdit->setChecked(false);
+
+ /*
+ * create & initialize the menu
+ */
+ d->mNew->plug(menu);
+ d->mEdit->plug(menu);
+ d->mSwap->plug(menu);
+
+ /*
+ * get standard board and set it
+ */
+ initGame();
+ emit newState(d->mGame[0]);
+
+ /*
+ * initialize the commit timeout
+ */
+ ct = new QTimer(this);
+ connect(ct, SIGNAL(timeout()), this, SLOT(done()));
+
+ /*
+ * internal statue variables
+ */
+ d->mRollFlag = d->mUndoFlag = d->mGameFlag = d->mDoneFlag = false;
+ connect(this, SIGNAL(allowCommand(int, bool)), this, SLOT(setAllowed(int, bool)));
+
+ /*
+ * Restore last stored settings
+ */
+ readConfig();
+}
+
+/*
+ * Destructor. The only child is the popup menu.
+ */
+KBgEngineOffline::~KBgEngineOffline()
+{
+ saveConfig();
+ delete d->mRandom;
+ delete d;
+}
+
+
+// == configuration handling ===================================================
+
+/*
+ * Put the engine specific details in the setup dialog
+ */
+void KBgEngineOffline::getSetupPages(KDialogBase *nb)
+{
+ /*
+ * Main Widget
+ */
+ QVBox *vbp = nb->addVBoxPage(i18n("Offline Engine"), i18n("Use this to configure the Offline engine"),
+ kapp->iconLoader()->loadIcon(PROG_NAME "_engine", KIcon::Desktop));
+
+ /*
+ * Get a multi page work space
+ */
+ KTabCtl *tc = new KTabCtl(vbp, "offline tabs");
+
+ /*
+ * Player names
+ */
+ QWidget *w = new QWidget(tc);
+ QGridLayout *gl = new QGridLayout(w, 2, 1, nb->spacingHint());
+
+ /*
+ * Group boxes
+ */
+ QGroupBox *gbn = new QGroupBox(i18n("Names"), w);
+
+ gl->addWidget(gbn, 0, 0);
+
+ gl = new QGridLayout(gbn, 2, 2, 20);
+
+ d->mLe[0] = new QLineEdit(d->mName[0], gbn);
+ d->mLe[1] = new QLineEdit(d->mName[1], gbn);
+
+ QLabel *lb[2];
+ lb[0] = new QLabel(i18n("First player:"), gbn);
+ lb[1] = new QLabel(i18n("Second player:"), gbn);
+
+ for (int i = 0; i < 2; i++) {
+ gl->addWidget(lb[i], i, 0);
+ gl->addWidget(d->mLe[i], i, 1);
+ }
+
+ QWhatsThis::add(d->mLe[0], i18n("Enter the name of the first player."));
+ QWhatsThis::add(d->mLe[1], i18n("Enter the name of the second player."));
+
+ /*
+ * Done with the page, put it in
+ */
+ gl->activate();
+ tc->addTab(w, i18n("&Player Names"));
+}
+
+/*
+ * Called when the setup dialog is positively closed
+ */
+void KBgEngineOffline::setupOk()
+{
+ d->mName[0] = d->mLe[0]->text();
+ d->mName[1] = d->mLe[1]->text();
+}
+void KBgEngineOffline::setupDefault()
+{
+ d->mName[0] = i18n("South");
+ d->mName[1] = i18n("North");
+}
+void KBgEngineOffline::setupCancel()
+{
+ // do nothing
+}
+
+/*
+ * Restore settings
+ */
+void KBgEngineOffline::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("offline engine");
+
+ d->mName[0] = config->readEntry("player-one", i18n("South")); // same as above
+ d->mName[1] = config->readEntry("player-two", i18n("North")); // same as above
+ cl = config->readNumEntry("timer", 2500);
+}
+
+/*
+ * Save the engine specific settings
+ */
+void KBgEngineOffline::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("offline engine");
+
+ config->writeEntry("player-one", d->mName[0] );
+ config->writeEntry("player-two", d->mName[1]);
+ config->writeEntry("timer", cl);
+}
+
+
+// == start and init games =====================================================
+
+/*
+ * Start a new game.
+ */
+void KBgEngineOffline::newGame()
+{
+ int u = 0;
+ int t = 0;
+
+ /*
+ * If there is a game running we warn the user first
+ */
+ if (d->mGameFlag && (KMessageBox::warningYesNo((QWidget *)parent(),
+ i18n("A game is currently in progress. "
+ "Starting a new one will terminate it."),
+ QString::null, i18n("Start New Game"),
+ i18n("Continue Old Game"))
+ == KMessageBox::No))
+ return;
+
+ /*
+ * Separate from the previous game
+ */
+ emit infoText("<br/><br/><br/>");
+
+ /*
+ * Get player's names - user can still cancel
+ */
+ if (!queryPlayerName(US) || !queryPlayerName(THEM))
+ return;
+
+ /*
+ * let the games begin
+ */
+ d->mGameFlag = true;
+
+ /*
+ * Initialize the board
+ */
+ initGame();
+
+ /*
+ * Figure out who starts by rolling
+ */
+ while (u == t) {
+ u = getRandom();
+ t = getRandom();
+ emit infoText(i18n("%1 rolls %2, %3 rolls %4.").
+ arg(d->mName[0]).arg(u).arg(d->mName[1]).arg(t));
+ }
+
+ if (u > t) {
+ emit infoText(i18n("%1 makes the first move.").arg(d->mName[0]));
+ d->mRoll = US;
+ } else {
+ emit infoText(i18n("%1 makes the first move.").arg(d->mName[1]));
+ d->mRoll = THEM;
+ int n = u; u = t; t = n;
+ }
+
+ /*
+ * set the dice and tell the board
+ */
+ rollDiceBackend(d->mRoll, u, t);
+
+ /*
+ * tell the user
+ */
+ emit statText(i18n("%1 vs. %2").arg(d->mName[0]).arg(d->mName[1]));
+}
+
+/*
+ * Initialize the state descriptors mGame[0|1]
+ */
+void KBgEngineOffline::initGame()
+{
+ /*
+ * nobody rolled yet
+ */
+ d->mRoll = -1;
+
+ /*
+ * set up a standard game
+ */
+ d->mGame[0].setCube(1, true, true);
+ d->mGame[0].setDirection(+1);
+ d->mGame[0].setColor(+1);
+ for (int i = 1; i < 25; i++)
+ d->mGame[0].setBoard(i, US, 0);
+ d->mGame[0].setBoard( 1, US, 2); d->mGame[0].setBoard( 6, THEM, 5);
+ d->mGame[0].setBoard( 8, THEM, 3); d->mGame[0].setBoard(12, US, 5);
+ d->mGame[0].setBoard(13, THEM, 5); d->mGame[0].setBoard(17, US, 3);
+ d->mGame[0].setBoard(19, US, 5); d->mGame[0].setBoard(24, THEM, 2);
+ d->mGame[0].setHome(US, 0); d->mGame[0].setHome(THEM, 0);
+ d->mGame[0].setBar(US, 0); d->mGame[0].setBar(THEM, 0);
+ d->mGame[0].setDice(US , 0, 0); d->mGame[0].setDice(US , 1, 0);
+ d->mGame[0].setDice(THEM, 0, 0); d->mGame[0].setDice(THEM, 1, 0);
+
+ /*
+ * save backup of the game state
+ */
+ d->mGame[1] = d->mGame[0];
+
+ emit allowCommand(Load, true);
+}
+
+/*
+ * Open a dialog to query for the name of player w. Return true unless
+ * the dialog was canceled.
+ */
+bool KBgEngineOffline::queryPlayerName(int w)
+{
+ bool ret = false;
+ QString *name;
+ QString text;
+
+ if (w == US) {
+ name = &d->mName[0];
+ text = i18n("Please enter the nickname of the player whose home\n"
+ "is in the lower half of the board:");
+ } else {
+ name = &d->mName[1];
+ text = i18n("Please enter the nickname of the player whose home\n"
+ "is in the upper half of the board:");
+ }
+
+ do {
+ *name = KLineEditDlg::getText(text, *name, &ret, (QWidget *)parent());
+ if (!ret) break;
+
+ } while (name->isEmpty());
+
+ return ret;
+}
+
+
+// == moving ===================================================================
+
+/*
+ * Finish the last move - called by the timer and directly by the used
+ */
+void KBgEngineOffline::done()
+{
+ ct->stop();
+
+ emit allowMoving(false);
+ emit allowCommand(Done, false);
+ emit allowCommand(Undo, false);
+
+ if (abs(d->mGame[0].home(d->mRoll)) == 15) {
+
+ emit infoText(i18n("%1 wins the game. Congratulations!").
+ arg((d->mRoll == US) ? d->mName[0] : d->mName[1]));
+ d->mGameFlag = false;
+ emit allowCommand(Roll, false);
+ emit allowCommand(Cube, false);
+
+ } else {
+
+ emit allowCommand(Roll, true);
+ if (d->mGame[0].cube((d->mRoll == US ? THEM : US)) > 0) {
+
+ d->mGame[0].setDice(US , 0, 0); d->mGame[0].setDice(US , 1, 0);
+ d->mGame[0].setDice(THEM, 0, 0); d->mGame[0].setDice(THEM, 1, 0);
+
+ emit newState(d->mGame[0]);
+ emit getState(&d->mGame[0]);
+
+ d->mGame[1] = d->mGame[0];
+
+ emit infoText(i18n("%1, please roll or double.").
+ arg((d->mRoll == THEM) ? d->mName[0] : d->mName[1]));
+ emit allowCommand(Cube, true);
+
+ } else {
+
+ roll();
+ emit allowCommand(Cube, false);
+ }
+ }
+}
+
+/*
+ * Undo the last move
+ */
+void KBgEngineOffline::undo()
+{
+ ct->stop();
+
+ d->mRedoFlag = true;
+ ++d->mUndo;
+
+ emit allowMoving(true);
+ emit allowCommand(Done, false);
+ emit allowCommand(Redo, true);
+ emit undoMove();
+}
+
+/*
+ * Redo the last move
+ */
+void KBgEngineOffline::redo()
+{
+ --d->mUndo;
+ emit redoMove();
+}
+
+/*
+ * Take the move string and make the changes on the working copy
+ * of the state.
+ */
+void KBgEngineOffline::handleMove(QString *s)
+{
+ int index = 0;
+ QString t = s->mid(index, s->find(' ', index));
+ index += 1 + t.length();
+ int moves = t.toInt();
+
+ /*
+ * Allow undo and possibly start the commit timer
+ */
+ d->mRedoFlag &= ((moves < d->mMove) && (d->mUndo > 0));
+ emit allowCommand(Undo, moves > 0);
+ emit allowCommand(Redo, d->mRedoFlag);
+ emit allowCommand(Done, moves == d->mMove);
+ if (moves == d->mMove && cl) {
+ emit allowMoving(false);
+ ct->start(cl, true);
+ }
+
+ /*
+ * Apply moves to d->mGame[1] and store results in d->mGame[0]
+ */
+ d->mGame[0] = d->mGame[1];
+
+ /*
+ * process each individual move
+ */
+ for (int i = 0; i < moves; i++) {
+ bool kick = false;
+ t = s->mid(index, s->find(' ', index) - index);
+ index += 1 + t.length();
+ char c = '-';
+ if (t.contains('+')) {
+ c = '+';
+ kick = true;
+ }
+ QString r = t.left(t.find(c));
+ if (r.contains("bar")) {
+ d->mGame[0].setBar(d->mRoll, abs(d->mGame[0].bar(d->mRoll)) - 1);
+ } else {
+ int from = r.toInt();
+ d->mGame[0].setBoard(from, d->mRoll, abs(d->mGame[0].board(from)) - 1);
+ }
+ t.remove(0, 1 + r.length());
+ if (t.contains("off")) {
+ d->mGame[0].setHome(d->mRoll, abs(d->mGame[0].home(d->mRoll)) + 1);
+ } else {
+ int to = t.toInt();
+ if (kick) {
+ d->mGame[0].setBoard(to, d->mRoll, 0);
+ int el = ((d->mRoll == US) ? THEM : US);
+ d->mGame[0].setBar(el, abs(d->mGame[0].bar(el)) + 1);
+ }
+ d->mGame[0].setBoard(to, d->mRoll, abs(d->mGame[0].board(to)) + 1);
+ }
+ }
+}
+
+
+// == dice & rolling ===========================================================
+
+/*
+ * Roll random dice for the player whose turn it is
+ */
+void KBgEngineOffline::roll()
+{
+ rollDice((d->mRoll == US) ? THEM : US);
+}
+
+/*
+ * If possible, roll random dice for player w
+ */
+void KBgEngineOffline::rollDice(const int w)
+{
+ if ((d->mRoll != w) && d->mRollFlag) {
+ rollDiceBackend(w, getRandom(), getRandom());
+ return;
+ }
+ emit infoText(i18n("It's not your turn to roll!"));
+}
+
+/*
+ * Return a random integer between 1 and 6. According to the man
+ * page of rand(), this is the way to go...
+ */
+int KBgEngineOffline::getRandom()
+{
+ return 1+d->mRandom->getLong(6);
+}
+
+/*
+ * Set the dice for player w to a and b. Reload the board and determine the
+ * maximum number of moves
+ */
+void KBgEngineOffline::rollDiceBackend(const int w, const int a, const int b)
+{
+ /*
+ * This is a special case that stems from leaving the edit
+ * mode.
+ */
+ if (a == 0)
+ return;
+
+ /*
+ * Set the dice and tel the board about the new state
+ */
+ d->mGame[0].setDice(w, 0, a);
+ d->mGame[0].setDice(w, 1, b);
+ d->mGame[0].setDice((w == US) ? THEM : US, 0, 0);
+ d->mGame[0].setDice((w == US) ? THEM : US, 1, 0);
+ d->mGame[0].setTurn(w);
+
+ d->mGame[1] = d->mGame[0];
+
+ d->mRoll = w;
+ emit newState(d->mGame[0]);
+
+ /*
+ * No more roling until Done and no Undo yet
+ */
+ emit allowCommand(Undo, false);
+ emit allowCommand(Roll, false);
+ d->mRedoFlag = false;
+ d->mUndo = 0;
+
+ /*
+ * Tell the players how many checkers to move
+ */
+ switch (d->mMove = d->mGame[0].moves()) {
+ case -1:
+ emit infoText(i18n("Game over!"));
+ d->mGameFlag = false;
+ emit allowCommand(Roll, false);
+ emit allowCommand(Cube, false);
+ emit allowMoving(false);
+ break;
+ case 0:
+ emit infoText(i18n("%1, you cannot move.").
+ arg((w == US) ? d->mName[0] : d->mName[1]));
+ if (cl)
+ ct->start(cl, true);
+ emit allowMoving(false);
+ break;
+// case 1:
+ default:
+ emit infoText(QString((w == US) ? d->mName[0] : d->mName[1]) +
+ i18n(", please move 1 piece.",", please move %n pieces.",d->mMove));
+ emit allowMoving(true);
+ break;
+ }
+}
+
+
+// == cube =====================================================================
+
+/*
+ * Double the cube for the player that can double - asks player
+ */
+void KBgEngineOffline::cube()
+{
+ int w = ((d->mRoll == US) ? THEM : US);
+
+ if (d->mRollFlag && d->mGame[0].cube(w) > 0) {
+ emit allowCommand(Cube, false);
+ if (KMessageBox::questionYesNo((QWidget *)parent(),
+ i18n("%1 has doubled. %2, do you accept the double?").
+ arg((w == THEM) ? d->mName[1] : d->mName[0]).
+ arg((w == US) ? d->mName[1] : d->mName[0]),
+ i18n("Doubling"), i18n("Accept"), i18n("Reject")) != KMessageBox::Yes) {
+ d->mGameFlag = false;
+ emit allowCommand(Roll, false);
+ emit allowCommand(Cube, false);
+ emit infoText(i18n("%1 wins the game. Congratulations!").
+ arg((w == US) ? d->mName[0] : d->mName[1]));
+ return;
+ }
+
+ emit infoText(i18n("%1 has accepted the double. The game continues.").
+ arg((w == THEM) ? d->mName[0] : d->mName[1]));
+
+ if (d->mGame[0].cube(US)*d->mGame[0].cube(THEM) > 0)
+ d->mGame[0].setCube(2, w == THEM, w == US);
+ else
+ d->mGame[0].setCube(2*d->mGame[0].cube(w), w == THEM, w == US);
+
+ emit newState(d->mGame[0]);
+ emit getState(&d->mGame[0]);
+
+ d->mGame[1] = d->mGame[0];
+
+ roll();
+ }
+}
+
+/*
+ * Double the cube for player w
+ */
+void KBgEngineOffline::doubleCube(const int)
+{
+ cube();
+}
+
+
+// == various slots & functions ================================================
+
+/*
+ * Check with the user if we should really quit in the middle of a
+ * game.
+ */
+bool KBgEngineOffline::queryClose()
+{
+ if (!d->mGameFlag)
+ return true;
+
+ switch (KMessageBox::warningContinueCancel((QWidget *)parent(),
+ i18n("In the middle of a game. "
+ "Really quit?"), QString::null, KStdGuiItem::quit())) {
+ case KMessageBox::Continue :
+ return TRUE;
+ case KMessageBox::Cancel :
+ return FALSE;
+ default: // cancel
+ return FALSE;
+ }
+ return true;
+}
+
+/*
+ * Quitting is fine at any time
+ */
+bool KBgEngineOffline::queryExit()
+{
+ return true;
+}
+
+/*
+ * Handle textual commands. Right now, all commands are ignored
+ */
+void KBgEngineOffline::handleCommand(const QString& cmd)
+{
+ emit infoText(i18n("Text commands are not yet working. "
+ "The command '%1' has been ignored.").arg(cmd));
+}
+
+/*
+ * Load the last known sane state of the board
+ */
+void KBgEngineOffline::load()
+{
+ if (d->mEdit->isChecked())
+ emit newState(d->mGame[1]);
+ else {
+ // undo up to four moves
+ undo();
+ undo();
+ undo();
+ undo();
+ }
+}
+
+/*
+ * Store if cmd is allowed or not
+ */
+void KBgEngineOffline::setAllowed(int cmd, bool f)
+{
+ switch (cmd) {
+ case Roll:
+ d->mRollFlag = f;
+ return;
+ case Undo:
+ d->mUndoFlag = f;
+ return;
+ case Cube:
+ d->mCubeFlag = f;
+ return;
+ case Done:
+ d->mDoneFlag = f;
+ return;
+ }
+}
+
+/*
+ * Swaps the used colors on the board
+ */
+void KBgEngineOffline::swapColors()
+{
+ d->mGame[1].setDice(US, 0, d->mGame[0].dice(US, 0));
+ d->mGame[1].setDice(US, 1, d->mGame[0].dice(US, 1));
+ d->mGame[1].setDice(THEM, 0, d->mGame[0].dice(THEM, 0));
+ d->mGame[1].setDice(THEM, 1, d->mGame[0].dice(THEM, 1));
+ d->mGame[1].setColor(d->mGame[1].color(THEM), US);
+ emit newState(d->mGame[1]);
+ emit getState(&d->mGame[1]);
+ d->mGame[0] = d->mGame[1];
+}
+
+/*
+ * Switch back and forth between edit and play mode
+ */
+void KBgEngineOffline::toggleEditMode()
+{
+ emit setEditMode(d->mEdit->isChecked());
+ if (d->mEdit->isChecked()) {
+ ct->stop();
+ d->mNew->setEnabled(false);
+ d->mSwap->setEnabled(false);
+ emit allowCommand(Undo, false);
+ emit allowCommand(Roll, false);
+ emit allowCommand(Done, false);
+ emit allowCommand(Cube, false);
+ emit statText(i18n("%1 vs. %2 - Edit Mode").arg(d->mName[0]).arg(d->mName[1]));
+ } else {
+ d->mNew->setEnabled(true);
+ d->mSwap->setEnabled(true);
+ emit statText(i18n("%1 vs. %2").arg(d->mName[0]).arg(d->mName[1]));
+ emit getState(&d->mGame[1]);
+ d->mGame[0] = d->mGame[1];
+ emit allowCommand(Done, d->mDoneFlag);
+ emit allowCommand(Cube, d->mCubeFlag);
+ emit allowCommand(Undo, d->mUndoFlag);
+ emit allowCommand(Roll, d->mRollFlag);
+ int w =((d->mGame[0].dice(US, 0) && d->mGame[0].dice(US, 1)) ? US : THEM);
+ rollDiceBackend(w, d->mGame[0].dice(w, 0), d->mGame[0].dice(w, 1));
+ }
+}
+
+// EOF
diff --git a/kbackgammon/engines/offline/kbgoffline.h b/kbackgammon/engines/offline/kbgoffline.h
new file mode 100644
index 00000000..db2bdc03
--- /dev/null
+++ b/kbackgammon/engines/offline/kbgoffline.h
@@ -0,0 +1,213 @@
+/* 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$
+
+*/
+
+#ifndef __KBGOFFLINE_H
+#define __KBGOFFLINE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <generic/kbgengine.h>
+
+#include "kbgboard.h"
+#include "kbgstatus.h"
+
+class KBgEngineOfflinePrivate;
+
+/**
+ *
+ * The interface of an offline backgammon engine. The engine is inherently
+ * stupid and doesn't play - it just manages the games betweeen two humans
+ * sitting at the same computer. Network enabled games will be part of the
+ * next generation engine (KBgNg).
+ *
+ * @short The offline backgammon engine
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ *
+ */
+class KBgEngineOffline : public KBgEngine
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgEngineOffline(QWidget *parent = 0, QString *name = 0, QPopupMenu *pmenu = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgEngineOffline();
+
+ /**
+ * Fills the engine-specific page into the notebook
+ */
+ virtual void getSetupPages(KDialogBase *nb);
+
+ /**
+ * Save new steup
+ */
+ virtual void setupOk();
+
+ /**
+ * Load default setup
+ */
+ virtual void setupDefault();
+
+ /**
+ * Cancel the changes to the setup
+ */
+ virtual void setupCancel();
+
+ /**
+ * Check with the engine if we can quit. This may require user
+ * interaction.
+ */
+ virtual bool queryClose();
+
+ /**
+ * About to be closed. Let the engine exit properly.
+ */
+ virtual bool queryExit();
+
+public slots:
+
+ /**
+ * Read user settings from the config file
+ */
+ virtual void readConfig();
+
+ /**
+ * Save user settings to the config file
+ */
+ virtual void saveConfig();
+
+ /**
+ * Roll dice for the player w
+ */
+ virtual void rollDice(const int w);
+
+ /**
+ * Double the cube of player w
+ */
+ virtual void doubleCube(const int w);
+
+ /**
+ * A move has been made on the board - see the board class
+ * for the format of the string s
+ */
+ virtual void handleMove(QString *s);
+
+ /**
+ * Undo the last move
+ */
+ virtual void undo();
+
+ /**
+ * Redo the last move
+ */
+ virtual void redo();
+
+ /**
+ * Roll dice for whoevers turn it is
+ */
+ virtual void roll();
+
+ /**
+ * Double the cube for whoevers can double right now
+ */
+ virtual void cube();
+
+ /**
+ * Reload the board to the last known sane state
+ */
+ virtual void load();
+
+ /**
+ * Commit a move
+ */
+ virtual void done();
+
+ /**
+ * Process the string cmd
+ */
+ virtual void handleCommand(const QString& cmd);
+
+ /**
+ * Start a new game.
+ */
+ virtual void newGame();
+ virtual bool haveNewGame() {return true;}
+
+
+protected slots:
+
+ /**
+ * Initialize the state descriptors game[0] and game[1]
+ */
+ void initGame();
+
+ /**
+ * Switch back and forth between edit and play mode
+ */
+ void toggleEditMode();
+
+ /**
+ * Store if cmd is allowed or not
+ */
+ void setAllowed(int cmd, bool f);
+
+ /**
+ * Swaps the used colors on the board
+ */
+ void swapColors();
+
+protected:
+
+ /**
+ * Returns a random integer between 1 and 6
+ */
+ int getRandom();
+
+ /**
+ * Set the dice for player w to a and b. Reload the board and determine the
+ * maximum number of moves
+ */
+ void rollDiceBackend(const int w, const int a, const int b);
+
+ /**
+ * Open a dialog to query for the name of player w. Return true unless
+ * the dialog was canceled.
+ */
+ bool queryPlayerName(int w);
+
+private:
+
+ KBgEngineOfflinePrivate *d;
+
+};
+
+#endif // __KBGOFFLINE_H
diff --git a/kbackgammon/eventsrc b/kbackgammon/eventsrc
new file mode 100644
index 00000000..de6f5f90
--- /dev/null
+++ b/kbackgammon/eventsrc
@@ -0,0 +1,802 @@
+[!Global!]
+IconName=kbackgammon
+Comment=KBackgammon
+Comment[af]=Kbackgammon
+Comment[ar]=لعبة النرد/الطاولة (KBackgammon)
+Comment[be]=Нарды
+Comment[bn]=কে-ব্যাকগ্যামোন
+Comment[cs]=Vrchcáby
+Comment[eo]=Bakgamono
+Comment[hi]=के-बैकगेमॉन
+Comment[ne]=केडीई ब्याकगामोन
+Comment[pt_BR]=KGamão
+Comment[ro]=Joc de table
+Comment[sv]=Kbackgammon
+Comment[ta]=கேபேக்கமான்
+Comment[tg]=KНардбозӣ
+Comment[tr]=KTavla
+Comment[zh_TW]=KBackgammon 西洋雙陸棋
+
+
+[game over w]
+Name=Game over, you won
+Name[af]=Speletjie bo, jy wen
+Name[ar]=اللعبة انتهت، لقد فزت
+Name[az]=Oyun Qurtardı, siz uddunuz
+Name[be]=Канец гульні, вы выйгралі
+Name[bg]=Спечелихте
+Name[bn]=খেল খতম, আপনি জিতেছেন
+Name[br]=Echu an abadenn, aet out ar maout
+Name[bs]=Igra završena, vi ste pobjednik
+Name[ca]=Final del joc, heu guanyat
+Name[cs]=Konec hry, vyhrál(a) jste
+Name[cy]=Gêm drosodd, ennill wnaethoch chi
+Name[da]=Spillet forbi, du vandt
+Name[de]=Spiel beendet, Sie haben gewonnen
+Name[el]=Τέλος παιχνιδιού, νικήσατε
+Name[eo]=Ludo finita, vi gajnis
+Name[es]=Fin de la partida, usted ganó
+Name[et]=Mäng läbi, sina võitsid
+Name[eu]=Jokoa amaitu da, irabazi duzu
+Name[fa]=بازی تمام شد، شما بردید
+Name[fi]=Peli loppu, voitit
+Name[fr]=Fin de la partie, vous avez gagné
+Name[gl]=Fin do xogo, vostede gaña
+Name[he]=המשחק הסתיים, ניצחת
+Name[hi]=खेल ख़त्म, आप जीते
+Name[hr]=Igra je završena. Pobijedili ste!
+Name[hu]=Vége a játéknak, Ön nyert
+Name[is]=Leik lokið, þú vannst
+Name[it]=Partita finita, hai vinto
+Name[ja]=ゲームオーバー、あなたの勝ち
+Name[km]=ល្បែង​ចប់, អ្នក​ឈ្នះ​ហើយ
+Name[lt]=Žaidimas baigtas, jūs laimėjote
+Name[lv]=Spēles beigas, jūs uzvarējāt
+Name[mk]=Играта заврши, вие победивте
+Name[mt]=Logħba spiċċat - int irbaħt
+Name[nb]=Spillet er slutt, du vant
+Name[nds]=Speel vörbi, Du hest wunnen
+Name[ne]=खेल समाप्त, तपाईँले जित्नु भयो
+Name[nl]=Spel is afgelopen, u hebt gewonnen.
+Name[nn]=Spelet er slutt, du vann
+Name[nso]=Papadi e fedile, o fentse
+Name[pa]=ਖੇਡ ਖਤਮ, ਤੁਸੀਂ ਜਿੱਤ ਗਏ
+Name[pl]=Koniec gry, wygrałeś
+Name[pt]=Fim do jogo, ganhou
+Name[pt_BR]=Fim do jogo; você ganhou
+Name[ro]=Joc terminat. Ai cîştigat.
+Name[ru]=Конец игры, вы выиграли
+Name[se]=Speallu nogai, don vuitet
+Name[sk]=Koniec hry, vyhrali ste
+Name[sl]=Konec igre, zmagali ste
+Name[sr]=Крај игре, победили сте
+Name[sr@Latn]=Kraj igre, pobedili ste
+Name[sv]=Spelet är slut, du vann
+Name[ta]=விளையாட்டு முடிந்தது, நீங்கள் வென்றுவிட்டீர்கள்
+Name[tg]=Бозӣ ба итмом расид, шумо ғолиб омадед
+Name[th]=จบเกม - คุณชนะ
+Name[tr]=Oyun bitti, sen kazandın
+Name[uk]=Гру завершено, ви виграли
+Name[uz]=Oʻyin tugadi, siz gʻalaba qozondingiz
+Name[uz@cyrillic]=Ўйин тугади, сиз ғалаба қозондингиз
+Name[ven]=Mutambo wo fhela, no wina
+Name[vi]=Trò chơi kết thúc, bạn thắng
+Name[wa]=Li djeu est houte, vos avoz wangnî
+Name[xh]=Umdlalo uphelile, uphumelele
+Name[zh_CN]=游戏结束,您赢了
+Name[zh_TW]=遊戲結束,您贏了
+Name[zu]=Umdlalo uphelile, uphemelele
+Comment=You have won the current game of backgammon
+Comment[af]=Jy het wen die huidige speletjie van backgammon
+Comment[ar]=لقد فزت اللعبة الحالية من لعبة النرد/الطاولة
+Comment[az]=Hazırkı nərdtaxta oyununu uddunuz
+Comment[be]=Вы выйгралі партыю ў нарды
+Comment[bg]=Спечелихте
+Comment[bn]=এই ব্যাকগ্যামোন খেলাটি আপনি জিতেছেন
+Comment[bs]=Pobjedili ste u trenutnoj backgammon igri
+Comment[ca]=Heu guanyat aquesta partida de backgamon
+Comment[cs]=Tuto hru ve vrchcáby jste vyhráli
+Comment[cy]=Rydych wedi ennill y gêm gyfredol o dawlbwrdd
+Comment[da]=Du har vundet dette spil backgammon
+Comment[de]=Sie haben die Backgammon-Partie gewonnen!
+Comment[el]=Κερδίσατε αυτή την παρτίδα backgammon
+Comment[eo]=Vi gajnis la nunan bakgamonludon
+Comment[es]=Usted ha ganado la partida actual de backgammon
+Comment[et]=Sa võitsid selle mängu
+Comment[eu]=Uneko Backgammon jokoa irabazi duzu
+Comment[fa]=شما بازی جاری تخته نرد را بردید
+Comment[fi]=Olet voittanut backgammon pelin
+Comment[fr]=Vous avez gagné cette partie de backgammon
+Comment[gl]=Vostede gañou esta partida de backgammon
+Comment[he]=ניצחת במשחק השש־בש הנוכחי
+Comment[hi]=आप बैकगेमॉन का हालिया खेल जीत गए
+Comment[hr]=Pobijedili ste u ovoj partiji backgammona
+Comment[hu]=Ön megnyerte ezt a backgammon játékot
+Comment[is]=Þú vannst þennan Backgammon leik
+Comment[it]=Hai vinto questa partita di backgammon
+Comment[ja]=現在のbackgammonゲームに勝ちました
+Comment[km]=អ្នក​បានឈ្មះ​ល្បែង​បច្ចុប្បន្ន​នៃ backgammon
+Comment[lt]=Jūs laimėjote šį backgammon žaidimą
+Comment[lv]=Jūs uzvarējāt tekošajā bekgemona spēlē
+Comment[mk]=Ја добивте тековната игра на табла
+Comment[mt]=Int irbaħt il-logħba preżenti tal-backgammon
+Comment[nb]=Du vant det gjeldende backgammon-spillet
+Comment[nds]=Du hest den Backgammon-Törn wunnen
+Comment[ne]=ब्याकगामोनको हालको खेल तपाईँले जित्नु भयो
+Comment[nl]=U hebt het huidige Backgammon-spel gewonnen.
+Comment[nn]=Du har vunne denne backgammon-runden
+Comment[nso]=O fentse papadi ya bjale ya backgammon
+Comment[pa]=ਤੁਸੀਂ ਮੌਜੂਦਾ ਬੈਕਗਮੋਮ ਦੀ ਮੌਜੂਦਾ ਖੇਡ ਜਿੱਤ ਗਏ
+Comment[pl]=Wygrałeś bieżącą grę backgammon
+Comment[pt]=Ganhou o jogo de gamão
+Comment[pt_BR]=Você ganhou o jogo atual de gamão
+Comment[ro]=Aţi cîştigat jocul de table curent
+Comment[ru]=Вы выиграли партию в нарды
+Comment[se]=Don leat vuoitán dán backgammon-vuoru
+Comment[sk]=Vyhrali ste aktuálnu hru v backgammone
+Comment[sl]=Zmagali ste trenutno igro backgammona
+Comment[sr]=Победили сте у овој игри бекгемона
+Comment[sr@Latn]=Pobedili ste u ovoj igri bekgemona
+Comment[sv]=Du har vunnit det aktuella spelet av backgammon
+Comment[ta]=பேக்காம்மோனானின் தற்போதைய விளையாட்டை நீங்கள் வென்றுவிட்டீர்கள்
+Comment[tg]=Шумо дар нардбозии ҷорӣ ғолиб омадед
+Comment[tr]=Şu anki tavla oyununu kazandınız
+Comment[uk]=Ви виграли поточну гру backgammon
+Comment[ven]=No khunda kha mutambo wa backgammon
+Comment[vi]=Bạn thắng trong trò chơi backgammon này
+Comment[wa]=Vos avoz wangnî l' djeu d' backgammon
+Comment[xh]=Uphumelele emdlalweni wangoku we backgammon
+Comment[zh_CN]=您赢了这盘双陆棋游戏
+Comment[zh_TW]=您贏了這一盤西洋雙陸棋
+Comment[zu]=Uphumelele emdlalweni wamanje we-backgammon
+default_sound=kbackgammon-won.wav
+default_presentation=1
+
+[game over l]
+Name=Gamo over, you lost
+Name[af]=Speletjie bo, jy verloor
+Name[ar]=اللعبة انتهت، لقد خسرت
+Name[az]=Oyun Qurtardı, siz uduzdunuz
+Name[be]=Канец гульні, вы прайгралі
+Name[bg]=Загубихте
+Name[bn]=খেল খতম, আপনি হেরে গিয়েছেন
+Name[br]=Echu eo an abadenn, kollet out
+Name[bs]=Igra završena, izgubili ste
+Name[ca]=Final del joc, heu perdut
+Name[cs]=Konec hry, prohrál(a) jste
+Name[cy]=Gêm drosodd, colli wnaethoch chi
+Name[da]=Spillet forbi, du tabte
+Name[de]=Spiel beendet, Sie haben verloren
+Name[el]=Τέλος παιχνιδιού, χάσατε
+Name[en_GB]=Game over, you lost
+Name[eo]=Ludo finita, vi malgajnis
+Name[es]=Fin de la partida, usted perdió
+Name[et]=Mäng läbi, sina kaotasid
+Name[eu]=Jokoa amaitu da, galdu duzu
+Name[fa]=بازی تمام شد، شما باختید
+Name[fi]=Peli loppu, hävisit
+Name[fr]=Fin de la partie, vous avez perdu
+Name[gl]=Fin do xogo, vostede perde
+Name[he]=המשחק הסתיים, הפסדת
+Name[hi]=खेल ख़त्म, आप हारे
+Name[hr]=Igra je završena. Izgubili ste.
+Name[hu]=Vége a játéknak, Ön vesztett
+Name[is]=Leik lokið, þú tapaðir
+Name[it]=Partita finita, hai perso
+Name[ja]=ゲームオーバー、あなたの負け
+Name[km]=ល្បែងចប់​, អ្នក​ចាញ់​ហើយ
+Name[lt]=Žaidimas baigtas, jūs pralaimėjote
+Name[lv]=Spēles beigas, jūs zaudējāt
+Name[mk]=Играта заврши, вие изгубивте
+Name[mt]=Logħba spiċċat - int tlift
+Name[nb]=Spillet er slutt, du tapte
+Name[nds]=Speel vörbi, Du hest verloren
+Name[ne]=खेल समाप्त, तपाईँ हार्नु भयो
+Name[nl]=Spel is afgelopen, u hebt verloren.
+Name[nn]=Spelet er slutt, du tapte
+Name[nso]=Papadi e fedile, o paletswe
+Name[pa]=ਖੇਡ ਖਤਮ, ਤੁਹਾਡੀ ਵਾਰੀ ਖਤਮ
+Name[pl]=Koniec gry, przegrałeś
+Name[pt]=Fim do jogo, perdeu
+Name[pt_BR]=Fim do jogo; você perdeu
+Name[ro]=Joc terminat. Ai pierdut.
+Name[ru]=Конец игры, вы проиграли
+Name[se]=Speallu nogai, don vuoittehallet
+Name[sk]=Koniec hry, prehrali ste
+Name[sl]=Konec igre, izgubili ste
+Name[sr]=Крај игре, изгубили сте
+Name[sr@Latn]=Kraj igre, izgubili ste
+Name[sv]=Spelet är slut, du förlorade
+Name[ta]=விளையாட்டு முடிந்தது, நீங்கள் தோற்றுவிட்டீர்கள்
+Name[tg]=Бозӣ ба итмом расид, шумо мағлуб шудед
+Name[th]=จบเกม - คุณแพ้
+Name[tr]=Oyun bitti, sen kaybettin
+Name[uk]=Гру завершено, ви програли
+Name[uz]=Oʻyin tugadi, siz yutqazdingiz
+Name[uz@cyrillic]=Ўйин тугади, сиз ютқаздингиз
+Name[ven]=Mutambo wo fhela, no liwa
+Name[vi]=Trò chơi kết thúc, bạn thua
+Name[wa]=Li djeu est houte, vos avoz pierdou
+Name[xh]=Umdlalo uphelile, wohluliwe
+Name[zh_CN]=游戏结束,您输了
+Name[zh_TW]=遊戲結束,您輸了
+Name[zu]=Umdlalo uphellile, uhluliwe
+Comment=You have lost the current game of backgammon
+Comment[af]=Jy het verloor die huidige speletjie van backgammon
+Comment[ar]=لقد خسرت اللعبة الحالية من لعبة النرد/الطاولة
+Comment[az]=Hazırkı nərdtaxta oyununu uduzdunuz
+Comment[be]=Вы прайгралі партыю ў нарды
+Comment[bg]=Загубихте
+Comment[bn]=এই ব্যাকগ্যামোন খেলাটি আপনি হেরেছেন
+Comment[bs]=Izgubili ste u trenutnoj backgammon igri
+Comment[ca]=Heu perdut aquesta partida de backgamon
+Comment[cs]=Tuto hru ve vrchcáby jste prohráli
+Comment[cy]=Rydych wedi colli y gêm gyfredol o dawlbwrdd
+Comment[da]=Du har tabt dette spil backgammon
+Comment[de]=Sie haben die Backgammon-Partie verloren!
+Comment[el]=Χάσατε αυτή την παρτίδα backgammon
+Comment[eo]=Vi malgajnis la nunan bakgamonludon
+Comment[es]=Usted ha perdido la partida actual de backgammon
+Comment[et]=Sa kaotasid selle mängu
+Comment[eu]=Uneko Backgammon jokoa galdu duzu
+Comment[fa]=شما بازی جاری تخته نرد را باختید
+Comment[fi]=Olet hävinnyt backgammon pelin
+Comment[fr]=Vous avez perdu cette partie de backgammon
+Comment[gl]=Vostede perdeu esta partida de backgammon
+Comment[he]=הפסדת במשחק השש־בש הנוכחי
+Comment[hi]=आप बैकगेमॉन का हालिया खेल हार गए
+Comment[hr]=Izgubili ste u ovoj partiji backgammona
+Comment[hu]=Ön elvesztette ezt a backgammon játékot
+Comment[is]=Þú tapaðir þessum Backgammon leik
+Comment[it]=Hai perso questa partita di backgammon
+Comment[ja]=現在のbackgammonゲームに負けました
+Comment[km]=អ្នក​បានចាញ់​ល្បែង​បច្ចុប្បន្ន​នៃ backgammon
+Comment[lt]=Jūs pralaimėjote šį backgammon žaidimą
+Comment[lv]=Jūs zaudējāt tekošajā bekgemona spēlē
+Comment[mk]=Ја изгубивте тековната игра на табла
+Comment[mt]=Int tlift il-logħba preżenti tal-backgammon
+Comment[nb]=Du tapte det gjeldende backgammon-spillet
+Comment[nds]=Du hest den Backgammon-Törn verloren
+Comment[ne]=ब्याकगामोनको हालको खेल तपाईँले हार्नु भयो
+Comment[nl]=U hebt het huidige Backgammon-spel verloren.
+Comment[nn]=Du har tapt denne backgammon-runden
+Comment[nso]=O paletswe ke papadi ya bjale ya backgammon
+Comment[pl]=Przegrałeś bieżącą grę backgammon
+Comment[pt]=Perdeu o jogo de gamão
+Comment[pt_BR]=Infelizmente você perdeu o jogo atual de gamão
+Comment[ro]=Aţi pierdut jocul de table curent
+Comment[ru]=Вы проиграли партию в нарды
+Comment[se]=Don leat vuoittehallan dán backgammon-vuoru
+Comment[sk]=Prehrali ste aktuálnu hru v backgammone
+Comment[sl]=Izgubili ste trenutno igro backgammona
+Comment[sr]=Изгубили сте у овој игри бекгемона
+Comment[sr@Latn]=Izgubili ste u ovoj igri bekgemona
+Comment[sv]=Du har förlorat det aktuella spelet av backgammon
+Comment[ta]=பேக்காம்மோனானின் தற்போதைய விளையாட்டில் நீங்கள் தோல்வி அடைந்துள்ளீர்கள்.
+Comment[tg]=Шумо дар нардбозии ҷорӣ мағлуб шудед
+Comment[tr]=Şu anki tavla oyununu kaybettiniz
+Comment[uk]=Ви програли поточну гру backgammon
+Comment[ven]=No kunda kha mutambo wa backgammon
+Comment[vi]=Bạn thua trong trò chơi backgammon này
+Comment[wa]=Vos avoz pierdou l' djeu d' backgammon
+Comment[xh]=Wahlulekile emdlalweni wangoku we backgammon
+Comment[zh_CN]=您输了这盘双陆棋游戏
+Comment[zh_TW]=您輸了這一盤西洋雙陸棋
+Comment[zu]=Uhluliwe emdlallweni wamanje we-backgammon
+default_sound=kbackgammon-lost.wav
+default_presentation=1
+
+[roll or double]
+Name=Roll or double
+Name[af]=Rol of dubbel
+Name[az]=At və ya Cütlə
+Name[be]=Кінуць косці або падвоіць
+Name[bg]=Хвърляне или удвояване
+Name[bn]=গড়ান অথবা দ্বিগুন করুন
+Name[bs]=Ponovo ili duplo
+Name[ca]=Tirar o doblar
+Name[cs]=Hoďte nebo double
+Name[cy]=Taflu neu dwbl
+Name[da]=Kast eller fordobl
+Name[de]=Würfeln oder verdoppeln
+Name[el]=Ρίξτε το ζάρι ή διπλασιάστε
+Name[eo]=Rulu aŭ duobligu
+Name[es]=Tirar o doblar
+Name[et]=Veereta või duubelda
+Name[eu]=Jaurti edo bikoiztu
+Name[fa]=غلتاندن یا دو برابر کردن
+Name[fi]=Heitä tai tuplaa
+Name[fr]=Jeter les dés ou doubler
+Name[gl]=Botar ou dobrar
+Name[he]=הטלה או הכפלה
+Name[hi]=पाँसा फेंकें या दोगुना करें
+Name[hr]=Bacaj ili dvostruko
+Name[hu]=Dobás vagy duplázás
+Name[is]=Kastaðu eða tvöfaldaðu
+Name[it]=Lancia o raddoppia
+Name[ja]=振るかダブル
+Name[km]=ក្រឡុក ឬ ទ្វេ
+Name[lt]=Ridenti ar dvigubinti
+Name[lv]=Mest vai dubultot
+Name[mk]=Фрлете или удвојте
+Name[mt]=Waddab damem jew Irdoppja
+Name[nb]=Kast eller doble
+Name[nds]=Wörpeln oder verdubbeln
+Name[ne]=घुमाउनुहोस् वा दोब्बर पार्नुहोस्
+Name[nl]=Werpen of verdubbelen
+Name[nn]=Rull eller dobla
+Name[nso]=Tokolosa goba gabedi
+Name[pa]=ਰੋਲ ਜਾਂ ਦੁਹਰਾ
+Name[pl]=Rzucaj lub podwój
+Name[pt]=Lançar ou duplicar
+Name[pt_BR]=Jogar ou dobrar
+Name[ro]=Aruncă sau dublează
+Name[ru]=Бросить кости или удвоить
+Name[se]=Časkke dahje duppalastte
+Name[sk]=Hodiť alebo zdvojiť
+Name[sl]=Meči ali podvoji
+Name[sr]=Баците коцкице или удвостручите
+Name[sr@Latn]=Bacite kockice ili udvostručite
+Name[sv]=Slå eller dubblera
+Name[ta]=சுற்று அல்லது இரட்டிப்பாக்கு
+Name[tg]=Партофтани мӯҳр ё дубора партофтан
+Name[th]=ทอดหรือได้ทอดใหม่
+Name[tr]=At ya da çift
+Name[uk]=Кинути кості або подвоїти
+Name[ven]=Kungulusani kha ni kou pada
+Name[vi]=Cuộn hay gấp
+Name[xh]=Jikeleza okanye phindaphinda
+Name[zh_CN]=掷骰子或加倍
+Name[zh_TW]=擲骰子或加倍
+Name[zu]=Gingqa noma phinda kabili
+Comment=It's your turn to roll the dice or double the cube
+Comment[af]=Dit is jou skakel na rol die dobbelsteen of dubbel die kubus
+Comment[az]=Zərləri atmaqda ya da kubları cütləməkdə sizin sıranız gəldi
+Comment[be]=Ваша чарга кідаць косці або падвойваць
+Comment[bg]=Хвърляне или удвояване
+Comment[bn]=এখন আপনার পাশা গড়ানোর বা কিউব দ্বিগুন করার পালা
+Comment[bs]=Na vas je red da bacite kocke ili poduplate cube
+Comment[ca]=És el vostre torn per a tirar els daus o doblar el cub
+Comment[cs]=Jste na tahu, buď hoďte kostkou nebo double
+Comment[cy]=Eich tro chi yw hi i daflu'r dîs neu ddwblu'r ciwb
+Comment[da]=Det er din tur til at kaste terningerne eller fordoble kuben
+Comment[de]=Sie sind dran, entweder zu würfeln oder zu verdoppeln
+Comment[el]=Είναι η σειρά σας να ρίξετε τα ζάρια
+Comment[eo]=Estas via vico ĵeti la du kubojn aŭ duobligi la kubon
+Comment[es]=Es su turno para tirar los dados o doblar el cubo
+Comment[et]=Sinu kord veeretada täringut või kahekordistada panust
+Comment[eu]=Kuboa jaurti edo bikoizteko zure txanda da
+Comment[fa]=نوبت شماست که طاس را بغلتانید، یا مکعب را دو برابر کنید
+Comment[fi]=On sinun vuoro heittää noppaa tai tuplata
+Comment[fr]=C'est à votre tour de jeter les dés ou de doubler le videau.
+Comment[gl]=É a súa quenda de botar o dado ou dobrar o cubo
+Comment[he]=תורך להטיל את הקוביות או להכפיל את הקובייה
+Comment[hi]=पाँसा फेंकने की या क्यूब को दोगुना करने की यह आपकी बारी है
+Comment[hr]=Vi ste na redu da bacite kocku ili je udvostručite
+Comment[hu]=Dobjon a kockával vagy duplázzon
+Comment[is]=Þú átt leik. Kastaðu teningnum eða tvöfaldaðu
+Comment[it]=È il tuo turno di lanciare i dadi o di raddoppiare il cubo
+Comment[ja]=あなたがさいころを振るかダブルする番です
+Comment[km]=វា​គឺ​ជា​វេន​របស់​អ្នក ដើម្បី​ក្រឡុក​គ្រាប់​ឡុកឡាក់ ឬ ដើម្បី​ទ្វេ​គូប
+Comment[lt]=Jūsų eilė ridenti kauliuką ar dvigubinti kubą
+Comment[lv]=Šis ir jūsu gājiens, lai mestu kauliņu vai dubultotu
+Comment[mk]=Вие сте на ред да ги фрлите коцките или да ја удвоите коцката
+Comment[mt]=Imiss lilek twaddab id-damem jew tirdoppja l-kubu
+Comment[nb]=Det er din tur til å kaste terningene eller doble kuben
+Comment[nds]=Du büst mit Wörpeln oder Verdubbeln an de Reeg
+Comment[ne]=पासा घुमाउने वा घन दोब्बर पार्ने तपाईँको पालो हो
+Comment[nl]=U bent aan de beurt om de dobbelsteen te werpen of te verdubbelen.
+Comment[nn]=Det er din tur til å rulla terningen eller dobla kuben
+Comment[nso]=Ke nako ya gago yago tokolosa mataese goba wa double cube
+Comment[pl]=Twój ruch, by rzucić kostką lub podwoić sześcian
+Comment[pt]=É a sua vez de lançar os dados ou duplicar o cubo
+Comment[pt_BR]=É a sua vez de jogar os dados ou dobrar o cubo
+Comment[ro]=Este rîndul dumneavoastră să aruncaţi zarurile sau să dublaţi cubul
+Comment[ru]=Ваша очередь бросать кости или удваивать
+Comment[se]=Lea du vuorru birccu časkit dahje duppalastit kuba
+Comment[sk]=Ste na ťahu, buď hoďte kockou alebo zdvojte
+Comment[sl]=Na vrsti ste za met kock ali podvojitev vrednosti
+Comment[sr]=Ваш је ред да баците коцкице или да дуплирате коцку
+Comment[sr@Latn]=Vaš je red da bacite kockice ili da duplirate kocku
+Comment[sv]=Det är din tur att slå tärningen eller dubblera kuben
+Comment[ta]=நீங்கள் தாயத்தை உருட்டும் அல்லது பட்டகத்தை இரட்டிக்கும் முறை
+Comment[tg]=Навбати шумо барои партофтани мӯҳр ё партофтани дубора
+Comment[tr]=Zar atma sırası sizde
+Comment[uk]=Ваша черга кидати кості
+Comment[ven]=Ndi tshifhinga tshavho tsha u kungulusa dice kana pada cube
+Comment[vi]=Đến lượt bạn gieo xúc sắc hay double cube
+Comment[xh]=Lithuba lakho lokujikelezisa idayisi okanye phinda kabini ityhubhu
+Comment[zh_CN]=该您掷骰子或加倍
+Comment[zh_TW]=該您擲骰子或加倍
+Comment[zu]=Ithuba lakho lokugingqa idayisi noma uphinde kabili iqhuzu
+default_sound=kbackgammon-roll.wav
+default_presentation=1
+
+[roll]
+Name=Roll the dice
+Name[af]=Rol die dobbelsteen
+Name[ar]=ارمي النرد
+Name[az]=Zərləri at
+Name[be]=Кінуць косці
+Name[bg]=Хвърляне на заровете
+Name[bn]=পাশা গড়ান
+Name[bs]=Baci kocke
+Name[ca]=Tirar els daus
+Name[cs]=Hodit kostkou
+Name[cy]=Taflwch y dîs
+Name[da]=Kast terningerne
+Name[de]=Würfeln
+Name[el]=Ρίξτε το ζάρι
+Name[eo]=Rulu aŭ ĵetu kubojn
+Name[es]=Tirar los dados
+Name[et]=Veereta täringut
+Name[eu]=Jaurti dadoa
+Name[fa]=غلتاندن طاس
+Name[fi]=Heitä noppaa
+Name[fr]=Lancer les dés
+Name[gl]=Botar o dado
+Name[he]=הטלת הקוביות
+Name[hi]=पाँसा फेंकें
+Name[hr]=Baci kocke
+Name[hu]=Dobás a kockával
+Name[is]=Kastaðu teningnum
+Name[it]=Lancia i dadi
+Name[ja]=サイコロを振ってください
+Name[km]=ក្រឡុក​គ្រាប់​ឡុកឡាក់
+Name[lt]=Ridenti kauliuką
+Name[lv]=Mest kauliņu
+Name[mk]=Фрлете ги коцките
+Name[mt]=Waddab id-damem
+Name[nb]=Kast terningene
+Name[nds]=Wörpeln
+Name[ne]=पासा घुमाउनुहोस्
+Name[nl]=Dobbelsteen werpen
+Name[nn]=Rull terningen
+Name[nso]=Tokolosa mataese
+Name[pa]=ਗੋਟੀ ਘੁੰਮਾਓ
+Name[pl]=Rzuć kostką
+Name[pt]=Lançar os dados
+Name[pt_BR]=Jogar os dados
+Name[ro]=Aruncă zarurile
+Name[ru]=Бросить кости
+Name[se]=Časkke birccu
+Name[sk]=Hodiť kocku
+Name[sl]=Meci kocke
+Name[sr]=Баците коцкице
+Name[sr@Latn]=Bacite kockice
+Name[sv]=Kasta tärningen
+Name[ta]=தாயத்தை உருட்டுக
+Name[tg]=Партофтани мӯҳр
+Name[th]=ทอดเต๋า
+Name[tr]=Zarı at
+Name[uk]=Кинути кості
+Name[uz]=Toshni otish
+Name[uz@cyrillic]=Тошни отиш
+Name[ven]=Kungulusani dice
+Name[vi]=Cuộn hay xắc
+Name[wa]=Taper les dés
+Name[xh]= Jikelezisa idayisi
+Name[zh_CN]=掷骰子
+Name[zh_TW]=擲骰子
+Name[zu]=Gingqa idayisi
+Comment=It's your turn to roll the dice
+Comment[af]=Dit is jou skakel na rol die dobbelsteen
+Comment[ar]=لقد حان دورك لرمي النرد
+Comment[az]=Zərləri atmaqda sizin sıranız gəldi
+Comment[be]=Ваша чарга кідаць косці
+Comment[bg]=Хвърляне на заровете
+Comment[bn]=এখন আপনার পাশা গড়ানোর পালা
+Comment[bs]=Na vas je red da bacate kocke
+Comment[ca]=És el vostre torn per a tirar els daus
+Comment[cs]=Jste na tahu, hoďte kostkou
+Comment[cy]=Eich tro chi yw hi i daflu'r dîs
+Comment[da]=Det er din tur til at kaste terningerne
+Comment[de]=Sie sind dran mit Würfeln!
+Comment[el]=Είναι η σειρά σας να ρίξετε τα ζάρια
+Comment[eo]=Estas via vico ĵeti la kubojn
+Comment[es]=Es su turno para tirar los dados
+Comment[et]=Sinu kord täringut veeretada
+Comment[eu]=Dadoa jaurtizeko zure txanda da
+Comment[fa]=نوبت شماست که طاس را بغلتانید
+Comment[fi]=Sinun vuoro heittää noppaa
+Comment[fr]=C'est à votre tour de jeter les dés
+Comment[gl]=É a súa quenda de botar o dado
+Comment[he]=תורך להטיל את הקוביות
+Comment[hi]=पाँसा फेंकने की यह आपकी बारी है
+Comment[hr]=Vi ste na redu da bacite kocku
+Comment[hu]=Dobjon a kockával
+Comment[is]=Þú átt að kasta
+Comment[it]=È il tuo turno di lanciare i dadi
+Comment[ja]=あなたがサイコロを振る番です
+Comment[km]=វា​ជា​វេន​របស់​អ្នក​ដើម្បី​ប្រមៀលឡុក​គ្រាប់​ឡុក​ឡាក់
+Comment[lt]=Jūsų eilė ridenti kauliuką
+Comment[lv]=Šis ir jūsu gājiens mest kauliņu
+Comment[mk]=Вие сте на ред да ги фрлите коцките
+Comment[mt]=Imiss lilek twaddab id-damem
+Comment[nb]=Det er din tur til å kaste terningene
+Comment[nds]=Du büst mit Wörpeln an de Reeg
+Comment[ne]=पासा घुमाउने तपाईँको पालो हो
+Comment[nl]=U bent aan de beurt om de dobbelsteen te werpen.
+Comment[nn]=Det er din tur til å rulla terningen
+Comment[nso]=Ke nako ya gago yago tokolosa mataese
+Comment[pa]=ਗੋਟੀ ਘੁੰਮਾਉਣ ਦੀ ਵਾਰੀ ਤੁਹਾਡੀ ਹੈ
+Comment[pl]=Twój ruch do rzutu kostką
+Comment[pt]=É a sua vez de lançar os dados
+Comment[pt_BR]=É a sua vez de jogar os dados
+Comment[ro]=Este rîndul dumneavoastră să aruncaţi zarurile
+Comment[ru]=Ваша очередь бросать кости
+Comment[se]=Lea du vuorru birccu časkit
+Comment[sk]=Ste na ťahu, hoďte kockou
+Comment[sl]=Na vrsti ste za met kock
+Comment[sr]=Ваш је ред да баците коцкице
+Comment[sr@Latn]=Vaš je red da bacite kockice
+Comment[sv]=Det är din tur att kasta tärningen
+Comment[ta]=நீங்கள் இப்போது தாயத்தை உருட்டும் முறை
+Comment[tg]=Навбати шумо барои партофтани мӯҳрҳо
+Comment[tr]=Zar atma sırası sizde
+Comment[uk]=Ваша черга кидати кості
+Comment[ven]=Ndi tshifhinga tshanu tsha u tamba daisi
+Comment[vi]=Đến lượt bạn reo xúc sắc
+Comment[wa]=C' est a vos d' taper les dés asteure
+Comment[zh_CN]=该您掷骰子了
+Comment[zh_TW]=該您擲骰子了
+Comment[zu]=Ithuba lakho lokugingqa idayisi
+default_sound=kbackgammon-roll.wav
+default_presentation=1
+
+[move]
+Name=Move checkers
+Name[af]=Beweeg skuifstukke
+Name[az]=Daşları Hərəkət etdir
+Name[be]=Перасунуць фішку
+Name[bg]=Преместване на пулове
+Name[bn]=চেকার্স চালুন
+Name[ca]=Moure fitxes
+Name[cs]=Přesunout kameny
+Name[cy]=Symud drafftiau
+Name[da]=Flyt brikkerne
+Name[de]=Steine ziehen
+Name[el]=Μετακινήστε πούλια
+Name[eo]=Movu pecojn
+Name[es]=Mover fichas
+Name[et]=Liiguta nuppe
+Name[eu]=Mugitu fitxak
+Name[fa]=حرکت بازبینها
+Name[fi]=Siirrä tammea
+Name[fr]=Déplacer des pions
+Name[gl]=Mover fichas
+Name[he]=הזזת חלקים
+Name[hi]=चेकर्स खिसकाएँ
+Name[hr]=Pomakni figure
+Name[hu]=Lépés
+Name[is]=Færðu
+Name[it]=Sposta le pedine
+Name[ja]=チェッカーの移動
+Name[km]=ផ្លាស់​ទី​អ្នក​ពិនិត្យ
+Name[lt]=Eiti šaškėmis
+Name[lv]=Pārvietot kauliņus
+Name[mk]=Преместете ги пуловите
+Name[mt]=Mexxi checkers
+Name[nb]=Flytt brikker
+Name[nds]=Steen trecken
+Name[ne]=चाल परिक्षक
+Name[nl]=Stukken verplaatsen
+Name[nn]=Flytt brikker
+Name[nso]=Sutisa checkers
+Name[pl]=Rusz pionkiem
+Name[pt]=Mover as peças
+Name[pt_BR]=Mover peças
+Name[ro]=Mutaţi piesele
+Name[ru]=Передвинуть фишку
+Name[se]=Sirdde bihtáid
+Name[sk]=Presunúť kamene
+Name[sl]=Premakni figure
+Name[sr]=Померите чекере
+Name[sr@Latn]=Pomerite čekere
+Name[sv]=Flytta brickor
+Name[ta]=கட்டம் கட்டமாக நகற்றுக
+Name[tg]=Ҷойивазкунии домнаҳо
+Name[th]=ย้ายตัวหมาก
+Name[tr]=Pulu oynat
+Name[uk]=Пересунути шашки
+Name[ven]=Tshimbidzani checkers
+Name[vi]=Di chuyển checkers
+Name[xh]= Hambisa icheckers
+Name[zh_CN]=移动棋子
+Name[zh_TW]=移動棋子
+Name[zu]=Nyakazisa i-checkers
+Comment=The dice have been rolled and it's your turn to move checkers
+Comment[af]=Die dobbelsteen het al gerol en dit is jou skakel na beweeg skuifstukke
+Comment[az]=Zəri atdınız və daşları oynatmaq vaxtı gəldi
+Comment[be]=Вы ўжо кінулі косці, і зараз ваш ход
+Comment[bg]=Преместване на пулове
+Comment[bn]=পাশা গড়ানো হয়েছে এবং এখন আপনার চেকার্স চালার পালা
+Comment[ca]=S'han llançat els daus i és el vostre torn per a moure les fitxes
+Comment[cs]=Kostka byla hozená a teď máte přesunout kameny
+Comment[cy]=Mae'r dîs wedi eu taflu ac eich tro chi yw hi i symud y drafftiau
+Comment[da]=Terningerne er kastet og det er din tur til at flytte brikkerne
+Comment[de]=Die Würfel sind gefallen, Sie müssen ziehen.
+Comment[el]=Τα ζάρια ρίχτηκαν και είναι η σειρά σας να μετακινήσετε τα πούλια
+Comment[eo]=La kuboj estas ĵetitaj kaj estas via vico movi la pecojn
+Comment[es]=Se han lanzado los dados y es su turno para mover fichas
+Comment[et]=Täring sai visatud, liiguta nüüd nuppe
+Comment[eu]=Dadoak jaurti dira eta fitxak mugitzeko zure txanda da
+Comment[fa]=طاس غلتانیده شد، و نوبت شماست که بازبینها را حرکت بدهید
+Comment[fi]=Noppaa on heitetty ja on sinun vuoro siirtää tammea
+Comment[fr]=Les dés ont été jetés et c'est à votre tour de déplacer des pions
+Comment[gl]=O dado xa rodou e é a súa quenda de mover as fichas
+Comment[he]=הקוביות הוטלו וכעת תורך להזיז את אבני המשחק
+Comment[hi]=पाँसा फेंक दिया गया है और यह चेकर्स को चलने की आपकी बारी है
+Comment[hr]=Kocke su bačene i vaš je red da pomaknete figure
+Comment[hu]=A dobás megtörtént, most Ön lép
+Comment[is]=Það er búið að kasta og þú átt að færa
+Comment[it]=I dadi sono stati lanciati e tocca a te muovere le pedine
+Comment[ja]=サイは投げられました、チェッカーを動かす番です
+Comment[km]=គ្រាប់​ឡុក​ឡាក់​ត្រូវ​បាន​ប្រមៀល ហើយ​វា​ជា​វេន​របស់​អ្នក​ដើម្បី​ផ្លាស់​ទីអ្នក​ពិនិត្យ
+Comment[lt]=Kauliukas nuridentas ir dabar jūsų eilė stumti šaškes
+Comment[lv]=Kauliņi ir mesti un ir jūsu kārta izdarīt gājienu
+Comment[mk]=Коцките се фрлени и вие сте на ред да ги преместите пуловите
+Comment[mt]=Id-damem twaddbu u jmiss lilek tmexxi ċ-checkers
+Comment[nb]=Terningene er kastet og det er din tur til å flytte
+Comment[nds]=Du hest wörpelt un muttst nu trecken
+Comment[ne]=पासा घुमाइएको छ र परीक्षक सार्ने अब तपाईँको पालो छ ।
+Comment[nl]=De dobbelsteen is geworpen, en u bent aan de beurt om de stukken te verplaatsen.
+Comment[nn]=Terningen er rulla, og det er din tur til å flytta brikker
+Comment[nso]=Mataese a tokolositswe gomme ke nako ya gago yago sutisa checkers
+Comment[pl]=Kość została rzucona, Twój ruch do ruchu pionkiem
+Comment[pt]=Os dados foram lançados e é a sua vez de mexer as peças
+Comment[pt_BR]=Os dados foram jogados e é a sua vez de mover as peças
+Comment[ro]=Zarurile au fost aruncate şi e rîndul dumneavoastră să mutaţi piesele
+Comment[ru]=Вы уже бросили кости, и теперь ваша очередь ходить
+Comment[se]=Bircu lea časkon, ja dál lea du vuorru bihtáid sirdit
+Comment[sk]=Kocka bola hodená a teraz máte presunúť kamene
+Comment[sl]=Kocke so vržene in zdaj ste na vrsti za premik figur
+Comment[sr]=Коцкица је бачена и ваш је ред да померите чекере
+Comment[sr@Latn]=Kockica je bačena i vaš je red da pomerite čekere
+Comment[sv]=Tärningen har slagits och det är din tur att flytta brickor
+Comment[ta]=தாயம் உருட்டப்பட்டது.இப்போது நீங்கள் கட்டத்தை நகர்த்தலாம்.
+Comment[tg]=Мӯҳрҳо партофта шудаанд ва ҳоло навбати шумо барои ҷойивазкунии дамнаҳо
+Comment[tr]=Zar atıldı ve şimdi pulları oynatma sırası sizde
+Comment[uk]=Кості кинуто; ваша черга пересунути шашки
+Comment[ven]=Dice lo kunguluswa zwino ndi tshifhinga tshavho tsha u tshimbidza checkers
+Comment[vi]=xúc sắc đã được gieo và đến lượt bạn di chuyển checkers
+Comment[xh]=Amadayisi aqengqiwe lithuba lakho lukuhambisa iicheckersi
+Comment[zh_CN]=已经掷了骰子,该您移动了
+Comment[zh_TW]=已經擲了骰子,該您移動了
+Comment[zu]=Idayisi ligingqiwe, ithuba lakho lokunyakazisa i-checkers
+default_sound=kbackgammon-roll.wav
+default_presentation=1
+
+[invitation]
+Name=Game invitation
+Name[af]=Speletjie uitnodiging
+Name[ar]=دعوة إلى لعبة
+Name[az]=Oyuna Dəvət
+Name[be]=Запрашэнне да гульні
+Name[bg]=Покана за игра
+Name[bn]=খেলার আমন্ত্রণ
+Name[bs]=Poziv u igru
+Name[ca]=Invitació a una partida
+Name[cs]=Výzva ke hře
+Name[cy]=Gwahoddiad gêm
+Name[da]=Spilinvitation
+Name[de]=Einladung zum Spiel
+Name[el]=Πρόσκληση για παιχνίδι
+Name[eo]=Ludinvito
+Name[es]=Invitación al juego
+Name[et]=Mängu tervitus
+Name[eu]=Jokora gonbidapena
+Name[fa]=دعوت به بازی
+Name[fi]=Pelikutsu
+Name[fr]=Invitation à une partie
+Name[gl]=Invitación ao xogo
+Name[he]=הזמנה למשחק
+Name[hi]=खेल निमंत्रण
+Name[hr]=Poziv za igru
+Name[hu]=Játék kezdeményezése (meghívás)
+Name[is]=Býð þér í nýjan leik
+Name[it]=Invito a giocare
+Name[ja]=ゲームに招待
+Name[km]=សំបុត្រ​អញ្ជើញ​ល្បែង
+Name[lt]=Kvietimas į žaidimą
+Name[lv]=Ielūgums uz spēli
+Name[mk]=Покана за игра
+Name[mt]=Stedina għal logħba
+Name[nb]=Spillinvitasjon
+Name[nds]=Speelinladen
+Name[ne]=खेल निमन्त्रणा
+Name[nl]=Speluitnodiging
+Name[nn]=Spelinvitasjon
+Name[nso]=Memo ya papadi
+Name[pa]=ਖੇਡ ਸੱਦਾ
+Name[pl]=Zaproszenie do gry
+Name[pt]=Convite para jogo
+Name[pt_BR]=Convite para jogar
+Name[ro]=Invitaţie joc
+Name[ru]=Приглашение в игру
+Name[se]=Speallanbovdehus
+Name[sk]=Výzva na hru
+Name[sl]=Povabilo k igri
+Name[sr]=Позив у игру
+Name[sr@Latn]=Poziv u igru
+Name[sv]=Spelinbjudan
+Name[ta]= விளையாட்டு அழைப்பிதழ்
+Name[tg]=Ташрифот ба бозӣ
+Name[th]=เชิญให้เล่นเกมด้วย
+Name[tr]=Oyuna davet
+Name[uk]=Запрошення до гри
+Name[uz]=Oʻyinga taklif qilish
+Name[uz@cyrillic]=Ўйинга таклиф қилиш
+Name[ven]=U rambiwa ha mutambo
+Name[vi]=Mời chơi game
+Name[xh]=Isimemo somdlalo
+Name[zh_CN]=游戏邀请
+Name[zh_TW]=邀請別人加入遊戲
+Name[zu]=Isimemo somdlalo
+Comment=Somebody has invited you to a match
+Comment[af]=Iemand het uitgenooi jy na 'n ooreenstem
+Comment[ar]=لقد دعاك أحد لمباراة
+Comment[az]=Biri sizi oyuna dəvət etdi
+Comment[be]=Вас запрашаюць паўдзельнічаць у гульні
+Comment[bg]=Покана за игра
+Comment[bn]=কেউ একজন আপনাকে একটি ম্যাচে আমন্ত্রণ জানিয়েছে
+Comment[bs]=Neko vas je pozvao u meč
+Comment[ca]=Algú us ha invitat a una partida
+Comment[cs]=Někdo vás vyzval na zápas
+Comment[cy]=Mae rhywun wedi eich gwahodd i gêm
+Comment[da]=Der er nogen som har inviteret dig til et spil
+Comment[de]=Jemand hat Sie zu einer Partie eingeladen.
+Comment[el]=Κάποιος σας κάλεσε για ένα παιχνίδι
+Comment[eo]=Iu invitis vin al ludo
+Comment[es]=Alguien le ha invitado a una partida
+Comment[et]=Keegi kutsus sind duellile
+Comment[eu]=Norbaitek joko batera gonbidatu zaitu
+Comment[fa]=شخصی شما را به یک مسابقه دعوت کرده است
+Comment[fi]=Joku on kutsunut sinut otteluun
+Comment[fr]=Quelqu'un vient de vous proposer une partie
+Comment[gl]=Alguén convidouno a botar unha partida
+Comment[he]=מישהו הזמין אותך למשחק
+Comment[hi]=किसी ने आपको प्रतियोगिता के लिए न्यौता दिया है
+Comment[hr]=Netko vas je pozvao na igru
+Comment[hu]=Valaki kihívta Önt egy játszmára
+Comment[is]=Það bauð þér einhver í leik
+Comment[it]=Qualcuno ti ha invitato ad una partita
+Comment[ja]=誰かがあなたをマッチに招待しました
+Comment[km]=មាន​មនុស្ស​អញ្ជើញ​អ្នក​ទៅ​កាន់​ការ​ប្រកួត
+Comment[lt]=Kažkas pakvietė jus mačui
+Comment[lv]=Kāds jūs ir uzaicinājis uz spēli
+Comment[mk]=Некој ве покани на натпревар
+Comment[mt]=Xi ħadd stiednek għal logħba
+Comment[nb]=Noen har invitert deg til et spill
+Comment[nds]=Een hett Di to en Törn inlaadt
+Comment[ne]=कसैले तपाईँलाई खेलका लागि निमन्त्रणा गरेकोछ ।
+Comment[nl]=Iemand heeft u voor een spel uitgenodigd.
+Comment[nn]=Nokon har invitert deg til ein runde
+Comment[nso]=Motho o mongwe ogo laleditse papading
+Comment[pa]=ਤੁਹਾਨੂੰ ਕਿਸੇ ਨੇ ਮੈਚ ਖੇਡਣ ਲਈ ਸੱਦਿਆ ਹੈ
+Comment[pl]=Ktoś zaproponował pojedynek
+Comment[pt]=Alguém o convidou para um jogo
+Comment[pt_BR]=Alguém o convidou para uma partida
+Comment[ro]=Cineva va invitat la o partidă
+Comment[ru]=Вас приглашают начать игру
+Comment[se]=Giinu lea bovden du speallat
+Comment[sk]=Niekto vás vyzval na zápas
+Comment[sl]=Nekdo vas je povabil k igri
+Comment[sr]=Неко вас је позвао у игру
+Comment[sr@Latn]=Neko vas je pozvao u igru
+Comment[sv]=Någon har bjudit in dig till en match
+Comment[ta]=எவரோ ஒருவர் உங்களை ஆட்டத்திற்கு அழைக்கிறார்
+Comment[tg]=Касе шуморо ба мусобиқа даъват мекунад
+Comment[tr]=Birileri sizi maça davet etti
+Comment[uk]=Хтось запрошує вас на матч
+Comment[uz]=Kimdir sizni oʻyinga taklif qildi
+Comment[uz@cyrillic]=Кимдир сизни ўйинга таклиф қилди
+Comment[ven]=Munwe muthu o ni ramba uri ni tambe naye
+Comment[vi]=Có người mời bạn chơi
+Comment[xh]=Ukhona umntu okumemele emdlalweni
+Comment[zh_CN]=有人邀请您进行游戏
+Comment[zh_TW]=有人邀請您進行遊戲
+Comment[zu]=Kukhona okumemele emdlalweni
+default_sound=kbackgammon-move.wav
+default_presentation=1
+
diff --git a/kbackgammon/icons/Makefile.am b/kbackgammon/icons/Makefile.am
new file mode 100644
index 00000000..1980b970
--- /dev/null
+++ b/kbackgammon/icons/Makefile.am
@@ -0,0 +1,3 @@
+
+KDE_ICON = kbackgammon kbackgammon_engine
+
diff --git a/kbackgammon/icons/hi128-app-kbackgammon.png b/kbackgammon/icons/hi128-app-kbackgammon.png
new file mode 100644
index 00000000..794c94e0
--- /dev/null
+++ b/kbackgammon/icons/hi128-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi16-app-kbackgammon.png b/kbackgammon/icons/hi16-app-kbackgammon.png
new file mode 100644
index 00000000..39640210
--- /dev/null
+++ b/kbackgammon/icons/hi16-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi16-app-kbackgammon_engine.png b/kbackgammon/icons/hi16-app-kbackgammon_engine.png
new file mode 100644
index 00000000..bd2ba6a1
--- /dev/null
+++ b/kbackgammon/icons/hi16-app-kbackgammon_engine.png
Binary files differ
diff --git a/kbackgammon/icons/hi22-app-kbackgammon.png b/kbackgammon/icons/hi22-app-kbackgammon.png
new file mode 100644
index 00000000..882d0f9b
--- /dev/null
+++ b/kbackgammon/icons/hi22-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi32-app-kbackgammon.png b/kbackgammon/icons/hi32-app-kbackgammon.png
new file mode 100644
index 00000000..8d4b3f1d
--- /dev/null
+++ b/kbackgammon/icons/hi32-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi32-app-kbackgammon_engine.png b/kbackgammon/icons/hi32-app-kbackgammon_engine.png
new file mode 100644
index 00000000..871ef577
--- /dev/null
+++ b/kbackgammon/icons/hi32-app-kbackgammon_engine.png
Binary files differ
diff --git a/kbackgammon/icons/hi48-app-kbackgammon.png b/kbackgammon/icons/hi48-app-kbackgammon.png
new file mode 100644
index 00000000..b6b5f154
--- /dev/null
+++ b/kbackgammon/icons/hi48-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi48-app-kbackgammon_engine.png b/kbackgammon/icons/hi48-app-kbackgammon_engine.png
new file mode 100644
index 00000000..8291e82a
--- /dev/null
+++ b/kbackgammon/icons/hi48-app-kbackgammon_engine.png
Binary files differ
diff --git a/kbackgammon/icons/hi64-app-kbackgammon.png b/kbackgammon/icons/hi64-app-kbackgammon.png
new file mode 100644
index 00000000..f76e4158
--- /dev/null
+++ b/kbackgammon/icons/hi64-app-kbackgammon.png
Binary files differ
diff --git a/kbackgammon/icons/hi64-app-kbackgammon_engine.png b/kbackgammon/icons/hi64-app-kbackgammon_engine.png
new file mode 100644
index 00000000..f29cb603
--- /dev/null
+++ b/kbackgammon/icons/hi64-app-kbackgammon_engine.png
Binary files differ
diff --git a/kbackgammon/kbackgammon.desktop b/kbackgammon/kbackgammon.desktop
new file mode 100644
index 00000000..f81fd922
--- /dev/null
+++ b/kbackgammon/kbackgammon.desktop
@@ -0,0 +1,75 @@
+[Desktop Entry]
+Exec=kbackgammon %i %m -caption "%c"
+Name=KBackgammon
+Name[af]=Kbackgammon
+Name[ar]=لعبة النرد/الطاولة (KBackgammon)
+Name[be]=Нарды
+Name[bn]=কে-ব্যাকগ্যামোন
+Name[cs]=Vrchcáby
+Name[eo]=Bakgamono
+Name[hi]=के-बैकगेमॉन
+Name[is]=Kotra
+Name[ja]=バックギャモン
+Name[ne]=केडीई ब्याकगामोन
+Name[pt_BR]=KGamão
+Name[ro]=Joc de table
+Name[sv]=Kbackgammon
+Name[ta]=கேபேக்கமான்
+Name[tg]=KНардбозӣ
+Name[tr]=Tavla
+Name[zh_TW]=KBackgammon 西洋雙陸棋
+Type=Application
+DocPath=kbackgammon/index.html
+GenericName=Backgammon Game
+GenericName[be]=Гульня ў нарды
+GenericName[bg]=Табла
+GenericName[bn]=ব্যাকগ্যামোন খেলা
+GenericName[bs]=Igra tavle (Backgammon)
+GenericName[ca]=Joc de Backgammon
+GenericName[cs]=Backgammon hra
+GenericName[cy]=Gêm Dawlbwrdd
+GenericName[da]=Backgammon-spil
+GenericName[de]=Backgammon Spiel
+GenericName[el]=Παιχνίδι τάβλι
+GenericName[eo]=Triktrakludo
+GenericName[es]=Juego de Backgammon
+GenericName[et]=Backgammoni mäng
+GenericName[eu]=Backgammon jokoa
+GenericName[fa]=بازی Backgammon
+GenericName[fi]=Backgammon lautapeli
+GenericName[fr]=Jeu de Backgammon
+GenericName[he]=משחק שש־בש
+GenericName[hr]=Backgammon
+GenericName[hu]=Backgammon
+GenericName[is]=Kotruleikur
+GenericName[it]=Gioco del Backgammon
+GenericName[ja]=バックギャモン
+GenericName[km]=ល្បែង Backgammon
+GenericName[lt]=Backgammon žaidimas
+GenericName[lv]=Backgammon spēle
+GenericName[mk]=Игра на табла
+GenericName[nb]=Backgammon-spill
+GenericName[nds]=Backgammon-Speel
+GenericName[ne]=ब्याकगामोन खेल
+GenericName[nl]=Backgammonspel
+GenericName[nn]=Backgammon-spel
+GenericName[pa]=ਬੈਕਗਾਮੋਨ ਖੇਡ
+GenericName[pl]=Backgammon
+GenericName[pt]=Jogo de Gamão
+GenericName[pt_BR]=Jogo de Gamão
+GenericName[ru]=Нарды
+GenericName[se]=Backgammon-speallu
+GenericName[sk]=Backgammon hra
+GenericName[sl]=Igra backgammona
+GenericName[sr]=Игра бекгемона
+GenericName[sr@Latn]=Igra bekgemona
+GenericName[sv]=Backgammonspel
+GenericName[ta]=பாக்காமான் விளையாட்டு
+GenericName[uk]=Гра в нарди
+GenericName[wa]=Djeu d' backgammon
+GenericName[zh_TW]=Backgammon 西洋雙陸棋遊戲
+Terminal=false
+Icon=kbackgammon
+X-KDE-StartupNotify=true
+X-DCOP-ServiceType=Multi
+Categories=Qt;KDE;Game;BoardGame;
diff --git a/kbackgammon/kbackgammonui.rc b/kbackgammon/kbackgammonui.rc
new file mode 100644
index 00000000..24791882
--- /dev/null
+++ b/kbackgammon/kbackgammonui.rc
@@ -0,0 +1,51 @@
+<!DOCTYPE kpartgui>
+<kpartgui name="kbackgammon" version="3">
+
+<MenuBar>
+ <Menu name="move"><text>&amp;Move</text>
+ <Action name="move_load" append="move_undo_merge"/>
+ <Action name="move_cube" append="move_turn_merge"/>
+ <Action name="move_engine"/>
+ </Menu>
+ <Menu name="command_menu"><text>&amp;Command</text>
+ </Menu>
+ <Menu name="settings"><text>&amp;Settings</text>
+ <Action name="options_textbar" append="show_merge"/>
+ </Menu>
+ <Menu name="help"><text>&amp;Help</text>
+ <Action name="help_www"/>
+ </Menu>
+</MenuBar>
+
+<ToolBar name="mainToolBar" noMerge="1" fullWidth="true"><text>Main Toolbar</text>
+ <Action name="game_new"/>
+ <Separator/>
+ <Action name="move_undo"/>
+ <Action name="move_redo"/>
+ <Action name="move_load"/>
+ <Separator/>
+ <Action name="move_roll"/>
+ <Action name="move_end_turn"/>
+ <Action name="move_cube"/>
+</ToolBar>
+
+<ToolBar name="cmdToolBar" noMerge="1" fullWidth="true"><text>Command Toolbar</text>
+ <Action name="command_label"/>
+ <Action name="command_lineedit"/>
+</ToolBar>
+
+<StatusBar/>
+
+<Menu name="popup">
+ <Action name="options_show_menubar"/>
+ <Separator/>
+ <Action name="move_undo"/>
+ <Action name="move_redo"/>
+ <Action name="move_load"/>
+ <Separator/>
+ <Action name="move_roll"/>
+ <Action name="move_end_turn"/>
+ <Action name="move_cube"/>
+</Menu>
+
+</kpartgui>
diff --git a/kbackgammon/kbg.cpp b/kbackgammon/kbg.cpp
new file mode 100644
index 00000000..55aef32a
--- /dev/null
+++ b/kbackgammon/kbg.cpp
@@ -0,0 +1,830 @@
+/* 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 "kbg.h"
+#include "kbg.moc"
+
+#include <qpainter.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qpixmap.h>
+#include <qstring.h>
+#include <qwhatsthis.h>
+#include <qstringlist.h>
+#include <qvaluelist.h>
+#include <qiconset.h>
+#include <qvbox.h>
+
+#include <kmenubar.h>
+#include <ktoolbar.h>
+#include <kapplication.h>
+#include <kiconloader.h>
+#include <kedittoolbar.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kstdaction.h>
+#include <kstdgameaction.h>
+#include <kaboutdata.h>
+#include <kmessagebox.h>
+#include <kconfig.h>
+#include <kcompletion.h>
+#include <kcompletionbox.h>
+#include <kpopupmenu.h>
+#include <kurllabel.h>
+#include <krun.h>
+#include <kstatusbar.h>
+#include <klineedit.h>
+#include <knuminput.h>
+#include <kprinter.h>
+
+#include "kbgtextview.h"
+#include "offline/kbgoffline.h"
+#include "fibs/kbgfibs.h"
+#include "gnubg/kbggnubg.h"
+#include "nextgen/kbgng.h"
+#include "version.h"
+
+
+// == setup ====================================================================
+
+/*
+ * Constructor creates user interface, actions and first engine.
+ */
+KBg::KBg()
+{
+ /*
+ * Initialize menu strings
+ */
+ engineString[Offline] = i18n("Open Board");
+ engineString[FIBS ] = i18n("FIBS");
+ engineString[GNUbg ] = i18n("GNU Backgammon (Experimental)");
+ engineString[NextGen] = i18n("Next Generation (Experimental)");
+
+ helpTopic[FIBSHome][0] = i18n("FIBS Home");
+ helpTopic[FIBSHome][1] = "http://www.fibs.com/";
+
+ helpTopic[RuleHome][0] = i18n("Backgammon Rules");
+ helpTopic[RuleHome][1] = "http://www.bkgm.com/rules.html";
+
+ /*
+ * The main view is shared between the board and a small text window
+ */
+ panner = new QSplitter(Vertical, this, "panner");
+ board = new KBgBoardSetup(panner, "board");
+ status = new KBgTextView(panner, "status");
+ setCentralWidget(panner);
+
+ /*
+ * Create all actions needed by the application
+ */
+ newAction = KStdGameAction::gameNew(this, SLOT(openNew()), actionCollection());
+ newAction->setEnabled(false);
+ KStdGameAction::print(this, SLOT(print()), actionCollection());
+ KStdGameAction::quit(this, SLOT(close()), actionCollection());
+
+ QStringList list;
+ for (int i = 0; i < MaxEngine; i++)
+ list.append(engineString[i]);
+ engineSet = new KSelectAction(i18n("&Engine"), 0, this, SLOT(setupEngine()), actionCollection(),
+ "move_engine");
+ engineSet->setItems(list);
+
+ // AB: what the heck has this to do with redisplay? perhaps use reload instead?
+ loadAction = KStdAction::redisplay(this, SLOT(load()), actionCollection(), "move_load");
+ loadAction->setEnabled(false);
+ undoAction = KStdGameAction::undo(this, SLOT(undo()), actionCollection());
+ undoAction->setEnabled(false);
+ redoAction = KStdGameAction::redo(this, SLOT(redo()), actionCollection());
+ redoAction->setEnabled(false);
+
+ rollAction = KStdGameAction::roll(this, SLOT(roll()), actionCollection());
+ rollAction->setEnabled(false);
+ endAction = KStdGameAction::endTurn(this, SLOT(done()), actionCollection());
+ endAction->setEnabled(false);
+ cubeAction = new KAction(i18n("Double Cube"), QIconSet(kapp->iconLoader()->loadIcon
+ (PROG_NAME "-double.xpm", KIcon::Toolbar)),
+ 0, this, SLOT(cube()), actionCollection(), "move_cube");
+ cubeAction->setEnabled(false);
+
+ KStdAction::showMenubar(this, SLOT(toggleMenubar()), actionCollection());
+ KStdAction::preferences(this, SLOT(setupDlg()), actionCollection());
+ KStdAction::saveOptions(this, SLOT(saveConfig()), actionCollection());
+
+ KPopupMenu *p = (new KActionMenu(i18n("&Backgammon on the Web"),
+ actionCollection(), "help_www"))->popupMenu();
+
+ (new KAction(helpTopic[FIBSHome][0], 0, this, SLOT(wwwFIBS()),
+ actionCollection(), "help_www_fibs"))->plug(p);
+ (new KAction(helpTopic[RuleHome][0], 0, this, SLOT(wwwRule()),
+ actionCollection(), "help_www_rule"))->plug(p);
+
+ /*
+ * Set up the command line - using actions, otherwise recreating the GUI will delete them
+ * (e.g. using KEditToolbar)
+ */
+ cmdLabel = new QLabel(i18n("Command: "), this);
+ new KWidgetAction( cmdLabel, cmdLabel->text(), 0, 0, 0, actionCollection(), "command_label");
+ cmdLine = new KLineEdit(this, "commandline");
+ KWidgetAction* actionCmdLine = new KWidgetAction( cmdLine, QString::null, 0, 0, 0, actionCollection(), "command_lineedit");
+ actionCmdLine->setAutoSized(true);
+
+ cmdLine->completionObject()->setOrder(KCompletion::Weighted);
+ connect(cmdLine, SIGNAL(returnPressed(const QString &)), this, SLOT(handleCmd(const QString &)));
+ /*
+ * Done with the actions, create the XML-defined parts of the
+ * user interface
+ */
+ statusBar();
+ setupGUI();
+
+ cmdLine->setFocus();
+
+ /*
+ * Initialize the engine to the default (offline). If the user
+ * prefers a different engine, it will be started later
+ */
+ for (int i = 0; i < MaxEngine; i++)
+ engine[i] = 0;
+ currEngine = None;
+ engineSet->setCurrentItem(Offline);
+ setupEngine();
+
+ /*
+ * Set up configuration handling.
+ * FIXME: support session management
+ */
+ connect(this, SIGNAL(readSettings()), board, SLOT(readConfig()));
+ connect(this, SIGNAL(saveSettings()), board, SLOT(saveConfig()));
+
+ /*
+ * Set up some whatis messages for the online help
+ */
+ QWhatsThis::add(status, i18n("This area contains the status messages for the game. "
+ "Most of these messages are sent to you from the current "
+ "engine."));
+ QWhatsThis::add(toolBar("cmdToolBar"),
+ i18n("This is the command line. You can type special "
+ "commands related to the current engine in here. "
+ "Most relevant commands are also available "
+ "through the menus."));
+ QWhatsThis::add(toolBar("mainToolBar"),
+ i18n("This is the button bar tool bar. It gives "
+ "you easy access to game related commands. "
+ "You can drag the bar to a different location "
+ "within the window."));
+ QWhatsThis::add(statusBar(),
+ i18n("This is the status bar. It shows you the currently "
+ "selected engine in the left corner."));
+
+ /*
+ * Create and initialize the context menu
+ */
+ QPopupMenu* menu = (QPopupMenu*)factory()->container("popup", this);
+ board->setContextMenu(menu);
+}
+
+/*
+ * Destructor is empty
+ */
+KBg::~KBg() {}
+
+
+// == engine handling ==========================================================
+
+/*
+ * Set the engine according to the currently selected item in the
+ * engineSet action. Additional engines have to be added to the switch
+ * statement (and only there).
+ */
+void KBg::setupEngine()
+{
+ /*
+ * Get new engine type
+ */
+ int type = engineSet->currentItem();
+
+ /*
+ * Engine doesn't need to be changed?
+ */
+ if (engine[type]) return;
+
+ /*
+ * Check with the engine if it can be terminated
+ */
+ if (currEngine != None && engine[currEngine] && !engine[currEngine]->queryClose()) {
+ engineSet->setCurrentItem(currEngine);
+ return;
+ }
+
+ /*
+ * Remove the old engine, create a new one, and hook up menu and slots/signals
+ */
+ QPopupMenu *commandMenu = (QPopupMenu *)factory()->container("command_menu", this);
+ QString s = PROG_NAME;
+ commandMenu->clear();
+
+ if (currEngine != None) {
+ delete engine[currEngine];
+ engine[currEngine] = 0;
+ }
+
+ switch (currEngine = type) {
+ case Offline:
+ engine[currEngine] = new KBgEngineOffline(this, &s, commandMenu);
+ break;
+ case FIBS:
+ engine[currEngine] = new KBgEngineFIBS(this, &s, commandMenu);
+ break;
+ case GNUbg:
+ engine[currEngine] = new KBgEngineGNU(this, &s, commandMenu);
+ break;
+ case NextGen:
+ engine[currEngine] = new KBgEngineNg(this, &s, commandMenu);
+ break;
+ default: // FIXME: we need some kind of catch here...
+ currEngine = Offline;
+ engine[currEngine] = new KBgEngineOffline(this, &s, commandMenu);
+ break;
+ }
+
+ statusBar()->message(engineString[currEngine]);
+ KConfig* config = kapp->config();
+ config->setGroup("global settings");
+ if (config->readBoolEntry("enable timeout", true))
+ engine[currEngine]->setCommit(config->readDoubleNumEntry("timeout", 2.5));
+ newAction->setEnabled(engine[currEngine]->haveNewGame());
+
+ // engine -> this
+ connect(engine[currEngine], SIGNAL(statText(const QString &)), this, SLOT(updateCaption(const QString &)));
+ connect(engine[currEngine], SIGNAL(infoText(const QString &)), status, SLOT(write(const QString &)));
+ connect(engine[currEngine], SIGNAL(allowCommand(int, bool)), this, SLOT(allowCommand(int, bool)));
+
+ // this -> engine
+ connect(this, SIGNAL(readSettings()), engine[currEngine], SLOT(readConfig()));
+ connect(this, SIGNAL(saveSettings()), engine[currEngine], SLOT(saveConfig()));
+
+ // board -> engine
+ connect(board, SIGNAL(rollDice(const int)), engine[currEngine], SLOT(rollDice(const int)));
+ connect(board, SIGNAL(doubleCube(const int)), engine[currEngine], SLOT(doubleCube(const int)));
+ connect(board, SIGNAL(currentMove(QString *)), engine[currEngine], SLOT(handleMove(QString *)));
+
+ // engine -> board
+ connect(engine[currEngine], SIGNAL(undoMove()), board, SLOT(undoMove()));
+ connect(engine[currEngine], SIGNAL(redoMove()), board, SLOT(redoMove()));
+ connect(engine[currEngine], SIGNAL(setEditMode(const bool)), board, SLOT(setEditMode(const bool)));
+ connect(engine[currEngine], SIGNAL(allowMoving(const bool)), board, SLOT(allowMoving(const bool)));
+ connect(engine[currEngine], SIGNAL(getState(KBgStatus *)), board, SLOT(getState(KBgStatus *)));
+ connect(engine[currEngine], SIGNAL(newState(const KBgStatus &)), board, SLOT(setState(const KBgStatus &)));
+
+ // now that all signals are connected, start the engine
+ engine[currEngine]->start();
+}
+
+
+// == configuration handing ====================================================
+
+/*
+ * Save all settings that should be saved for the next start.
+ */
+void KBg::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("global settings");
+
+ /*
+ * Save the main window options unless the user has asked not
+ * to do so.
+ */
+ if (config->readBoolEntry("autosave on exit", true)) {
+
+ config->setGroup("main window");
+
+ config->writeEntry("origin", pos());
+
+ config->writeEntry("font", status->font());
+ config->writeEntry("panner", (double)board->height()/(double)panner->height());
+
+ saveMainWindowSettings(config, "main window");
+ }
+
+ /*
+ * Save the history
+ */
+ config->setGroup("command line");
+ config->writeEntry("history", cmdLine->completionObject()->items());
+
+ /*
+ * Save current engine
+ */
+ config->setGroup("engine settings");
+ config->writeEntry("last engine", currEngine);
+
+ /*
+ * Tell other objects to save their settings, too.
+ */
+ emit saveSettings();
+
+ config->sync();
+}
+
+/*
+ * Read the stored configuration and apply it
+ */
+void KBg::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup("global settings");
+
+ /*
+ * Restore the main window settings unless the user has asked
+ * not to do so.
+ */
+ if (config->readBoolEntry("autosave on exit", true)) {
+
+ config->setGroup("main window");
+
+ QPoint pos, defpos(10, 10);
+ QFont kappFont = kapp->font();
+
+ pos = config->readPointEntry("origin", &defpos);
+
+ status->setFont(config->readFontEntry("font", &kappFont));
+
+ QValueList<int> l;
+ l.append(qRound( config->readDoubleNumEntry("panner", 0.75) *panner->height()));
+ l.append(qRound((1-config->readDoubleNumEntry("panner", 0.75))*panner->height()));
+ panner->setSizes(l);
+
+ applyMainWindowSettings(config, "main window");
+ }
+
+ /*
+ * Restore the history
+ */
+ config->setGroup("command line");
+ cmdLine->completionObject()->setItems(config->readListEntry("history"));
+
+ /*
+ * Tell other objects to read their configurations
+ */
+ emit readSettings();
+
+ /*
+ * Restore last engine
+ */
+ config->setGroup("engine settings");
+ engineSet->setCurrentItem((Engines)config->readNumEntry("last engine", Offline));
+ setupEngine();
+}
+
+
+// == configuration ============================================================
+
+/*
+ * Connected to the setup dialog applyButtonPressed signal. Make sure
+ * that all changes are saved.
+ */
+void KBg::setupOk()
+{
+ // global settings
+ KConfig* config = kapp->config();
+ config->setGroup("global settings");
+
+ config->writeEntry("enable timeout", cbt->isChecked());
+ config->writeEntry("timeout", sbt->value());
+ config->writeEntry("autosave on exit", cbs->isChecked());
+
+ // tell engine about commit timer
+ engine[currEngine]->setCommit(cbt->isChecked() ? sbt->value() : -1);
+
+ // one time requests
+ if (cbm->isChecked())
+ KMessageBox::enableAllMessages();
+
+ // tell children to read their changes
+ board->setupOk();
+
+ // engines
+ for (int i = 0; i < MaxEngine; i++)
+ engine[i]->setupOk();
+
+ // save it all
+ saveConfig();
+}
+
+/*
+ * Load default values for the user settings
+ */
+void KBg::setupDefault()
+{
+ // timeout
+ cbt->setChecked(true);
+ sbt->setValue(2.5);
+
+ // messages
+ cbm->setChecked(false);
+
+ // auto save
+ cbs->setChecked(true);
+
+ // board
+ board->setupDefault();
+
+ // engines
+ for (int i = 0; i < MaxEngine; i++)
+ engine[i]->setupDefault();
+}
+
+/*
+ * Connected to the setup dialog cancelButtonPressed signal. There
+ * isn't much to do. We tell the board to undo the changes.
+ */
+void KBg::setupCancel()
+{
+ // board
+ board->setupCancel();
+
+ // engines
+ for (int i = 0; i < MaxEngine; i++)
+ engine[i]->setupCancel();
+}
+
+/*
+ * Setup dialog is ready to be deleted. Do it later...
+ */
+void KBg::setupDone()
+{
+ nb->delayedDestruct();
+ for (int i = 0; i < MaxEngine; i++)
+ if (i != currEngine) engine[i] = 0;
+}
+
+// FIXME make more general...
+
+void KBg::startKCM(const QString &url)
+{
+ KRun::runCommand(url);
+}
+
+/*
+ * Initialize and display the setup dialog
+ */
+void KBg::setupDlg()
+{
+ /*
+ * Get a new notebook in which all other members can put their
+ * config pages
+ */
+ nb = new KDialogBase(KDialogBase::IconList, i18n("Configuration"),
+ KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Default|
+ KDialogBase::Apply|KDialogBase::Help,
+ KDialogBase::Ok, this, "setup", true, true);
+
+ KConfig* config = kapp->config();
+ config->setGroup("global settings");
+
+ /*
+ * Main Widget
+ */
+ QVBox *w = nb->addVBoxPage(i18n("General"), i18n("Here you can configure general settings of %1").
+ arg(kapp->aboutData()->programName()),
+ kapp->iconLoader()->loadIcon("go", KIcon::Desktop));
+
+ /*
+ * Group boxes
+ */
+ QGroupBox *gbm = new QGroupBox(i18n("Messages"), w);
+ QGroupBox *gbt = new QGroupBox(i18n("Timer"), w);
+ QGroupBox *gbs = new QGroupBox(i18n("Autosave"), w);
+ QGroupBox *gbe = new QGroupBox(i18n("Events"), w);
+
+ /*
+ * Timer box
+ */
+ QWhatsThis::add(gbt, i18n("After you finished your moves, they have to be sent to the engine. "
+ "You can either do that manually (in which case you should not enable "
+ "this feature), or you can specify an amount of time that has to pass "
+ "before the move is committed. If you undo a move during the timeout, the "
+ "timeout will be reset and restarted once you finish the move. This is "
+ "very useful if you would like to review the result of your move."));
+
+ cbt = new QCheckBox(i18n("Enable timeout"), gbt);
+ cbt->setChecked(config->readBoolEntry("enable timeout", true));
+
+ sbt = new KDoubleNumInput(gbt);
+ sbt->setRange(0.0, 60.0, 0.5);
+ sbt->setLabel(i18n("Move timeout in seconds:"));
+ sbt->setValue(config->readDoubleNumEntry("timeout", 2.5));
+
+ connect(cbt, SIGNAL(toggled(bool)), sbt, SLOT(setEnabled(bool)));
+ sbt->setEnabled(cbt->isChecked());
+
+ QGridLayout *gl = new QGridLayout(gbt, 2, 1, 20);
+ gl->addWidget(cbt, 0, 0);
+ gl->addWidget(sbt, 1, 0);
+
+ /*
+ * Enable messages
+ */
+ QWhatsThis::add(gbm, i18n("Check the box to enable all the messages that you have previously "
+ "disabled by choosing the \"Don't show this message again\" option."));
+
+ QGridLayout *glm = new QGridLayout(gbm, 1, 1, nb->spacingHint());
+ cbm = new QCheckBox(i18n("Reenable all messages"), gbm);
+ glm->addWidget(cbm, 0, 0);
+
+ /*
+ * Save options on exit ?
+ */
+ QWhatsThis::add(gbm, i18n("Check the box to automatically save all window positions on program "
+ "exit. They will be restored at next start."));
+
+ QGridLayout *gls = new QGridLayout(gbs, 1, 1, nb->spacingHint());
+ cbs = new QCheckBox(i18n("Save settings on exit"), gbs);
+ cbs->setChecked(config->readBoolEntry("autosave on exit", true));
+ gls->addWidget(cbs, 0, 0);
+
+ /*
+ * Event vonfiguration
+ */
+ QWhatsThis::add(gbe, i18n("Event notification of %1 is configured as part of the "
+ "system-wide notification process. Click here, and you "
+ "will be able to configure system sounds, etc.").
+ arg(kapp->aboutData()->programName()));
+
+ QGridLayout *gle = new QGridLayout(gbe, 1, 1, nb->spacingHint());
+ KURLLabel *lab = new KURLLabel("kcmshell kcmnotify",
+ i18n("Klick here to configure the event notification"), gbe);
+ lab->setMaximumSize(lab->sizeHint());
+
+ gle->addWidget(lab, 0, 0);
+ connect(lab, SIGNAL(leftClickedURL(const QString &)), SLOT(startKCM(const QString &)));
+
+ /*
+ * Board settings
+ */
+ board->getSetupPages(nb);
+
+ /*
+ * Hack alert: this little trick makes sure that ALL engines
+ * have their settings available in the dialog.
+ */
+ QPopupMenu *dummyPopup = new QPopupMenu(nb);
+ QString s = PROG_NAME;
+ for (int i = 0; i < MaxEngine; i++) {
+ if (currEngine != i) {
+ switch (i) {
+ case Offline:
+ engine[i] = new KBgEngineOffline(nb, &s, dummyPopup);
+ break;
+ case FIBS:
+ engine[i] = new KBgEngineFIBS(nb, &s, dummyPopup);
+ break;
+ case GNUbg:
+ engine[i] = new KBgEngineGNU(nb, &s, dummyPopup);
+ break;
+ case NextGen:
+ engine[i] = new KBgEngineNg(nb, &s, dummyPopup);
+ break;
+ }
+ connect(this, SIGNAL(saveSettings()), engine[i], SLOT(saveConfig()));
+ }
+ engine[i]->getSetupPages(nb);
+ }
+
+ /*
+ * Connect the signals of nb
+ */
+ connect(nb, SIGNAL(okClicked()), this, SLOT(setupOk()));
+ connect(nb, SIGNAL(applyClicked()), this, SLOT(setupOk()));
+ connect(nb, SIGNAL(cancelClicked()), this, SLOT(setupCancel()));
+ connect(nb, SIGNAL(defaultClicked()),this, SLOT(setupDefault()));
+
+ connect(nb, SIGNAL(finished()), this, SLOT(setupDone()));
+
+ nb->resize(nb->minimumSize());
+ nb->show();
+}
+
+
+// == action slots =============================================================
+
+/*
+ * Tell the board to print itself - restore and save user settings for
+ * the print dialog.
+ */
+void KBg::print()
+{
+ KPrinter *prt = new KPrinter();
+
+ KConfig* config = kapp->config();
+ config->setGroup("printing");
+
+ prt->setNumCopies(config->readNumEntry("numcopies", 1));
+ prt->setOutputFileName(config->readPathEntry("outputfile"));
+ prt->setOutputToFile(config->readBoolEntry("tofile", false));
+ prt->setPageSize((KPrinter::PageSize) config->readNumEntry("pagesize", KPrinter::A4));
+ prt->setOrientation((KPrinter::Orientation)config->readNumEntry("orientation", KPrinter::Landscape));
+
+ if (prt->setup(this, i18n("Print %1").arg(baseCaption))) {
+ QPainter p;
+ p.begin(prt);
+ board->print(&p);
+ p.end();
+ config->writeEntry("tofile", prt->outputToFile());
+ config->writePathEntry("outputfile", prt->outputFileName());
+ config->writeEntry("pagesize", (int)prt->pageSize());
+ config->writeEntry("orientation", (int)prt->orientation());
+ config->writeEntry("numcopies", prt->numCopies());
+ }
+ delete prt;
+}
+
+/*
+ * Toggle visibility of the menubar - be careful that the menu doesn't
+ * get lost
+ */
+void KBg::toggleMenubar()
+{
+ if (menuBar()->isVisible()) {
+
+ KMessageBox::information(this, i18n("You can enable the menubar again with the "
+ "right mouse button menu of the board."),
+ i18n("Information"), "conf_menubar_information");
+ menuBar()->hide();
+
+ } else {
+
+ menuBar()->show();
+ }
+}
+
+/*
+ * Display a standard dialog for the toolbar content
+ */
+void KBg::configureToolbars()
+{
+ saveMainWindowSettings(KGlobal::config(), "kedittoolbar settings"); // temp group
+ KEditToolbar dlg(actionCollection(), xmlFile(), true, this);
+ connect(&dlg,SIGNAL(newToolbarConfig()),this,SLOT(newToolbarConfig()));
+ dlg.exec();
+ KGlobal::config()->deleteGroup( "kedittoolbar settings" ); // delete temp group
+}
+
+/*
+ * Called when clicking OK or Apply in the toolbar editor
+ */
+void KBg::newToolbarConfig()
+{
+ createGUI();
+ applyMainWindowSettings(KGlobal::config(), "kedittoolbar settings");
+}
+
+/*
+ * Help slots
+ */
+void KBg::wwwFIBS() {showWWW(FIBSHome);}
+void KBg::wwwRule() {showWWW(RuleHome);}
+
+void KBg::showWWW(int t)
+{
+ kapp->invokeBrowser(helpTopic[t][1]);
+}
+
+/*
+ * Edit slots
+ */
+void KBg::undo() {engine[currEngine]->undo();}
+void KBg::redo() {engine[currEngine]->redo();}
+void KBg::roll() {engine[currEngine]->roll();}
+void KBg::cube() {engine[currEngine]->cube();}
+void KBg::done() {engine[currEngine]->done();}
+void KBg::load() {engine[currEngine]->load();}
+
+/*
+ * Start a new game with the current engine
+ */
+void KBg::openNew()
+{
+ engine[currEngine]->newGame();
+}
+
+
+// == various slots - not for actions ==========================================
+
+/*
+ * Check with the engine if it is okay to close the window.
+ * If so, save settings.
+ */
+bool KBg::queryClose()
+{
+ bool ret = engine[currEngine]->queryClose();
+ if ( ret )
+ saveConfig();
+ return ret;
+}
+
+/*
+ * Set the caption of the main window. If the user has requested pip
+ * counts, they are appended, too.
+ */
+void KBg::updateCaption(const QString &s)
+{
+ baseCaption = s;
+ QString msg;
+ if (!s.isEmpty()) {
+ msg = s;
+ if (board->getPipCount(US) >= 0) {
+ QString tmp;
+ tmp.setNum(board->getPipCount(US ));
+ msg += " - " + tmp;
+ tmp.setNum(board->getPipCount(THEM));
+ msg += "-" + tmp;
+ }
+ }
+ setCaption(msg, false);
+}
+
+/*
+ * Take the string from the commandline, give it to the engine, append
+ * to the history and clear the buffer.
+ */
+void KBg::handleCmd(const QString &s)
+{
+ if (!s.stripWhiteSpace().isEmpty()) {
+ engine[currEngine]->handleCommand(s);
+ cmdLine->completionObject()->addItem(s);
+ }
+ cmdLine->clear();
+ cmdLine->completionBox()->close();
+}
+
+/*
+ * Reflect the availability of commands in the button bar.
+ */
+void KBg::allowCommand(int cmd, bool f)
+{
+ switch (cmd) {
+ case KBgEngine::Undo:
+ undoAction->setEnabled(f);
+ break;
+ case KBgEngine::Redo:
+ redoAction->setEnabled(f);
+ break;
+ case KBgEngine::Roll:
+ rollAction->setEnabled(f);
+ break;
+ case KBgEngine::Cube:
+ cubeAction->setEnabled(f);
+ break;
+ case KBgEngine::Done:
+ endAction->setEnabled(f);
+ break;
+ case KBgEngine::Load:
+ loadAction->setEnabled(f);
+ break;
+ }
+}
+
+/*
+ * Catch the hide envents. That way, the current engine can close its
+ * child windows.
+ */
+void KBg::hideEvent(QHideEvent *e)
+{
+ KMainWindow::hideEvent(e);
+ engine[currEngine]->hideEvent();
+}
+
+/*
+ * Catch the show envents. That way, the current engine can open any
+ * previously hidden windows.
+ */
+void KBg::showEvent(QShowEvent *e)
+{
+ KMainWindow::showEvent(e);
+ engine[currEngine]->showEvent();
+}
+
+// EOF
+
diff --git a/kbackgammon/kbg.h b/kbackgammon/kbg.h
new file mode 100644
index 00000000..d191c8a3
--- /dev/null
+++ b/kbackgammon/kbg.h
@@ -0,0 +1,228 @@
+/* Yo Emacs, this -*- C++ -*-
+
+ Copyright (C) 1999-2001 Jens Hoefkens
+ hoefkens@pilot.msu.edu
+
+ 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$
+
+*/
+
+#ifndef __KBG_H
+#define __KBG_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kmainwindow.h>
+
+class QSplitter;
+class QCheckBox;
+class QPopupMenu;
+class QLabel;
+class KAction;
+class KSelectAction;
+class KLineEdit;
+class KDialogBase;
+class KDoubleNumInput;
+
+class KBgEngine;
+class KBgTextView;
+class KBgBoardSetup;
+
+
+class KBg : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor creates the full main window
+ */
+ KBg();
+
+ /**
+ * Destructor
+ */
+ virtual ~KBg();
+
+ /**
+ * Read various settings from the configuration files or
+ * set some reasonable defaults
+ */
+ void readConfig();
+
+public slots:
+
+ /**
+ * Set the caption to KFIBS_NAME + string + pipcount (if requested by
+ * the user)
+ */
+ void updateCaption(const QString &s);
+
+ /**
+ * Slot to be called by the engine - it enables/disables buttons
+ * in the button bar
+ */
+ void allowCommand(int cmd, bool f);
+
+ /**
+ * Sets the backgammon engine to type
+ */
+ void setupEngine();
+
+ void startKCM(const QString &);
+
+signals:
+
+ /**
+ * Tell all listeners to write their settings to disk
+ */
+ void saveSettings();
+
+ /**
+ * Tell all listeners to restore their settings or use reasonable
+ * defaults
+ */
+ void readSettings();
+
+protected:
+
+ /*
+ * Windows are to be hidden
+ */
+ virtual void hideEvent(QHideEvent *);
+
+ /*
+ * Redisplay the windows
+ */
+ virtual void showEvent(QShowEvent *);
+
+ /*
+ * Called before the window is closed. Check with the engine
+ * if that is okay.
+ */
+ virtual bool queryClose();
+
+protected slots:
+
+ /**
+ * Show the button bar - or not - depending on the corresponding action
+ */
+ void toggleMenubar();
+
+ void configureToolbars();
+ void newToolbarConfig();
+
+ /**
+ * Starts the print dialog and asks the board to print itself
+ */
+ void print();
+
+ void openNew();
+
+ /**
+ * Takes text from the commandline and hands it over to the
+ * current engine
+ */
+ void handleCmd(const QString &);
+
+ /**
+ * Saves the user settings to disk
+ */
+ void saveConfig();
+
+ /**
+ * Slots for the respective actions - called by the button bar
+ * and some global key shortcuts
+ */
+ void undo();
+ void redo();
+ void roll();
+ void cube();
+ void load();
+ void done();
+
+ /**
+ * Opens and displays the respective home pages
+ */
+ void showWWW(int t);
+
+ void wwwFIBS();
+ void wwwRule();
+
+ /**
+ * Show the big setup dialog
+ */
+ void setupDlg();
+
+ /**
+ * Save the settings
+ */
+ void setupOk();
+
+ /**
+ * Delete the setup dialog
+ */
+ void setupDone();
+
+ /**
+ * Load default values for user settings
+ */
+ void setupDefault();
+
+ /**
+ * Undo the settings
+ */
+ void setupCancel();
+
+private:
+ KAction *newAction, *undoAction, *redoAction, *rollAction, *cubeAction, *endAction, *loadAction;
+
+ /*
+ * Each engine has its own identifier.
+ */
+ enum Engines {None = -1, Offline, FIBS, GNUbg, NextGen, MaxEngine};
+ QString engineString[MaxEngine];
+ KBgEngine *engine[MaxEngine];
+ int currEngine;
+
+ QPopupMenu *dummyPopup;
+ enum HelpTopics {FIBSHome, RuleHome, MaxHelpTopic};
+ QString helpTopic[MaxHelpTopic][2];
+ KSelectAction *engineSet;
+
+ /**
+ * Notebook for the setup
+ */
+ KDialogBase *nb;
+ KDoubleNumInput *sbt;
+ QCheckBox *cbt, *cbs, *cbm;
+
+ /*
+ * UI elements
+ */
+ QSplitter *panner;
+ KBgBoardSetup *board;
+ KBgTextView *status;
+ KLineEdit *cmdLine;
+ QLabel *cmdLabel;
+ QString baseCaption; // for user friendly printing, we keep it around
+};
+
+#endif // __KBG_H
diff --git a/kbackgammon/kbgboard.cpp b/kbackgammon/kbgboard.cpp
new file mode 100644
index 00000000..8b961a45
--- /dev/null
+++ b/kbackgammon/kbgboard.cpp
@@ -0,0 +1,2918 @@
+/* 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.
+
+*/
+
+/*
+
+ This file contains the implementation of the KBgBoard class and
+ all related utility classes.
+
+ Effort has been made to keep this class general. Please comment on that
+ if you want to use it in your own project.
+
+*/
+
+#include <kapplication.h>
+
+#include "kbgboard.h"
+#include "kbgboard.moc"
+
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+#include <kcolordialog.h>
+#include <klocale.h>
+#include <qlayout.h>
+#include <qgroupbox.h>
+#include <qbuttongroup.h>
+#include <kconfig.h>
+#include <qwhatsthis.h>
+#include <qvbox.h>
+#include <kiconloader.h>
+#include <ktabctl.h>
+#include <kpushbutton.h>
+#include <kstdguiitem.h>
+
+#include "version.h"
+
+
+const int CUBE_UPPER = 3;
+const int CUBE_LOWER = 4;
+
+static const int MINIMUM_CHECKER_SIZE = 10;
+
+/*
+ * Set the default settings in all user configurations
+ */
+void KBgBoardSetup::setupDefault()
+{
+ // default background color
+ setBackgroundColor(QColor(200, 200, 166));
+ pbc_1->setPalette(QPalette(backgroundColor()));
+
+ // checker colors
+ baseColors[0] = black;
+ baseColors[1] = white;
+ pbc_2->setPalette(QPalette(baseColors[0]));
+ pbc_3->setPalette(QPalette(baseColors[1]));
+
+ // default font
+ setFont(QFont("Serif", 18, QFont::Normal));
+ kf->setFont(getFont());
+
+ // short moves
+ setShortMoveMode(SHORT_MOVE_DOUBLE);
+ for (int i = 0; i < 3; i++)
+ rbMove[i]->setChecked(i == SHORT_MOVE_DOUBLE);
+
+ // pip count
+ cbp->setChecked(computePipCount = true);
+}
+
+/*
+ * User committed the changes. Save them.
+ */
+void KBgBoardSetup::setupOk()
+{
+ // font selection
+ setFont(kf->font());
+
+ // move strategy
+ for (int i = 0; i < 3; i++)
+ if (rbMove[i]->isChecked()) setShortMoveMode(i);
+
+ // pipcount
+ computePipCount = cbp->isChecked();
+}
+
+/*
+ * User cancelled the changes. Undo the color changes that become
+ * visible right away.
+ */
+void KBgBoardSetup::setupCancel()
+{
+ // undo background color change
+ setBackgroundColor(saveBackgroundColor);
+
+ // undo checker color changes
+ baseColors[0] = saveBaseColors[0];
+ baseColors[1] = saveBaseColors[1];
+
+ for (int i = 0; i < 30; i++)
+ cells[i]->update();
+}
+
+/*
+ * Fills configuration page in the dialog nb
+ */
+void KBgBoardSetup::getSetupPages(KDialogBase *nb)
+{
+ /*
+ * Main Widget
+ * ===========
+ */
+ QVBox *vbp = nb->addVBoxPage(i18n("Board"), i18n("Here you can configure the backgammon board"),
+ kapp->iconLoader()->loadIcon(PROG_NAME, KIcon::Desktop));
+
+ /*
+ * Need more than one page
+ */
+ KTabCtl *tc = new KTabCtl(vbp, "board tabs");
+
+ QWidget *w = new QWidget(tc);
+ QGridLayout *gl = new QGridLayout(w, 3, 1, nb->spacingHint());
+
+ /*
+ * Group boxes
+ * ===========
+ */
+ QGroupBox *ga = new QGroupBox(w);
+ QButtonGroup *gm = new QButtonGroup(w);
+ QGroupBox *go = new QGroupBox(w);
+
+ ga->setTitle(i18n("Colors"));
+ gm->setTitle(i18n("Short Moves"));
+ go->setTitle(i18n("Options"));
+
+ gl->addWidget(ga, 0, 0);
+ gl->addWidget(gm, 1, 0);
+ gl->addWidget(go, 2, 0);
+
+ /*
+ * Appearance group
+ * ----------------
+ */
+ QGridLayout *blc = new QGridLayout(ga, 2, 2, 20);
+
+ pbc_1 = new QPushButton(i18n("Background"), ga);
+ pbc_1->setPalette(QPalette(backgroundColor()));
+
+ pbc_2 = new QPushButton(i18n("Color 1"), ga);
+ pbc_2->setPalette(QPalette(baseColors[0]));
+
+ pbc_3 = new QPushButton(i18n("Color 2"), ga);
+ pbc_3->setPalette(QPalette(baseColors[1]));
+
+ blc->addWidget(pbc_2, 0, 0);
+ blc->addWidget(pbc_3, 0, 1);
+ blc->addMultiCellWidget(pbc_1, 1, 1, 0, 1);
+
+ connect(pbc_1, SIGNAL(clicked()), this, SLOT(selectBackgroundColor()));
+ connect(pbc_2, SIGNAL(clicked()), this, SLOT(selectBaseColorOne()));
+ connect(pbc_3, SIGNAL(clicked()), this, SLOT(selectBaseColorTwo()));
+
+ /*
+ * Moving style
+ * ------------
+ */
+ QBoxLayout *blm = new QVBoxLayout(gm, nb->spacingHint());
+
+ blm->addSpacing(gm->fontMetrics().height());
+
+ for (int i = 0; i < 3; i++)
+ rbMove[i] = new QRadioButton(gm);
+
+ rbMove[SHORT_MOVE_NONE]->setText(i18n("&Disable short moves. Only drag and drop will move."));
+ rbMove[SHORT_MOVE_SINGLE]->setText(i18n("&Single clicks with the left mouse button will\n"
+ "move a checker the shortest possible distance."));
+ rbMove[SHORT_MOVE_DOUBLE]->setText(i18n("D&ouble clicks with the left mouse button will\n"
+ "move a checker the shortest possible distance."));
+
+ for (int i = 0; i < 3; i++) {
+ rbMove[i]->setMinimumSize(rbMove[i]->sizeHint());
+ blm->addWidget(rbMove[i]);
+ rbMove[i]->setChecked(i == getShortMoveMode());
+ }
+
+ /*
+ * Other options
+ * -------------
+ */
+ QGridLayout *glo = new QGridLayout(go, 1, 1, 20);
+
+ cbp = new QCheckBox(i18n("Show pip count in title bar"), go);
+ cbp->setChecked(computePipCount);
+ cbp->adjustSize();
+ cbp->setMinimumSize(cbp->size());
+
+ glo->addRowSpacing(0, cbp->height());
+ glo->addWidget(cbp, 0, 0);
+
+ gl->activate();
+
+ w->adjustSize();
+ w->setMinimumSize(w->size());
+
+ tc->addTab(w, i18n("&Board"));
+
+ /*
+ * Save current settings
+ * ---------------------
+ */
+ saveBackgroundColor = backgroundColor();
+ saveBaseColors[0] = baseColors[0];
+ saveBaseColors[1] = baseColors[1];
+
+ /*
+ * Font selection page
+ * ===================
+ */
+ w = new QWidget(tc);
+ kf = new KFontChooser(w);
+ kf->setFont(getFont());
+ gl = new QGridLayout(w, 1, 1, nb->spacingHint());
+ gl->addWidget(kf, 0, 0);
+ gl->activate();
+ w->adjustSize();
+ w->setMinimumSize(w->size());
+ tc->addTab(w, i18n("&Font"));
+}
+
+/*
+ * Empty constructor calls the board constructor
+ */
+KBgBoardSetup::KBgBoardSetup(QWidget *parent, const char *name, QPopupMenu *menu)
+ : KBgBoard(parent, name, menu)
+{
+ // empty
+}
+
+/*
+ * User changed first checker color
+ */
+void KBgBoardSetup::selectBaseColorOne()
+{
+ KColorDialog *c = new KColorDialog(this, "base-col-1", true);
+ c->setColor(baseColors[0]);
+ if (c->exec()) {
+ baseColors[0] = c->color();
+ pbc_2->setPalette(QPalette(baseColors[0]));
+ for (int i = 0; i < 30; i++)
+ cells[i]->update();
+ }
+ delete c;
+}
+
+/*
+ * User changed second checker color
+ */
+void KBgBoardSetup::selectBaseColorTwo()
+{
+ KColorDialog *c = new KColorDialog(this, "base-col-2", true);
+ c->setColor(baseColors[1]);
+ if (c->exec()) {
+ baseColors[1] = c->color();
+ pbc_3->setPalette(QPalette(baseColors[1]));
+ for (int i = 0; i < 30; i++)
+ cells[i]->update();
+ }
+ delete c;
+}
+
+/*
+ * User changed background color
+ */
+void KBgBoardSetup::selectBackgroundColor()
+{
+ KColorDialog *c = new KColorDialog(this, "bg-col", true);
+ c->setColor(backgroundColor());
+ if (c->exec()) {
+ setBackgroundColor(c->color());
+ pbc_1->setPalette(QPalette(backgroundColor()));
+ for (int i = 0; i < 30; i++)
+ cells[i]->update();
+ }
+ delete c;
+}
+
+/*
+ * Saves the persistent settings of the board
+ */
+void KBgBoard::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ config->writeEntry("bgcolor", backgroundColor());
+ config->writeEntry("color-1", baseColors[0]);
+ config->writeEntry("color-2", baseColors[1]);
+ config->writeEntry("font", getFont());
+ config->writeEntry("move", getShortMoveMode());
+ config->writeEntry("pip", computePipCount);
+}
+
+/*
+ * Restore the settings or use reasonable defaults
+ */
+void KBgBoard::readConfig()
+{
+ QColor col(200, 200, 166);
+ QFont fon("Serif", 18, QFont::Normal);
+
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ setBackgroundColor(config->readColorEntry("bgcolor", &col));
+ baseColors[0] = config->readColorEntry("color-1", &black);
+ baseColors[1] = config->readColorEntry("color-2", &white);
+ setFont(config->readFontEntry("font", &fon));
+ setShortMoveMode(config->readNumEntry("move", SHORT_MOVE_DOUBLE));
+ computePipCount = config->readBoolEntry("pip", true);
+}
+
+/*
+ * Get the font the board cells should use for the display of
+ * numbers and cube value.
+ */
+QFont KBgBoard::getFont() const
+{
+ return boardFont;
+}
+
+/*
+ * Allows the users of the board classe to set the font to be used
+ * on the board. Note that the fontsize is dynamically set
+ */
+void KBgBoard::setFont(const QFont& f)
+{
+ boardFont = f;
+}
+
+/*
+ * Ask the user for an updated cube value
+ */
+void KBgBoard::queryCube()
+{
+ KBgStatus *st = new KBgStatus();
+ getState(st);
+ KBgBoardQCube *dlg =
+ new KBgBoardQCube(abs(st->cube()), (st->cube(US) > 0), (st->cube(THEM) > 0));
+ if (dlg->exec()) {
+ bool u = ((dlg->getCubeValue() == 0) || (dlg->getCubeOwner() == US ));
+ bool t = ((dlg->getCubeValue() == 0) || (dlg->getCubeOwner() == THEM));
+ st->setCube((int)rint(pow(2.0, dlg->getCubeValue())), u, t);
+ setState(*st); // JENS
+ }
+ delete dlg;
+ delete st;
+}
+
+/*
+ * Constructor, creates the dialog but does not show nor execute it.
+ */
+KBgBoardQCube::KBgBoardQCube(int val, bool us, bool them)
+ : QDialog(0, 0, true)
+{
+ setCaption(i18n("Set Cube Values"));
+
+ QBoxLayout *vbox = new QVBoxLayout(this, 17);
+
+ QLabel *info = new QLabel(this);
+
+ cb[0] = new QComboBox(this, "first sb");
+ cb[1] = new QComboBox(this, "second sb");
+ ok = new KPushButton(KStdGuiItem::ok(), this);
+ cancel = new KPushButton(KStdGuiItem::cancel(), this);
+
+ info->setText(i18n("Set the face value of the cube and select who should be able to\n"
+ "double. Note that a face value of 1 automatically allows both\n"
+ "players to double."));
+
+ info->setMinimumSize(info->sizeHint());
+
+ vbox->addWidget(info, 0);
+
+ QBoxLayout *hbox_1 = new QHBoxLayout();
+ QBoxLayout *hbox_2 = new QHBoxLayout();
+
+ vbox->addLayout(hbox_1);
+ vbox->addLayout(hbox_2);
+
+ hbox_1->addWidget(cb[1]);
+ hbox_1->addWidget(cb[0]);
+
+ hbox_2->addWidget(ok);
+ hbox_2->addWidget(cancel);
+
+ cb[0]->insertItem(" 1", 0);
+ cb[0]->insertItem(" 2", 1);
+ cb[0]->insertItem(" 4", 2);
+ cb[0]->insertItem(" 8", 3);
+ cb[0]->insertItem("16", 4);
+ cb[0]->insertItem("32", 5);
+ cb[0]->insertItem("64", 6);
+
+ switch(val) {
+ case 1:
+ cb[0]->setCurrentItem(0);
+ break;
+ case 2:
+ cb[0]->setCurrentItem(1);
+ break;
+ case 4:
+ cb[0]->setCurrentItem(2);
+ break;
+ case 8:
+ cb[0]->setCurrentItem(3);
+ break;
+ case 16:
+ cb[0]->setCurrentItem(4);
+ break;
+ case 32:
+ cb[0]->setCurrentItem(5);
+ break;
+ case 64:
+ cb[0]->setCurrentItem(6);
+ break;
+ }
+
+ cb[1]->insertItem(i18n("Lower Player"), US);
+ cb[1]->insertItem(i18n("Upper Player"), THEM);
+ cb[1]->insertItem(i18n("Open Cube"), BOTH);
+
+ if (us && them)
+ cb[1]->setCurrentItem(BOTH);
+ else if (us)
+ cb[1]->setCurrentItem(US);
+ else if (them)
+ cb[1]->setCurrentItem(THEM);
+
+ cb[0]->setMinimumSize(cb[0]->sizeHint());
+ cb[1]->setMinimumSize(cb[1]->sizeHint());
+
+ ok->setMinimumSize(ok->sizeHint());
+ cancel->setMinimumSize(cancel->sizeHint());
+
+ setMinimumSize(childrenRect().size());
+
+ vbox->activate();
+
+ resize(minimumSize());
+
+ ok->setAutoDefault (true);
+ ok->setDefault(true);
+
+ cb[0]->setFocus();
+
+ connect(ok, SIGNAL(clicked()), SLOT(accept()));
+ connect(cancel, SIGNAL(clicked()), SLOT(reject()));
+
+ connect(cb[0], SIGNAL(activated(int)), SLOT(changePlayer(int)));
+ connect(cb[1], SIGNAL(activated(int)), SLOT(changeValue (int)));
+}
+
+/*
+ * Deconstructor, empty.
+ */
+KBgBoardQCube::~KBgBoardQCube()
+{
+ // nothing
+}
+
+/*
+ * Get the face value of the cube
+ */
+int KBgBoardQCube::getCubeValue()
+{
+ return cb[0]->currentItem();
+}
+
+/*
+ * Get the owner of the cube
+ */
+int KBgBoardQCube::getCubeOwner()
+{
+ return cb[1]->currentItem();
+}
+
+/*
+ * If the cube is open, the value can only be 1
+ */
+void KBgBoardQCube::changeValue(int player)
+{
+ if (player == BOTH)
+ cb[0]->setCurrentItem(0);
+
+}
+
+/*
+ * If the value is 1, the cube has to be open; and if the value
+ * becomes bigger than 1, the player cannot stay open.
+ */
+void KBgBoardQCube::changePlayer(int val)
+{
+ if (val == 0)
+ cb[1]->setCurrentItem(BOTH);
+ else if (cb[1]->currentItem() == BOTH)
+ cb[1]->setCurrentItem(US);
+}
+
+/*
+ * Constructor, creates the dialog but does not show nor execute it.
+ */
+KBgBoardQDice::KBgBoardQDice(const char *name)
+ : QDialog(0, name, true)
+{
+ setCaption(i18n("Set Dice Values"));
+
+ QBoxLayout *vbox = new QVBoxLayout(this, 17);
+
+ QLabel *info = new QLabel(this);
+
+ sb[0] = new QSpinBox(this, "first sb");
+ sb[1] = new QSpinBox(this, "second sb");
+ ok = new KPushButton(KStdGuiItem::ok(), this);
+ cancel = new KPushButton(KStdGuiItem::cancel(), this);
+
+ info->setText(i18n("Set the face values of the selected dice. The other player's\n"
+ "dice will be cleared and it will be the dice's owner's turn."));
+
+ info->setMinimumSize(info->sizeHint());
+
+ vbox->addWidget(info, 0);
+
+ QBoxLayout *hbox_1 = new QHBoxLayout();
+ QBoxLayout *hbox_2 = new QHBoxLayout();
+
+ vbox->addLayout(hbox_1);
+ vbox->addLayout(hbox_2);
+
+ hbox_1->addWidget(sb[0]);
+ hbox_1->addWidget(sb[1]);
+
+ hbox_2->addWidget(ok);
+ hbox_2->addWidget(cancel);
+
+ sb[0]->setMinimumSize(sb[0]->sizeHint());
+ sb[1]->setMinimumSize(sb[1]->sizeHint());
+
+ ok->setMinimumSize(ok->sizeHint());
+ cancel->setMinimumSize(cancel->sizeHint());
+
+ setMinimumSize(childrenRect().size());
+
+ vbox->activate();
+
+ resize(minimumSize());
+
+ ok->setAutoDefault (true);
+ ok->setDefault(true);
+
+ sb[0]->setFocus();
+
+ connect(ok, SIGNAL(clicked()), SLOT(accept()));
+ connect(cancel, SIGNAL(clicked()), SLOT(reject()));
+
+ sb[0]->setValue(1);
+ sb[1]->setValue(1);
+
+ sb[0]->setRange(1, 6);
+ sb[1]->setRange(1, 6);
+}
+
+/*
+ * Deconstructor, empty.
+ */
+KBgBoardQDice::~KBgBoardQDice()
+{
+ // nothing
+}
+
+/*
+ * Get the face value of the dice
+ */
+int KBgBoardQDice::getDice(int n)
+{
+ return sb[n]->value();
+}
+
+/*
+ * Allows for overriding the current turn color in edit mode.
+ */
+void KBgBoard::storeTurn(const int pcs)
+{
+ storedTurn = ((pcs > 0) ? +1 : -1);
+}
+
+/*
+ * Switch edit mode on/off
+ */
+void KBgBoard::setEditMode(const bool m)
+{
+ editMode = m;
+}
+
+/*
+ * Retrurns the current edit mode status.
+ */
+bool KBgBoard::getEditMode() const
+{
+ return editMode;
+}
+
+/*
+ * This function takes a KBgStatus object and fills it with the current
+ * board status.
+ */
+KBgStatus* KBgBoard::getState(KBgStatus *st) const
+{
+ st->setColor(color);
+ st->setDirection(direction);
+
+ st->setCube(cube, maydouble[US], maydouble[THEM]);
+
+ st->setBar(US, onbar[US]); st->setBar(THEM, onbar[THEM]);
+ st->setHome(US, onhome[US]); st->setHome(THEM, onhome[THEM]);
+
+ st->setDice(US, 0, dice[US][0]);
+ st->setDice(US, 1, dice[US][1]);
+
+ st->setDice(THEM, 0, dice[THEM][0]);
+ st->setDice(THEM, 1, dice[THEM][1]);
+
+ for (int i = 1; i < 25; ++i)
+ st->setBoard(i, ((color*board[i] < 0) ? THEM : US), abs(board[i]));
+
+ return st;
+}
+
+/*
+ * This function lets external users change the context menu
+ */
+void KBgBoard::setContextMenu(QPopupMenu *menu)
+{
+ contextMenu = menu;
+}
+
+/*
+ * This function prints all moves up to now in the extended FIBS command
+ * notation (that is moves that involved kicking have a "+" instead of "-".
+ */
+void KBgBoard::sendMove()
+{
+ if (getEditMode())
+ return;
+
+ QString s, t;
+
+ s.setNum(moveHistory.count());
+ s += " ";
+
+ QPtrListIterator<KBgBoardMove> it(moveHistory);
+ for (; it.current(); ++it) {
+ KBgBoardMove *move = it.current();
+ if (move->source() == BAR_US || move->source() == BAR_THEM ) {
+ s += "bar";
+ } else {
+ t.setNum(move->source());
+ s += t;
+ }
+ if (move->wasKicked())
+ s += "+";
+ else
+ s += "-";
+
+ if ((move->destination() != HOME_THEM_LEFT) && (move->destination() != HOME_THEM_RIGHT) &&
+ (move->destination() != HOME_US_LEFT ) && (move->destination() != HOME_US_RIGHT )) {
+ t.setNum(move->destination());
+ s += t;
+ } else {
+ s += "off";
+ }
+ s += " ";
+ }
+ emit currentMove(&s);
+}
+
+/*
+ * This is overloaded from QWidget, since it has to pass the new
+ * background color to the child widgets (the cells).
+ */
+void KBgBoard::setBackgroundColor(const QColor &col)
+{
+ if (col != backgroundColor()) {
+ QWidget::setBackgroundColor(col);
+ for( int i = 0; i < 30; ++i)
+ cells[i]->setBackgroundColor(col);
+ }
+}
+
+/*
+ * Overloaded from QWidget since we have to resize all cells
+ */
+void KBgBoard::resizeEvent(QResizeEvent *)
+{
+ int xo0 = 0;
+ int xo1, w;
+ int hu = height()/2;
+ int hl = height() - hu;
+
+ checkerDiam = (int)((width()/15-2)<(height()/10.0-2) ?
+ (width()/15-2) : (height()/10.0-2));
+
+ if (checkerDiam < MINIMUM_CHECKER_SIZE)
+ checkerDiam = MINIMUM_CHECKER_SIZE;
+
+ for (int i = 0; i < 14; ++i) {
+ xo1 = int((i+1)*width()/15.0);
+ w = xo1 - xo0;
+ cells[i ]->setGeometry(xo0, 0, w, hu);
+ cells[i+15]->setGeometry(xo0, hu, w, hl);
+ xo0 = xo1;
+ }
+ cells[14]->setGeometry(xo0, 0, width() - xo0, hu);
+ cells[29]->setGeometry(xo0, hu, width() - xo0, hl);
+}
+
+/*
+ * This function draws the whole board in black and white on the
+ * painter *p. It is very well suited for printing on paper.
+ * It scales the output according to the width of the widget.
+ * I.e. if the widget is insanely long (y-direction) this will look
+ * shitty. The upper 20% of the painter are not used. So the caller
+ * can print whatever she/he wants above the 0.2*p->viewport().height()
+ * margin (like game status information).
+ */
+void KBgBoard::print(QPainter *p)
+{
+ double sf = 0.8*p->viewport().width()/width();
+ int xo = int((p->viewport().width() - sf*width())/2);
+ int yo = int(0.2*p->viewport().height());
+ int hu = height()/2;
+
+ int xo0 = 0;
+ for (int i = 0; i < 15; ++i) {
+ cells[i ]->paintCell(p, xo+sf*xo0, yo , sf);
+ cells[i+15]->paintCell(p, xo+sf*xo0, yo+sf*(hu-1), sf);
+ xo0 = int((i+1)*width()/15.0);
+ }
+}
+
+/*
+ * This function returns the selected drawing color for a checker
+ * of the given sign(!). I.e. we distinguish checkers by whether
+ * they are negative or positive.
+ */
+QColor KBgBoard::getCheckerColor(int p) const
+{
+ return ((p < 0) ? baseColors[0] : baseColors[1]);
+}
+
+/*
+ * This small utility function returns the y-coordinate base
+ * of a checker. This is the offset in the y-coordinate at
+ * which we have toposition the upper corner of the first
+ * checker so that it is fully in the cell.
+ */
+int KBgBoardField::numberBase() const
+{
+ return (cellID < 13) ? 0 : height()-20;
+}
+
+/*
+ * This function computes the proper diameter for checkers on this cell.
+ * It tries to stay within the horizontal boundaries and adjusts the
+ * diameter in such a way that 5 checkers fit on top of each other and
+ * there is still some room for stacked checkers.
+ */
+int KBgBoardCell::getCheckerDiameter() const
+{
+ return board->checkerDiam;
+}
+
+/*
+ * Draws the cells content using the painter p.
+ * Reimplemented from QLabel.
+ */
+void KBgBoardCell::drawContents(QPainter *)
+{
+ QRect cr(0, 0, width(), height());
+ cr.moveBottomLeft(rect().bottomLeft());
+ QPixmap pix(cr.size());
+ QPainter tmp;
+ pix.fill(this, cr.topLeft());
+ tmp.begin(&pix);
+ paintCell(&tmp);
+ tmp.end();
+ bitBlt(this, 0, 0, &pix);
+ /*
+ * New state is now current.
+ * This avoids unnecessary redrawings.
+ */
+ stateChanged = false;
+}
+
+/*
+ * This does the absolute bare minimum of painting a cell. It draws a small
+ * horizontal black line that marks the outer boundary of the cell and all
+ * overloaded paintCell() member are supposed to call this one after(!) they
+ * have painted themselves.
+ */
+void KBgBoardCell::paintCell(QPainter *p, int xo, int yo, double sf) const
+{
+ int x1 = xo; int x2 = xo;
+ int y1 = yo; int y2 = yo;
+
+ if ((cellID==HOME_THEM_LEFT || cellID==BAR_THEM) ||
+ (cellID<13 && cellID>0)) {
+ x2 += int(sf*width());
+ } else if ((cellID==HOME_US_LEFT || cellID==BAR_US) ||
+ (cellID<25 && cellID>12)) {
+ x2 += int(sf*width());
+ y1 = y2 += int(sf*(height()-1));
+ } else if (cellID == HOME_THEM_RIGHT) {
+ x2 += int(sf*(width()-1));
+ } else if (cellID == HOME_US_RIGHT) {
+ x2 += int(sf*(width()-1));
+ y1 = y2 += int(sf*(height()-1));
+ } else {
+ return; // do nothing if the cellID is wrong
+ }
+
+ // draw line in black
+ p->setBrush( black );
+ p->setPen( black );
+ p->drawLine(x1, y1, x2, y2);
+}
+
+/*
+ * This function draws vertical boundaries around a cell. This is used
+ * for bars and homes to get them separated from the rest of the board.
+ */
+void
+KBgBoardCell::drawVertBorder(QPainter *p, int xo, int yo, double sf) const
+{
+ p->setBrush(black);
+ p->setPen(black);
+ p->drawLine(xo, yo, xo, yo+sf*(height()-1));
+ p->drawLine(xo+sf*(width()-1), yo, xo+sf*(width()-1), yo+sf*(height()-1));
+}
+
+/*
+ * This function draws the content of the homes on the painter *p. It
+ * starts at the upper left corner (xo, yo) and uses the scaling factor
+ * sf.
+ */
+void KBgBoardHome::paintCell(QPainter *p, int xo, int yo, double sf) const
+{
+ /*
+ * Only these homes contain checkers. The other ones contains dice and cube.
+ */
+ if (((cellID == HOME_THEM_LEFT ) && (direction > 0)) ||
+ ((cellID == HOME_THEM_RIGHT) && (direction < 0)) ||
+ ((cellID == HOME_US_LEFT ) && (direction > 0)) ||
+ ((cellID == HOME_US_RIGHT ) && (direction < 0))) {
+
+ drawOverlappingCheckers(p, xo, yo, sf);
+
+ } else {
+
+ drawDiceAndCube(p, ((cellID == HOME_THEM_LEFT ||
+ cellID == HOME_THEM_RIGHT) ?
+ THEM : US), xo, yo, sf);
+
+ }
+
+ /*
+ * Finally draw the boundaries
+ */
+ drawVertBorder(p, xo, yo, sf);
+ KBgBoardCell::paintCell(p, xo, yo, sf);
+}
+
+/*
+ * This function draws the content of the bar cells. Bars may contain
+ * checkers and the cube. Please read the comments in the code on how
+ * and why the checkers and (especially) the cube is printed.
+ */
+void KBgBoardBar::paintCell(QPainter *p, int xo, int yo, double sf) const
+{
+ /*
+ * Put in the checkers.
+ */
+ drawOverlappingCheckers(p, xo, yo, sf);
+
+ /*
+ * Now comes a slightly tricky part: the cube belongs in the center
+ * of the board if nobody has doubled yet. In the way we do the board
+ * the center belongs to two(!) fields - both bars.
+ *
+ * If we are not printing on paper we use the fact that
+ * Qt will clip the drawing for us. So we print the upper
+ * half of the cube and the lower half on different cells.
+ *
+ * Since there is no such thing as clipping when we print
+ * on paper we can only print one cube. It turns out that
+ * the lower one is sufficiently centered.
+ */
+ if (board->canDouble(US) &&
+ board->canDouble(THEM) &&
+ !(abs(xo)+abs(yo) > 0 && cellID == BAR_THEM)) {
+
+ drawCube(p, cellID == BAR_THEM ? CUBE_UPPER :
+ CUBE_LOWER, xo, yo, sf);
+
+ }
+
+ /*
+ * Finally draw the boundaries
+ */
+ drawVertBorder(p, xo, yo, sf);
+ KBgBoardCell::paintCell(p, xo, yo, sf);
+}
+
+
+/*
+ * This function draws a cube on the painetr p. The cube will be drawn in
+ * the coundaries given by cubeRect(...). The other parameters are like
+ * in the other functions.
+ */
+void KBgBoardCell::drawCube(QPainter *p, int who, int xo, int yo,
+ double sf) const
+{
+ QRect r = cubeRect(who, true, sf);
+ r.moveTopLeft(QPoint(xo+r.left(), yo+r.top()));
+
+ p->setBrush(black);
+ p->setPen(black);
+ p->drawRoundRect(r, 20, 20);
+
+ r = cubeRect(who, false, sf);
+ r.moveTopLeft(QPoint(xo+r.left(), yo+r.top()));
+
+ p->setBrush(white);
+ p->setPen(white);
+ p->drawRoundRect(r, 20, 20);
+
+ p->setBrush(black);
+ p->setPen(black);
+
+ QString cubeNum;
+ int v = board->getCube();
+ /*
+ * Ensure that the cube shows 64 initially
+ */
+ if (v == 1) v = 64;
+ cubeNum.setNum(v);
+
+ /*
+ * Adjust the font size
+ */
+ QFont f = board->getFont();
+ f.setPointSizeFloat(0.75*f.pointSizeFloat());
+ p->setFont(f);
+ p->drawText(r, AlignCenter, cubeNum);
+}
+
+/*
+ * This function returns a boundary rectangle for the dice. It does so for both
+ * dice (i is either 0 or 1). It can return big and small rectangles and everything
+ * is scaled with a default value of 1.0. The scale parameter determines the the
+ * size of the dice relative to the checker diameter.
+ */
+QRect KBgBoardCell::diceRect(int i, bool big, double sf, double scale) const
+{
+ int d = int(scale*getCheckerDiameter());
+ int l = (1+width())%2;
+ int k = (big ? 0 : 1);
+ return(QRect(sf*(width()/2-d+k),
+ sf*(height()/2-2*d-3+2*i*(d+3)-1+k),
+ sf*(2*(d-k)+1-l),
+ sf*(2*(d-k)+1-l)));
+}
+
+/*
+ * This function returns a bounding rectangle for the cube. This rectangle
+ * is moved to the correct place and scaled correctly. The cube is slightly
+ * smaller than the dice.
+ */
+QRect KBgBoardCell::cubeRect(int who, bool big, double sf) const
+{
+ QRect r = diceRect(0, big, sf, 0.40);
+
+ int d = int(0.40*getCheckerDiameter());
+ int h = r.height();
+ int k = (big ? 1 : 0);
+
+ switch (who) {
+ case US:
+ r.setTop(sf*(height() - 3*d) - k);
+ break;
+ case THEM:
+ r.setTop(sf*d - k);
+ break;
+ case CUBE_UPPER:
+ r.setTop(height()-d*sf - k);
+ break;
+ case CUBE_LOWER:
+ r.setTop( -d*sf - k);
+ break;
+ default:
+ return(QRect(0,0,0,0));
+ }
+ r.setHeight(h);
+ return r;
+}
+
+/*
+ * This function draws the face value on a given dice painter.
+ * If the painting of dice should be saved this is the place
+ * to modify.
+ */
+void KBgBoardHome::drawDiceFace(QPainter *p, int col, int num, int who,
+ int xo, int yo, double sf) const
+{
+ p->setBrush(board->getCheckerColor(col));
+ p->setPen(board->getCheckerColor(col));
+
+ QRect r = diceRect(num, false, sf);
+ r.moveTopLeft(QPoint(xo+r.left(), yo+r.top()));
+
+ int cx = r.width() /2;
+ int cy = r.height()/2;
+ int cx2 = cx/2;
+ int cy2 = cy/2;
+ int cx7 = int(0.7*cx);
+ int cy7 = int(0.7*cy);
+
+ switch (board->getDice(who, num)) {
+ case 5:
+ p->drawEllipse(r.x()+cx-cx7 , r.y()+cy+cy7-1, 2, 2);
+ p->drawEllipse(r.x()+cx+cx7-1, r.y()+cy-cy7 , 2, 2);
+ case 3: // fall through
+ p->drawEllipse(r.x()+cx-cx7 , r.y()+cy-cy7 , 2, 2);
+ p->drawEllipse(r.x()+cx+cx7-1, r.y()+cy+cy7-1, 2, 2);
+ case 1: // fall through
+ p->drawEllipse(r.x()+cx , r.y()+cy , 2, 2);
+ break;
+ case 4:
+ p->drawEllipse(r.x()+cx-cx2, r.y()+cy+cy2-1, 2, 2);
+ p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy-cy2, 2, 2);
+ case 2: // fall through
+ p->drawEllipse(r.x()+cx-cx2, r.y()+cy-cy2, 2, 2);
+ p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy+cy2-1, 2, 2);
+ break;
+ case 6:
+ p->drawEllipse(r.x()+cx-cx2, r.y()+cy-cy7, 2, 2);
+ p->drawEllipse(r.x()+cx-cx2, r.y()+cy, 2, 2);
+ p->drawEllipse(r.x()+cx-cx2, r.y()+cy+cy7, 2, 2);
+ p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy-cy7, 2, 2);
+ p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy, 2, 2);
+ p->drawEllipse(r.x()+cx+cx2-1, r.y()+cy+cy7, 2, 2);
+ break;
+ default: // nothing
+ break;
+ }
+}
+
+/*
+ * This function draws a nice little square on the painter p.
+ * The square is suited to contain a a face value as printed
+ * by drawDiceFace(...).
+ */
+void KBgBoardHome::drawDiceFrame(QPainter *p, int col, int num,
+ int xo, int yo, bool big, double sf) const
+{
+ p->setBrush(board->getCheckerColor(col));
+ p->setPen(board->getCheckerColor(col));
+ QRect r = diceRect(num, big, sf);
+ r.moveTopLeft(QPoint(xo+r.left(), yo+r.top()));
+ p->drawRoundRect(r, 20, 20);
+}
+
+/*
+ * If the event is left button we just store that. If the event is right
+ * button we ask the board to possibly display the popup menu.
+ */
+void KBgBoardCell::mousePressEvent(QMouseEvent *e)
+{
+ if (e->button() == RightButton)
+ board->showContextMenu();
+ else
+ mouseButton = e->button();
+}
+
+/*
+ * This function sets the short move mode of the board.
+ */
+void KBgBoard::setShortMoveMode(int m)
+{
+ switch (m) {
+ case SHORT_MOVE_NONE:
+ case SHORT_MOVE_SINGLE:
+ shortMoveMode = m;
+ break;
+ case SHORT_MOVE_DOUBLE:
+ default:
+ shortMoveMode = SHORT_MOVE_DOUBLE;
+ }
+}
+
+/*
+ * This function returns the currently selected short move mode.
+ */
+int KBgBoard::getShortMoveMode()
+{
+ return shortMoveMode;
+}
+
+/*
+ * This function checks if (a) the mouse event was a left button,
+ * (b) the parameter m equals the currently selected short move
+ * mode and (c) t a short move from this field is possible. If all
+ * tests are ok, the shortest possible move away from here is
+ * made.
+ */
+void KBgBoardCell::checkAndMakeShortMove(QMouseEvent *e, int m)
+{
+ if ((e->button() == LeftButton) &&
+ (board->getShortMoveMode() == m) &&
+ (dragPossible()) &&
+ (!board->getEditMode()))
+ makeShortMove();
+}
+
+/*
+ * This functions reacts on a double click.
+ */
+void KBgBoardCell::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ checkAndMakeShortMove(e, SHORT_MOVE_DOUBLE);
+}
+
+/*
+ * This function reacts on a double click. Note that the bar knows
+ * about two different double clicks: double the cube and make a
+ * short move.
+ */
+void KBgBoardBar::mouseDoubleClickEvent(QMouseEvent *e)
+{
+ QRect r = cubeRect(cellID == BAR_THEM ? CUBE_UPPER : CUBE_LOWER, true);
+ if (board->canDouble(US) &&
+ board->canDouble(THEM) && r.contains(e->pos())) {
+ if (board->getEditMode())
+ board->queryCube();
+ else
+ board->getDoubleCube(US);
+ return;
+ }
+ checkAndMakeShortMove(e, SHORT_MOVE_DOUBLE);
+}
+
+/*
+ * This is the destructor of the backgammon board. It frees
+ * all resources previously allocated.
+ */
+KBgBoard::~KBgBoard()
+{
+ restoreCursor();
+}
+
+/*
+ * This function draws dice and cube on the painter for a home cell.
+ * who may be either US or THEM.
+ */
+void KBgBoardHome::drawDiceAndCube(QPainter *p, int who, int xo, int yo,
+ double sf) const
+{
+ int col = ((who == THEM) ? -color : color);
+
+ /*
+ * draw the empty squares and then put the face value in there
+ */
+ for (int i = 0; i < 2; i++) {
+ drawDiceFrame(p,-col, i, xo, yo, true, sf);
+ drawDiceFrame(p, col, i, xo, yo, false, sf);
+ drawDiceFace(p,-col, i, who, xo, yo, sf);
+ }
+ /*
+ * if necessary draw the cube
+ */
+ if (board->canDouble(who) &&
+ !(board->canDouble(US) && board->canDouble(THEM)))
+ drawCube(p, who, xo, yo, sf);
+}
+
+/*
+ * This function determines whether a drag off this home is possible.
+ * This is only possible if there are checkers and edit mode is on.
+ */
+bool KBgBoardHome::dragPossible() const
+{
+ if (board->getEditMode())
+ return (pcs != 0);
+ return false;
+}
+
+/*
+ * This function determines whether a drag off this bar is possible.
+ * It checks in the follwoing order: (1) owner of this bar is the one
+ * whose turn it is now, (2) does the board allow moving right now is
+ * it in read-only mode?
+ */
+bool KBgBoardBar::dragPossible() const
+{
+ if (board->getEditMode())
+ return (pcs != 0);
+
+ switch(board->getTurn()) {
+ case US:
+ if (pcs*color <= 0) return false;
+ break;
+ case THEM:
+ if (pcs*color >= 0) return false;
+ break;
+ default:
+ return false;
+ }
+ return board->movingAllowed();
+}
+
+/*
+ * This function checks whether a checker can be moved away from
+ * this field. It first checks whether the owner of this field is
+ * the one whose turn to move it it, then it is checked whether
+ * the players bar is empty and finally it is checked if the board
+ * is in read-only mode.
+ */
+bool KBgBoardField::dragPossible() const
+{
+ if (board->getEditMode())
+ return (pcs != 0);
+
+ switch(board->getTurn()) {
+ case US:
+ if (pcs*color <= 0) return false;
+ break;
+ case THEM:
+ if (pcs*color >= 0) return false;
+ break;
+ default:
+ return false;
+ }
+ if (board->getOnBar(board->getTurn()))
+ return false;
+ return board->movingAllowed();
+}
+
+/*
+ * This function returns the current read-write flag of the board.
+ * If this returns true the board doesn't accept user input. If
+ * allowmoving is true we will accept user events.
+ */
+bool KBgBoard::movingAllowed() const
+{
+ return allowmoving;
+}
+
+/*
+ * This function sets the read-write or read-only flag of the
+ * board. See also movingAllowed().
+ */
+void KBgBoard::allowMoving(const bool fl)
+{
+ allowmoving = fl;
+}
+
+/*
+ * This function returns the current pip count of the player w.
+ */
+int KBgBoard::getPipCount(const int& w) const
+{
+ if (!computePipCount || (w != US && w != THEM))
+ return -1;
+ int pip = 25*abs(onbar[w]);
+ int d = ((w == US) ? 1 : -1);
+ for (int i = 1; i < 25; i++) {
+ if (d*board[i]*color > 0)
+ pip += ((d*direction < 0) ?
+ i*abs(board[i]) :
+ (25 - i)*abs(board[i]));
+ }
+ return pip;
+}
+
+/*
+ * This function handles double clicks on homes. It will ignore
+ * double clicks on the real home and only handle the ones on the
+ * "other" home - the one with the dice. It will propagate the event
+ * only if the the click happened within the boundaries of a
+ * dice or the cube.
+ */
+void KBgBoardHome::mouseDoubleClickEvent(QMouseEvent * e)
+{
+ if (e->button() != LeftButton)
+ return;
+ /*
+ * Check whether this is the bookkeeping home...
+ */
+ if ((cellID == HOME_US_LEFT && direction < 0) ||
+ (cellID == HOME_US_RIGHT && direction > 0) ||
+ (cellID == HOME_THEM_LEFT && direction < 0) ||
+ (cellID == HOME_THEM_RIGHT && direction > 0)) {
+
+ int w = ((cellID == HOME_US_LEFT || cellID == HOME_US_RIGHT) ?
+ US : THEM);
+ for (int i = 0; i < 2; ++i) {
+ QRect r = diceRect(i, true);
+ if (r.contains(e->pos())) {
+ if (board->getEditMode()) {
+
+ KBgBoardQDice *dlg = new KBgBoardQDice();
+ if (dlg->exec()) {
+ KBgStatus *st = new KBgStatus();
+ board->getState(st);
+ st->setDice(w, 0, dlg->getDice(0));
+ st->setDice(w, 1, dlg->getDice(1));
+ st->setDice(((w == US) ? THEM : US), 0, 0);
+ st->setDice(((w == US) ? THEM : US), 1, 0);
+ board->setState(*st); // JENS
+ delete st;
+ }
+ delete dlg;
+
+ } else
+ board->getRollDice(w);
+ return;
+ }
+ }
+ if (board->canDouble(w) &&
+ !(board->canDouble(US) && board->canDouble(THEM))) {
+ QRect r = cubeRect(w, true);
+ if (r.contains(e->pos()))
+ if (board->getEditMode())
+ board->queryCube();
+ else
+ board->getDoubleCube(w);
+ }
+ }
+}
+
+/*
+ * This function determines if a checker can be dropped on this field.
+ * It checks whether the field is already owned, empty or contains
+ * only one opponents piece. Then the dice are checked.
+ */
+bool KBgBoardField::dropPossible(int fromCellID, int newColor)
+{
+ if ((newColor*pcs > 0) || (pcs == 0) || (abs(pcs) == 1))
+ // editMode is checked in diceAllowMove(...)
+ return board->diceAllowMove(fromCellID, cellID);
+ return false;
+}
+
+/*
+ * This function determines if a checker can be dropped on this field.
+ * Drops on the bar are never possible.
+ */
+bool KBgBoardBar::dropPossible(int fromCellID, int newColor)
+{
+ if (!board->getEditMode())
+ return false;
+
+ if (newColor*pcs > 0)
+ return true;
+ if ((cellID == BAR_US) && (board->getTurn() == US))
+ return true;
+ if ((cellID == BAR_THEM) && (board->getTurn() == THEM))
+ return true;
+
+ return (fromCellID == -12345); // always false
+}
+
+/*
+ * This function checks if the current player can move a checker off.
+ * Check if we can move a piece off. This obviously only works if there
+ * are no pieces on the bar and all remaining pieces are in the home
+ * board. This does not check the dice and it doesn't work for multiple
+ * moves that start outside the home.
+ */
+bool KBgBoard::moveOffPossible() const
+{
+ if (getEditMode())
+ return true;
+
+ int w = getTurn();
+ int d = ((w == THEM) ? -1 : 1);
+ if (onbar[w] == 0 && d*direction > 0) {
+ for (int i = 1; i < 19; ++i) {
+ if (d*color*board[i] > 0) return false;
+ }
+ return true;
+
+ } else if (onbar[w] == 0 && d*direction < 0) {
+ for (int i = 24; i > 6; --i) {
+ if (d*color*board[i] > 0) return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+/*
+ * This function tries to determine the field cell under the point p.
+ * The point needs to be in board coordinates and the function returns
+ * a pointer to the cell or NULL if there is no cell under the point.
+ */
+
+KBgBoardCell* KBgBoard::getCellByPos(const QPoint& p) const
+{
+ for (int i = 0; i < 30; ++i) {
+ if (cells[i]->rect().contains(cells[i]->mapFromParent(p)))
+ return cells[i];
+ }
+ return NULL;
+}
+
+/*
+ * This function takes a board number (1 to 24 and 0 or 25 depending on the
+ * direction) or a cell ID and returns a pointer to the corresponding cell.
+ * If the cell cannot be found it returns NULL.
+ */
+KBgBoardCell* KBgBoard::getCell(int num)
+{
+ switch (num) {
+ case BAR_US:
+ return (KBgBoardCell *)cells[22];
+ case BAR_THEM:
+ return (KBgBoardCell *)cells[ 7];
+ case HOME_THEM_LEFT:
+ return (KBgBoardCell *)cells[ 0];
+ case HOME_THEM_RIGHT:
+ return (KBgBoardCell *)cells[14];
+ case HOME_US_LEFT:
+ return (KBgBoardCell *)cells[15];
+ case HOME_US_RIGHT:
+ return (KBgBoardCell *)cells[29];
+ default:
+ int cell;
+ if (num < 0 || num > 25)
+ return NULL;
+ else if (num < 7)
+ cell = ((direction > 0) ? num : 29 - num);
+ else if (num < 13)
+ cell = ((direction > 0) ? num + 1 : 28 - num);
+ else if (num < 19)
+ cell = ((direction > 0) ? 41 - num : num - 12);
+ else
+ cell = ((direction > 0) ? 40 - num : num - 11);
+ return (KBgBoardCell *)cells[cell];
+ }
+}
+
+/*
+ * This function translates a field ID to the field number or just
+ * returns the ID for bars and homes.
+ */
+int KBgBoard::IDtoNum(const int ID) const
+{
+ if (ID > 0 && ID < 25) {
+ if (ID < 13)
+ return ((direction > 0) ? ID : 12 + ID);
+ else
+ return ((direction > 0) ? 37 - ID : 25 - ID);
+ }
+ return ID;
+}
+
+/*
+ * This function takes a checker from the cell if possible. It also
+ * updates the bookkeeping of the board and redraws itself.
+ */
+bool KBgBoardCell::getPiece()
+{
+ if (pcs != 0) {
+ ((pcs > 0) ? --pcs : ++pcs);
+ stateChanged = true;
+ refresh();
+ board->updateField(getNumber(), pcs);
+ return true;
+ }
+ return false;
+}
+
+/*
+ * This function stores the current cursor and replaces it with the
+ * supplied one c.
+ */
+void KBgBoard::replaceCursor(const QCursor& c)
+{
+ if (savedCursor)
+ delete savedCursor;
+ savedCursor = new QCursor(cursor());
+ setCursor(c);
+}
+
+/*
+ * This function restores the previously set cursor to the stored one.
+ */
+void KBgBoard::restoreCursor()
+{
+ if (savedCursor) {
+ setCursor(*savedCursor);
+ delete savedCursor;
+ savedCursor = NULL;
+ }
+}
+
+/*
+ * This function puts a checker of color newColor on the cell. It handles
+ * all necessary updates including the kicking. It will however not properly
+ * handle illegal moves!
+ */
+void KBgBoardCell::putPiece(int newColor)
+{
+ if (newColor*pcs > 0) {
+ pcs > 0 ? ++pcs : --pcs;
+ } else if (pcs == 0) {
+ newColor > 0 ? pcs = 1 : pcs = -1;
+ } else if (newColor*pcs < 0) {
+ board->kickedPiece();
+ newColor > 0 ? pcs = 1 : pcs = -1;
+ }
+ stateChanged = true;
+ refresh();
+ board->updateField(getNumber(), pcs);
+ board->sendMove();
+}
+
+/*
+ * This function handles mouse release events. It is important to know that
+ * the cell where the first mousePressEvent occurred receives the release event.
+ * The release event marks the end of a drag or a single click short move.
+ */
+void KBgBoardCell::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (dragInProgress) {
+
+ KBgBoardCell *dest = board->getCellByPos
+ (mapToParent(e->pos()));
+ board->restoreCursor();
+ if ((dest != NULL) && (dest->dropPossible(cellID, ((board->getTurn() == US) ?
+ color : -color)))) {
+ if (!board->getEditMode())
+ board->makeMove(getNumber(), dest->getNumber());
+ dest->putPiece(((board->getTurn() == US) ? color : -color));
+ } else {
+ putPiece(((board->getTurn() == US) ? color : -color));
+ }
+ dragInProgress = false;
+
+ } else {
+
+ checkAndMakeShortMove(e, SHORT_MOVE_SINGLE);
+
+ }
+}
+
+/*
+ * This is the destructor of the home cells. It doesn't do anything.
+ */
+KBgBoardHome::~KBgBoardHome()
+{
+ // nothing
+}
+
+/*
+ * This is the destructor of the bar cells. It doesn't do anything.
+ */
+KBgBoardBar::~KBgBoardBar()
+{
+ // nothing
+}
+
+/*
+ * This is the destructor of regular fields. It doesn't do anything.
+ */
+KBgBoardField::~KBgBoardField()
+{
+ // nothing
+}
+
+/*
+ * This is the constructor of the bars. It calls the base class' constructor
+ * and defines the QWhatsThis string.
+ */
+KBgBoardBar::KBgBoardBar(QWidget * parent, int numID)
+ : KBgBoardCell(parent, numID)
+{
+ QWhatsThis::add(this, i18n("This is the bar of the backgammon board.\n\n"
+ "Checkers that have been kicked from the board are put "
+ "on the bar and remain there until they can be put back "
+ "on the board. Checkers can be moved by dragging them to "
+ "their destination or by using the 'short move' feature.\n\n"
+ "If the cube hasn't been doubled yet and if it can be used, "
+ "its face shows 64 and if the cube can be doubled, double "
+ "clicking it will do so."));
+}
+
+/*
+ * This is the constructor of regular fields. It calls the base class' constructor
+ * and defines the QWhatsThis string.
+ */
+KBgBoardField::KBgBoardField(QWidget * parent, int numID)
+ : KBgBoardCell(parent, numID)
+{
+ QWhatsThis::add(this, i18n("This is a regular field of the backgammon board.\n\n"
+ "Checkers can be placed on this field and if the current state "
+ "of the game and the dice permit this, they can be moved by "
+ "dragging them to their destination or by using the 'short "
+ "move' feature."));
+}
+
+/*
+ * This is the constructor of the homes. It calls the base class' constructor
+ * and defines the QWhatsThis string.
+ */
+KBgBoardHome::KBgBoardHome(QWidget * parent, int numID)
+ : KBgBoardCell(parent, numID)
+{
+ QWhatsThis::add(this, i18n("This part of the backgammon board is the home.\n\n"
+ "Depending on the direction of the game, one of the homes "
+ "contains the dice and the other one contains checkers that "
+ "have been moved off the board. Checkers can never be moved "
+ "away from the home. If this home contains the dice and the "
+ "current state of the game permits this, double clicking on "
+ "the dice will roll them. Moreover, the cube might be placed "
+ "on the home bar and if it can be doubled, double clicking it "
+ "will do so."));
+ savedDice[0] = -1;
+ savedDice[1] = -1;
+}
+
+/*
+ * This function updates the number of checkers on the bar and also updates
+ * the cell if the cube has changed (this is more often than necessary...)
+ */
+void KBgBoardBar::cellUpdate(const int p, const bool cubechanged)
+{
+ stateChanged = (cubechanged || colorChanged);
+ if (pcs != p) {
+ stateChanged = true;
+ pcs = p;
+ }
+}
+
+/*
+ * This function updates the number of checkers on the field.
+ */
+void KBgBoardField::cellUpdate(const int p, const bool cubechanged)
+{
+ if (p != pcs) {
+ pcs = p;
+ stateChanged = true;
+ }
+ bool f = stateChanged; // useless, avoids compiler warning
+ stateChanged = cubechanged;
+ stateChanged = (f || colorChanged);
+}
+
+/*
+ * This function updates the number of checkers on the home if it
+ * actually contains checkers. It will also redraw if the cube or dice
+ * have changed.
+ */
+void KBgBoardHome::cellUpdate(const int p, const bool cubechanged)
+{
+ if ((cellID == HOME_THEM_LEFT && direction > 0) ||
+ (cellID == HOME_THEM_RIGHT && direction < 0) ||
+ (cellID == HOME_US_LEFT && direction > 0) ||
+ (cellID == HOME_US_RIGHT && direction < 0)) {
+
+ if (pcs != p) {
+ pcs = p;
+ stateChanged = true;
+ }
+
+ } else {
+
+ int who = ((cellID == HOME_THEM_LEFT || cellID == HOME_THEM_RIGHT) ? THEM : US);
+
+ stateChanged = ((savedDice[0] != board->getDice(who, 0)) ||
+ (savedDice[1] != board->getDice(who, 1)));
+
+ savedDice[0] = board->getDice(who, 0);
+ savedDice[1] = board->getDice(who, 1);
+
+ stateChanged = (stateChanged || cubechanged || colorChanged || directionChanged);
+ }
+}
+
+/*
+ * This function returns whose players turn it is.
+ */
+int KBgBoard::getTurn() const
+{
+ if (getEditMode())
+ return ((storedTurn*color > 0) ? US : THEM);
+
+ if (getDice(US , 0) != 0 && getDice(US , 1) != 0)
+ return US;
+ if (getDice(THEM, 0) != 0 && getDice(THEM, 1) != 0)
+ return THEM;
+ return -1;
+}
+
+/*
+ * This is the constructor of the basic cells. It initializes the cell
+ * to a sane state and connects a signal to the board.
+ */
+KBgBoardCell::KBgBoardCell(QWidget * parent, int numID)
+ : QLabel(parent)
+{
+ board = (KBgBoard *)parent;
+
+ direction = +1;
+ color = -1;
+ pcs = 0;
+ cellID = numID;
+ stateChanged = false;
+ colorChanged = false;
+ directionChanged = false;
+ mouseButton = NoButton;
+ dragInProgress = false;
+
+ connect(parent, SIGNAL(finishedUpdate()), this, SLOT(refresh()));
+}
+
+/*
+ * This is the destructor of the cells. It doesn't do anything.
+ */
+KBgBoardCell::~KBgBoardCell()
+{
+ // nothing
+}
+
+/*
+ * This function returns the color of the checkers on this cell.
+ */
+int KBgBoardCell::getCellColor()
+{
+ return ((pcs < 0) ? -1 : +1);
+}
+
+/*
+ * This function updates the basic board settings color and direction
+ * and signals a redraw if necessary.
+ */
+void KBgBoardCell::statusUpdate(int dir, int col)
+{
+ if (direction != dir || color != col) {
+ colorChanged = (color != col);
+ directionChanged = (direction != dir);
+ color = col;
+ direction = dir;
+ stateChanged = true;
+ }
+}
+
+/*
+ * This function refreshes the content of the cell if necessary.
+ */
+void KBgBoardCell::refresh()
+{
+ if (stateChanged) {
+ update();
+ stateChanged = false;
+ colorChanged = false;
+ directionChanged = false;
+ }
+}
+
+/*
+ * This function returns the board number of this cell as given by the board.
+ */
+int KBgBoardCell::getNumber() const
+{
+ return board->IDtoNum(cellID);
+}
+
+/*
+ * This function returns the number of checkers of player who on the bar.
+ */
+int KBgBoard::getOnBar(int who) const
+{
+ return ((who == US || who == THEM) ? onbar[who] : 0);
+}
+
+/*
+ * This function returns the face value of the n-th dice of player w
+ */
+int KBgBoard::getDice( int w, int n ) const
+{
+ return (((w == US || w == THEM) && (n == 0 || n == 1)) ? dice[w][n] : 0);
+}
+
+/*
+ * This function returns the current cube value.
+ */
+int KBgBoard::getCube() const
+{
+ return cube;
+}
+
+/*
+ * This function updates the stored number of pieces on field f to v.
+ */
+void KBgBoard::updateField(int f, int v)
+{
+ switch (f) {
+ case BAR_US:
+ case BAR_THEM:
+ onbar[((f == BAR_US) ? US : THEM)] = v;
+ break;
+ case HOME_US_RIGHT:
+ case HOME_US_LEFT:
+ onhome[US] = v;
+ break;
+ case HOME_THEM_RIGHT:
+ case HOME_THEM_LEFT:
+ onhome[THEM] = v;
+ break;
+ default:
+ if (0 < f && f < 25)
+ board[f] = v;
+ break;
+ }
+}
+
+/*
+ * This function displays the context menu our parent may have given us
+ */
+void KBgBoard::showContextMenu()
+{
+ if (contextMenu) contextMenu->popup(QCursor::pos());
+}
+
+/*
+ * This function determines if the player who can double.
+ */
+bool KBgBoard::canDouble(int who) const
+{
+ return ((who == US || who == THEM) ? maydouble[who] : false);
+}
+
+/*
+ * This function is a simple utility for makeMove. It takes care
+ * of all the bookeeeping needed for a move.
+ */
+int KBgBoard::makeMoveHelper(int si, int sf, int delta)
+{
+ moveHistory.append(new KBgBoardMove(si, sf, abs(delta)));
+ --possMoves[abs(delta)];
+ return delta;
+}
+
+/*
+ * This function makes a move from src to dest for the current player.
+ * It can handle illegal moves but the move should have been checked.
+ */
+void KBgBoard::makeMove(int src, int dest)
+{
+ int m[4];
+ int l;
+
+ int d = direction*((getTurn() == US) ? +1 : -1);
+
+ if (src == BAR_US || src == BAR_THEM ) {
+
+ int start = ((d > 0) ? 0 : 25);
+ l = checkMultiMove(start, dest, m);
+ moveHistory.append(new KBgBoardMove(src, start+d*m[0], m[0]));
+ src = start+d*m[0];
+ --possMoves[m[0]];
+ for (int i = 1; i < l; i++)
+ src += makeMoveHelper(src, src+d*m[i], d*m[i]);
+
+ } else if (0 < src && src < 25 && 0 < dest && dest < 25) {
+
+ l = checkMultiMove(src, dest, m);
+ for (int i = 0; i < l; i++)
+ src += makeMoveHelper(src, src+d*m[i], d*m[i]);
+
+ } else {
+
+ int s = src;
+ int final = ((d > 0) ? 25 : 0);
+ while (((l = checkMultiMove(s, final, m)) == 0) && (0 < s && s < 25))
+ s -= d;
+
+ for (int i = 0; i < l-1; i++)
+ src += makeMoveHelper(src, src+d*m[i], d*m[i]);
+
+ moveHistory.append(new KBgBoardMove(src, dest, ((d > 0) ? 25 - src : src)));
+ --possMoves[m[l-1]];
+
+ }
+}
+
+/*
+ * This function checks if there is any possibility (based on the dice)
+ * to move from src to dest. It takes the ownership of the intermediate
+ * fields into account. The function returns the number of steps necessary
+ * to perform the move (or 0 if the move is not possible) and the actual
+ * dice values used for the steps are returned in the array m.
+ *
+ * The values src and dest are expected to be in board coordinates and the
+ * homes and/or bars should already be mapped to the corresponding values
+ * 0 and 25 (based onb direction and whose turn it is).
+ */
+int KBgBoard::checkMultiMove(int src, int dest, int m[4])
+{
+ m[0] = 0; m[1] = 0; m[2] = 0; m[3] = 0;
+
+ int mcolor = ((getTurn() == US) ? color : -color);
+ int d = ((src > dest) ? -1 : 1);
+
+ /*
+ * These are very easy special cases: move length is 0 or
+ * player cannot move to the destination field.
+ */
+ if ((src == dest) || (mcolor*board[dest] < -1)) return 0;
+
+ int diceToUse[4];
+ int dice = 0;
+ /*
+ * Get the available step sizes for this move
+ */
+ for (int i = 1; i < 7; i++) {
+ for (int j = 0; j < possMoves[i]; j++) {
+ diceToUse[dice++] = i;
+ /*
+ * If this happens there is something wrong
+ */
+ if (dice > 4) return 0;
+ }
+ }
+ /*
+ * And start all possible combination of dices.
+ */
+ switch (dice) {
+ case 4: if (src+4*d*diceToUse[0] == dest) {
+ if ((mcolor*board[src+1*d*diceToUse[0]] >= 0) &&
+ (mcolor*board[src+2*d*diceToUse[0]] >= 0) &&
+ (mcolor*board[src+3*d*diceToUse[0]] >= 0)) {
+ m[0] = m[1] = m[2] = m[3] = diceToUse[0];
+ return 4;
+ }
+ }
+ case 3: if (src+3*d*diceToUse[0] == dest) {
+ if ((mcolor*board[src+1*d*diceToUse[0]] >= 0) &&
+ (mcolor*board[src+2*d*diceToUse[0]] >= 0)) {
+ m[0] = m[1] = m[2] = diceToUse[0];
+ return 3;
+ }
+ }
+ case 2: if ((src+d*(diceToUse[0]+diceToUse[1])) == dest) {
+ if (mcolor*board[src+d*diceToUse[0]] >= 0) {
+ m[0] = diceToUse[0];
+ m[1] = diceToUse[1];
+ return 2;
+ }
+ if (mcolor*board[src+d*diceToUse[1]] >= 0) {
+ m[0] = diceToUse[1];
+ m[1] = diceToUse[0];
+ return 2;
+ }
+ }
+ case 1: if (abs(src-dest) < 7 && possMoves[abs(src-dest)] > 0) {
+ m[0] = abs(src-dest);
+ return 1;
+ }
+ default: return 0;
+ }
+}
+
+/*
+ * This function determines if a checker can be dropped on this home field.
+ * It first checks whether this is the proper of the four home fields (belongs
+ * to the player and not the one with dice and cube). Then we check if the move
+ * itself is possible.
+ */
+bool KBgBoardHome::dropPossible(int fromCellID, int newColor)
+{
+ if ((cellID==HOME_US_LEFT && board->getTurn() == US && direction > 0) ||
+ (cellID==HOME_THEM_LEFT && board->getTurn() == THEM && direction > 0) ||
+ (cellID==HOME_US_RIGHT && board->getTurn() == US && direction < 0) ||
+ (cellID==HOME_THEM_RIGHT && board->getTurn() == THEM && direction < 0))
+ return (board->moveOffPossible() &&
+ board->diceAllowMove(fromCellID, cellID));
+ return (newColor == -12345); // always false
+}
+
+/*
+ * This function is a simple boolean interface to checkMultiMove.
+ * It takes car of directions and bar/home mappings. If necessary
+ * it also handles the case of bearing off.
+ */
+bool KBgBoard::diceAllowMove(int src, int dest)
+{
+ int m[4];
+ int w = getTurn();
+ int k = ((w == US) ? +1 : -1);
+ int t = ((k*direction > 0) ? 25 : 0);
+ int d = ((k*direction > 0) ? +1 : -1);
+
+ if (getEditMode())
+ return true;
+
+ if ((w == US && src == BAR_US) || (w == THEM && src == BAR_THEM)) {
+ /*
+ * Move comes from a bar. Hence it has to end on a field
+ * and not on bars or homes. If there are checkers left
+ * on the bar we don't accept multi moves.
+ */
+ if (0 < dest && dest < 25) {
+ int r = checkMultiMove((k*direction > 0) ? 0 : 25,
+ IDtoNum(dest), m);
+ return((abs(onbar[w]) == 0) ? (r != 0) : (r == 1));
+ } else {
+ return false;
+ }
+ } else if (0 < dest && dest < 25 && 0 < src && src < 25) {
+ /*
+ * Move from a field to a field
+ */
+ if (direction*k*(IDtoNum(dest)-IDtoNum(src)) > 0) {
+ return(checkMultiMove(IDtoNum(src), IDtoNum(dest), m));
+ } else {
+ return false;
+ }
+ } else {
+ /*
+ * Move from a field on the home. First we try exact dice.
+ */
+ if (checkMultiMove(IDtoNum(src), t, m) > 0) return true;
+
+ /*
+ * Then maybe we could bear the checker off ?
+ */
+ int i = IDtoNum(src);
+ while (0 < i && i < 25) {
+ i -= d;
+ if (k*color*board[i] > 0) return false;
+ }
+
+ /*
+ * Indeed we are bearing off. So find the highest dice and use it.
+ * Start from all the way back to catch double 6 from the start.
+ */
+ int j = 24;
+ while (checkMultiMove(t-d*j, t, m) == 0 && j > 0) {--j;}
+ return (j >= t-d*IDtoNum(src));
+ }
+ return false;
+}
+
+/*
+ * This is the most important of all members of the board class. It takes
+ * a single board status object and initializes the internal status.
+ */
+void KBgBoard::setState(const KBgStatus &st)
+{
+ color = st.color();
+ direction = st.direction();
+
+ cubechanged = (cube != abs(st.cube()));
+ cube = abs(st.cube());
+ maydouble[US ] = (st.cube(US ) > 0);
+ maydouble[THEM] = (st.cube(THEM) > 0);
+
+ for (int i = 0; i < 30; i++)
+ cells[i]->statusUpdate(direction, color);
+
+ for (int i = 1; i < 25; ++i)
+ board[i] = st.board(i);
+
+ onbar[US ] = st.bar(US );
+ onbar[THEM] = st.bar(THEM);
+
+ onhome[US] = st.home(US );
+ onhome[THEM] = st.home(THEM);
+
+ dice[US ][0] = st.dice(US , 0);
+ dice[US ][1] = st.dice(US , 1);
+ dice[THEM][0] = st.dice(THEM, 0);
+ dice[THEM][1] = st.dice(THEM, 1);
+
+ for (int i = 0; i < 7; ++i)
+ possMoves[i] = 0;
+
+ int w = getTurn();
+ if (getEditMode())
+ w = ((dice[US][0] && dice[US][1]) ? US : THEM);
+
+ if (w == US || w == THEM) {
+ ++possMoves[dice[w][0]];
+ ++possMoves[dice[w][1]];
+ if (dice[w][0] == dice[w][1])
+ possMoves[dice[w][0]] *= 2;
+ }
+
+ board[ 0] = 0;
+ board[25] = 0;
+ for (int i=1; i<25; ++i)
+ (getCell(i))->cellUpdate(board[i]);
+
+ (getCell(BAR_US ))->cellUpdate(st.bar(US ), cubechanged);
+ (getCell(BAR_THEM))->cellUpdate(st.bar(THEM), cubechanged);
+
+ (getCell(HOME_US_LEFT ))->cellUpdate(st.home(US ), cubechanged);
+ (getCell(HOME_US_RIGHT ))->cellUpdate(st.home(US ), cubechanged);
+ (getCell(HOME_THEM_LEFT ))->cellUpdate(st.home(THEM), cubechanged);
+ (getCell(HOME_THEM_RIGHT))->cellUpdate(st.home(THEM), cubechanged);
+
+ moveHistory.clear();
+ redoHistory.clear();
+
+ emit finishedUpdate();
+}
+
+/*
+ * This function starts a drag from this cell if possible. It asks the board to
+ * change the mouse pointer and takes a checker away from this cell.
+ */
+void KBgBoardCell::mouseMoveEvent(QMouseEvent *)
+{
+ if ((mouseButton == LeftButton) && dragPossible()) {
+ dragInProgress = true;
+ QRect cr(0, 0, 1+getCheckerDiameter(), 1+getCheckerDiameter());
+ cr.moveBottomLeft(rect().bottomLeft());
+ QPixmap pix(cr.size());
+ QPainter tmp;
+ pix.fill(this, cr.topLeft());
+ tmp.begin(&pix);
+ board->drawSimpleChecker(&tmp, 0, 0, pcs, getCheckerDiameter());
+ tmp.end();
+ pix.setMask(pix.createHeuristicMask());
+ QBitmap mask = *(pix.mask());
+ QBitmap newCursor;
+ newCursor = pix;
+ board->replaceCursor(QCursor(newCursor, mask));
+ if (board->getEditMode())
+ board->storeTurn(pcs);
+ getPiece();
+ }
+ mouseButton = NoButton;
+}
+
+/*
+ * This function draws a checker on the painter p. It is painted
+ * in the ractangle with the upper left corner (x,y) and has a
+ * maximum diameter of diam. This checker has only two colors and
+ * as such it is suited for the mouse cursor and printing.
+ */
+void KBgBoard::drawSimpleChecker(QPainter *p, int x, int y, int pcs,
+ int diam) const
+{
+ p->setBrush(getCheckerColor(pcs));
+ p->setPen(getCheckerColor(pcs));
+ p->drawEllipse(x+1, y+0, diam-0, diam-0);
+ p->setBrush(getCheckerColor(-pcs));
+ p->setPen(getCheckerColor(-pcs));
+ p->drawEllipse(x+2, y+1, diam-2, diam-2);
+ p->setBrush(getCheckerColor(pcs));
+ p->setPen(getCheckerColor(pcs));
+ p->drawEllipse(x+3, y+2, diam-4, diam-4);
+}
+
+/*
+ * This function draws an anti-aliased checker on the painter p. It
+ * is painted in the ractangle with the upper left corner (x,y) and
+ * has a diameter of diam. col indicates the color of the cell this
+ * checker is painted on. Special values for col are 0 and 100 that
+ * indicate that the checker is stacked (bars and homes) or stacked
+ * on a field respectively. upper indicates whether the checker is
+ * in the upper half of the board or not.
+ */
+void KBgBoard::drawChecker(QPainter *p, int x, int y, int pcs, int diam,
+ int col, bool upper) const
+{
+ drawCircle(p, x, y, pcs, diam , col, upper, true );
+ drawCircle(p, x+1, y+1,-pcs, diam-2, col, upper, false);
+ drawCircle(p, x+2, y+2, pcs, diam-4, col, upper, false);
+}
+
+/*
+ * This function draws checkers on the painter *p. They overlap so that
+ * up to fifteen checkers fit on the cell. This is used by homes and
+ * bars.
+ */
+void KBgBoardCell::drawOverlappingCheckers(QPainter *p, int xo, int yo,
+ double sf) const
+{
+ int d = getCheckerDiameter();
+ bool upper =
+ cellID == HOME_THEM_LEFT ||
+ cellID == HOME_THEM_RIGHT ||
+ cellID == BAR_THEM;
+ double xp = xo + sf*((width()-d-1)/2);
+ double ra = sf*d;
+ for (int i = 0; i < abs(pcs); ++i) {
+ double yp = yo + (upper ? 1+i*sf*height()/25.0 :
+ sf*(height()-d-i*height()/25.0));
+ board->drawChecker(p, xp, yp, pcs, ra, 0, upper);
+ }
+}
+
+/*
+ * This function paints the content of a regular cell on the painter p.
+ * It does so by first drawing a triangle (depending on whether we draw
+ * on the screen or not this will be antialiased). Then on top of that
+ * we draw the field number in inverse color. Finally we draw all the
+ * checkers in such a way that always five are in one level and the next
+ * level is slightly shifted.
+ */
+void KBgBoardField::paintCell(QPainter *p, int xo, int yo, double sf) const
+{
+ QColor color, alphaColor, background = backgroundColor();
+ bool printing = abs(xo)+abs(yo) > 0;
+
+ if (printing) {
+ /*
+ * This is the code for black and white printing on
+ * paper. This justs draws a triangle and surrounds
+ * it by a black triangle. Easy but works.
+ */
+ QPointArray pa(3);
+
+ color = (getNumber()%2 ? white : black);
+
+ if (cellID < 13) {
+ pa.setPoint( 0, xo , yo );
+ pa.setPoint( 1, xo + sf*width()/2, yo + 0.9*sf*height());
+ pa.setPoint( 2, xo + sf*width() , yo );
+ } else {
+ pa.setPoint( 0, xo , yo + sf*(height()-1));
+ pa.setPoint( 1, xo + sf*width()/2, yo + 0.1*sf*height());
+ pa.setPoint( 2, xo + sf*width() , yo + sf*(height()-1));
+ }
+
+ p->setBrush(color);
+ p->setPen(color);
+ p->drawPolygon(pa);
+
+ p->setBrush(black);
+ p->setPen(black);
+ p->drawPolyline(pa);
+
+ } else {
+ /*
+ * This is the code for antialiased triangles. This code has
+ * been written by Bo Thorsen.
+ */
+ color = board->getCheckerColor(getNumber()%2-1);
+
+ int topX, topY, bottomX1, bottomX2, bottomY, incrY;
+ topX = xo + (int)(sf*width()/2.0);
+ bottomX1 = xo;
+ bottomX2 = xo + (int)(sf*width());
+ if (cellID < 13) {
+ topY = yo + (int)(0.9*sf*height());
+ bottomY = yo;
+ incrY = 1;
+ } else {
+ topY = yo + (int)(0.1*sf*height());
+ bottomY = yo + (int)(sf*height());
+ incrY = -1;
+ }
+
+ float x1 = bottomX1, x2 = bottomX2;
+ float dx1 = (float)(topX-bottomX1) / (topY-bottomY);
+ float dx2 = (float)(topX-bottomX2) / (topY-bottomY);
+ if (dx1 < 0) dx1 = -dx1;
+ if (dx2 < 0) dx2 = -dx2;
+
+ p->setPen( color );
+ p->drawLine(bottomX1, bottomY, bottomX2, bottomY);
+ x1 += dx1;
+ x2 -= dx2;
+
+ /*
+ * The scaling factor (0.99) cuts off the top op the points
+ */
+ for (int y=bottomY; x1 < x2*0.99; y+=incrY) {
+ int ix1 = (int)x1, ix2 = (int)x2;
+ float a1 = x1 - ix1, a2 = x2 - ix2;
+
+ /*
+ * This is a simple linear interpolation between
+ * the two colors
+ */
+ int red1 = (int)
+ ((1-a1)*color.red() + a1*background.red());
+ int green1 = (int)
+ ((1-a1)*color.green() + a1*background.green());
+ int blue1 = (int)
+ ((1-a1)*color.blue() + a1*background.blue());
+ int red2 = (int)
+ (a2*color.red() + (1-a2)*background.red());
+ int green2 = (int)
+ (a2*color.green() + (1-a2)*background.green());
+ int blue2 = (int)
+ (a2*color.blue() + (1-a2)*background.blue());
+
+ /*
+ * Draw the antialiasing pixels
+ */
+ alphaColor.setRgb(red1, green1, blue1);
+ p->setPen(alphaColor);
+ p->drawPoint(ix1, y);
+ alphaColor.setRgb(red2, green2, blue2);
+ p->setPen(alphaColor);
+ p->drawPoint(ix2, y);
+
+ ix1++;
+ ix2--;
+ x1 += dx1;
+ x2 -= dx2;
+
+ if (ix1 <= ix2 && x1 < x2*0.99) {
+ /*
+ * Draw the line
+ */
+ p->setPen(color);
+ p->drawLine(ix1, y, ix2, y);
+ }
+ }
+ }
+
+ /*
+ * Print the field number in inverted color
+ */
+ color = board->getCheckerColor((1+getNumber())%2-1);
+
+ p->setBrush(color);
+ p->setPen(color);
+
+ QString t;
+ t.setNum(getNumber());
+
+ p->setFont(board->getFont());
+ int textHeight = QFontMetrics(p->font()).height();
+ p->drawText(xo, yo+((cellID < 13) ? 5 : height()-5-textHeight),
+ width()*sf, textHeight, AlignCenter, t);
+
+ /*
+ * Put the checkers on the field.
+ */
+ int d = getCheckerDiameter();
+ double yp, xp = xo + sf*((width()-d-1)/2);
+ double ra = sf*d;
+ bool upper = cellID < 13;
+ int col = (getNumber()%2) ? 1 : -1;
+
+ for (int i = 0; i < abs(pcs); ++i) {
+ /*
+ * There is hard work in these formulas. Unless you have
+ * tried _ALL_ possible windowsizes: don't touch!
+ */
+ yp = yo + (upper ? sf*((i%5)+(i/5)/4.0)*(d-1) :
+ sf*(height()-((1+i%5)*d)-int(i/5)*0.25*d)-1);
+ if (printing) {
+ board->drawSimpleChecker(p, xp, yp, pcs, ra);
+ } else {
+ board->drawChecker(p, xp, yp, pcs, ra,
+ ((i < 5) ? col : 100), upper);
+ }
+ }
+
+ /*
+ * Finally draw the horizontal boundaries
+ */
+ KBgBoardCell::paintCell(p, xo, yo, sf);
+}
+
+/*
+ * This function draws an anti-aliased circle on the painter p. It is painted
+ * in the ractangle with the upper left corner (x,y) and has a maximum diameter
+ * of diam. col and upper are as in drawChecker(). outer indicates if this
+ * circle blends with the background. Note that this function needs knowledge
+ * about the triangles on the cells. This is long but it is just a big if
+ * construct.
+ */
+void KBgBoard::drawCircle(QPainter *p, int x, int y, int pcs, int diam,
+ int col, bool upper, bool outer) const
+{
+ QColor fColor = getCheckerColor(pcs);
+ QColor alphaColor;
+ QColor bColor;
+
+ int red, green, blue;
+ int rad = diam/2;
+ int xoff = 0;
+
+ float sn = 4;
+ float rs = 0.25*diam*diam;
+ float cf, a;
+
+ for (int ys = rad; ys >= 0; ys--) {
+ for (int xs = xoff; cf = 0, xs < rad; xs++) {
+
+ /*
+ * perform super-sample this pixel
+ */
+ for (int s1 = 0; s1 < sn; s1++)
+ for (int s2 = 0; s2 < sn; s2++)
+ if ((rad-xs+s1/sn)*(rad-xs+s1/sn)+
+ (rad-ys+s2/sn)*(rad-ys+s2/sn) < rs)
+ cf += 1;
+ a = cf/sn/sn;
+
+ if (outer && (col == 0 || col == 100)) {
+
+ if (col == 0)
+ bColor = backgroundColor();
+ else
+ bColor = fColor;
+
+ red = (int)
+ ((1-a)*bColor.red()+a*fColor.red());
+ green = (int)
+ ((1-a)*bColor.green()+a*fColor.green());
+ blue = (int)
+ ((1-a)*bColor.blue()+a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ if (upper) {
+
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+
+ p->setBrush(fColor);
+ p->setPen(fColor);
+
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+
+ } else {
+
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+
+ p->setBrush(fColor);
+ p->setPen(fColor);
+
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+
+ }
+
+ } else if (outer) {
+
+ if (upper) {
+
+ bColor = getCheckerColor(col);
+
+ red = (int)((1-a)*bColor.red()+
+ a*fColor.red());
+ green = (int)((1-a)*bColor.green()+
+ a*fColor.green());
+ blue = (int)((1-a)*bColor.blue()+
+ a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+
+ bColor = backgroundColor();
+
+ red = (int)((1-a)*bColor.red()+
+ a*fColor.red());
+ green = (int)((1-a)*bColor.green()+
+ a*fColor.green());
+ blue = (int)((1-a)*bColor.blue()+
+ a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ if (x+xs < rad*(y+ys)/(0.45*height())) {
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+ }
+ if (x+xs<rad*(y+diam-ys)/(0.45*height())) {
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+ }
+
+ } else {
+
+ bColor = getCheckerColor(col);
+
+ red = (int)((1-a)*bColor.red()+
+ a*fColor.red());
+ green = (int)((1-a)*bColor.green()+
+ a*fColor.green());
+ blue = (int)((1-a)*bColor.blue()+
+ a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+
+ bColor = backgroundColor();
+
+ red = (int)((1-a)*bColor.red()+
+ a*fColor.red());
+ green = (int)((1-a)*bColor.green()+
+ a*fColor.green());
+ blue = (int)((1-a)*bColor.blue()+
+ a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ if (x+xs<rad*(0.5-(y+ys)/
+ (1.0*height()))/0.45) {
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+ }
+ if (x+xs < rad*(0.5-(y+diam-ys)/
+ (1.0*height()))/0.45) {
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+ }
+ }
+
+ } else {
+
+ bColor = getCheckerColor(-pcs);
+
+ red = (int)((1-a)*bColor.red()+
+ a*fColor.red());
+ green = (int)((1-a)*bColor.green()+
+ a*fColor.green());
+ blue = (int)((1-a)*bColor.blue()+
+ a*fColor.blue());
+
+ alphaColor.setRgb(red, green, blue);
+ p->setBrush(alphaColor);
+ p->setPen(alphaColor);
+
+ p->drawPoint(x+xs, y+ys);
+ p->drawPoint(x+diam-xs, y+ys);
+ p->drawPoint(x+xs, y+diam-ys);
+ p->drawPoint(x+diam-xs, y+diam-ys);
+
+ }
+
+ if (fabs(cf-sn*sn) < 0.0001) {
+
+ p->moveTo(x+xs, y+ys);
+ p->lineTo(x+diam-xs, y+ys);
+ p->moveTo(x+xs, y+diam-ys);
+ p->lineTo(x+diam-xs, y+diam-ys);
+
+ xoff = xs;
+ break;
+
+ }
+ }
+ }
+}
+
+/*
+ * This function redoes a previously undone move
+ */
+void KBgBoard::redoMove()
+{
+ if (getEditMode())
+ return;
+
+ int w = getTurn();
+ int mcolor = ((w == US) ? color : -color);
+ KBgBoardMove *move = redoHistory.last();
+ if (move && (w == US || w == THEM)) {
+ /*
+ * Make changes at source
+ */
+ if (move->source() == BAR_US || move->source() == BAR_THEM) {
+ onbar[w] -= mcolor;
+ (getCell(move->source()))->cellUpdate(onbar[w], false);
+ } else {
+ board[move->source()] -= mcolor;
+ (getCell(move->source()))->cellUpdate(board[move->source()]);
+ }
+ /*
+ * Make changes at the destination
+ */
+ if ((move->destination() == HOME_THEM_LEFT ) || (move->destination() == HOME_THEM_RIGHT) ||
+ (move->destination() == HOME_US_LEFT ) || (move->destination() == HOME_US_RIGHT )) {
+ onhome[w] += mcolor;
+ (getCell(move->destination()))->cellUpdate(onhome[w], false);
+ } else {
+ board[move->destination()] += mcolor;
+ if (move->wasKicked()) {
+ board[move->destination()] = mcolor;
+ onbar[((w == US) ? THEM : US)] -= mcolor;
+ (getCell(((w == US) ? BAR_THEM : BAR_US)))->cellUpdate
+ (onbar[((w == US) ? THEM : US)], false);
+ }
+ (getCell(move->destination()))->cellUpdate(board[move->destination()]);
+ }
+ makeMove(move->source(), move->destination());
+ redoHistory.remove();
+ emit finishedUpdate();
+ }
+ sendMove();
+}
+
+/*
+ * This function performs and undo for the last move and updates the parent
+ * of the board by calling sendMove() after the undo.
+ */
+void KBgBoard::undoMove()
+{
+ if (getEditMode())
+ return;
+
+ int w = getTurn();
+ int mcolor = ((w == US) ? color : -color);
+ KBgBoardMove *move = moveHistory.last();
+ if (move && (w == US || w == THEM)) {
+ /*
+ * Undo changes at source
+ */
+ if (move->source() == BAR_US || move->source() == BAR_THEM) {
+ onbar[w] += mcolor;
+ (getCell(move->source()))->cellUpdate(onbar[w], false);
+ } else {
+ board[move->source()] += mcolor;
+ (getCell(move->source()))->cellUpdate
+ (board[move->source()]);
+ }
+ /*
+ * Undo changes at the destination
+ */
+ if ( (move->destination() == HOME_THEM_LEFT ) ||
+ (move->destination() == HOME_THEM_RIGHT) ||
+ (move->destination() == HOME_US_LEFT ) ||
+ (move->destination() == HOME_US_RIGHT )) {
+ onhome[w] -= mcolor;
+ (getCell(move->destination()))->cellUpdate
+ (onhome[w], false);
+ } else {
+ board[move->destination()] -= mcolor;
+ if (move->wasKicked()) {
+ board[move->destination()] = -mcolor;
+ onbar[((w == US) ? THEM : US)] += mcolor;
+ (getCell(((w == US) ?
+ BAR_THEM : BAR_US)))->cellUpdate
+ (onbar[((w == US) ? THEM : US)], false);
+ }
+ (getCell(move->destination()))->cellUpdate
+ (board[move->destination()]);
+ }
+ ++possMoves[move->length()];
+ redoHistory.append(new KBgBoardMove(*move));
+ moveHistory.remove();
+ emit finishedUpdate();
+ }
+ sendMove();
+}
+
+/*
+ * While putting a piece on a cell the cell has noticed that it changed
+ * ownership and hence needs a piece to be kicked. Since cells don't
+ * know where the opponents bar is we handle this here.
+ */
+void KBgBoard::kickedPiece()
+{
+ int w = ((getTurn()) == US ? THEM : US);
+
+ if (w == US) {
+ onbar[w] += color;
+ (getCell(BAR_US ))->cellUpdate(onbar[w], false);
+ } else {
+ onbar[w] -= color;
+ (getCell(BAR_THEM))->cellUpdate(onbar[w], false);
+ }
+ if (!getEditMode()) {
+ KBgBoardMove *move = moveHistory.last();
+ move->setKicked(true);
+ }
+ emit finishedUpdate();
+}
+
+/*
+ * This is a very short utility function for makeShortMove().
+ */
+void KBgBoardCell::makeShortMoveHelper(int s, int d)
+{
+ if (getPiece()) {
+ board->makeMove(s, d);
+ KBgBoardCell *dest = board->getCell(d);
+ dest->putPiece(((board->getTurn() == US) ? color : -color));
+ }
+}
+
+/*
+ * This function makes the shortes possible move from this cell. It
+ * uses only one dice and and it will kick opponent checkers.
+ */
+void KBgBoardCell::makeShortMove()
+{
+ int m[4];
+
+ int dir = ((board->getTurn() == US) ? direction : -direction);
+ int src = board->IDtoNum(cellID);
+
+ if (src == BAR_US || src == BAR_THEM) {
+
+ int s = (dir > 0) ? 0 : 25;
+ for (int i = 1; i < 7; i++) {
+ int d = (dir > 0) ? i : 25 - i;
+ if (board->checkMultiMove(s, d, m) == 1) {
+ makeShortMoveHelper(src, d);
+ break;
+ }
+ }
+
+ } else {
+
+ for (int i = 1; i < 7; i++) {
+ int d = src + dir*i;
+ if (d > 25) d = 25;
+ if (d < 0) d = 0;
+ if (0 < d && d < 25) {
+
+ if (board->checkMultiMove(src, d, m) == 1) {
+ makeShortMoveHelper(src, d);
+ break;
+ }
+
+ } else {
+
+ if (board->moveOffPossible()) {
+ int whichHome;
+ if (board->getTurn() == US)
+ whichHome = ((direction > 0) ?
+ HOME_US_LEFT :
+ HOME_US_RIGHT);
+ else
+ whichHome = ((direction > 0) ?
+ HOME_THEM_LEFT :
+ HOME_THEM_RIGHT);
+
+ if (board->diceAllowMove
+ (cellID, whichHome)) {
+ makeShortMoveHelper(src, whichHome);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+/*
+ * Ask the current backgammon engine for a doubled cube.
+ */
+void KBgBoard::getDoubleCube(const int w)
+{
+ emit doubleCube(w);
+}
+
+/*
+ * Ask the current backgammon engine rolling the dice.
+ */
+void KBgBoard::getRollDice(const int w)
+{
+ emit rollDice(w);
+}
+
+/*
+ * This is the constructor of the KBgBoard class. It creates
+ * a backgammon board with an initial distribution of checkers, empty
+ * dice and a cube with face value 1. The initial board is not usable!
+ * You have to change the status by passing a KBgStatus
+ * object to setState(...) before you can play!
+ */
+KBgBoard::KBgBoard(QWidget *parent, const char *name, QPopupMenu *menu)
+ : QWidget(parent, name)
+{
+ /*
+ * The following lines set up internal bookkeeping data.
+ */
+ moveHistory.setAutoDelete(true);
+ redoHistory.setAutoDelete(true);
+ cube = 1;
+ allowMoving(true);
+ setEditMode(false);
+ savedCursor = NULL;
+ checkerDiam = MINIMUM_CHECKER_SIZE;
+
+ /*
+ * We may be initialized with a popup menu by our parent.
+ */
+ contextMenu = menu;
+
+ baseColors[0] = black;
+ baseColors[1] = white;
+
+ /*
+ * Get the 30 cells that constitute the board and initialize
+ * them properly.
+ */
+ cells[ 0] = new KBgBoardHome(this, HOME_THEM_LEFT);
+ cells[14] = new KBgBoardHome(this, HOME_THEM_RIGHT);
+ cells[15] = new KBgBoardHome(this, HOME_US_LEFT);
+ cells[29] = new KBgBoardHome(this, HOME_US_RIGHT);
+
+ cells[ 7] = new KBgBoardBar(this, BAR_THEM);
+ cells[22] = new KBgBoardBar(this, BAR_US);
+
+ for (int i=1; i<7; ++i) {
+ cells[ i] = new KBgBoardField(this, i);
+ cells[ 7+i] = new KBgBoardField(this, 6+i);
+ cells[15+i] = new KBgBoardField(this, 12+i);
+ cells[22+i] = new KBgBoardField(this, 18+i);
+ }
+
+ /*
+ * Get the default seeting of the board and initialize the
+ * state of it.
+ */
+ KBgStatus *st = new KBgStatus();
+
+ st->setCube(1, true, true);
+ st->setDirection(+1);
+ st->setColor(+1);
+
+ st->setBoard( 1, US, 2); st->setBoard( 6, THEM, 5);
+ st->setBoard( 8, THEM, 3); st->setBoard(12, US, 5);
+ st->setBoard(13, THEM, 5); st->setBoard(17, US, 3);
+ st->setBoard(19, US, 5); st->setBoard(24, THEM, 2);
+
+ st->setHome(US, 0);
+
+ st->setDice(US , 0, 0); st->setDice(US , 1, 0);
+ st->setDice(THEM, 0, 0); st->setDice(THEM, 1, 0);
+
+ setState(*st);
+
+ delete st;
+
+ /*
+ * This line simplifies the checkMultiMove(...) function a lot.
+ */
+ board[0] = board[25] = 0;
+
+ /*
+ * User interface design settings come here. These may be
+ * overwritten by the user.
+ */
+ shortMoveMode = SHORT_MOVE_DOUBLE;
+ setBackgroundColor(QColor(200, 200, 166));
+ computePipCount = true;
+
+ /*
+ * Set initial font
+ */
+ setFont(QApplication::font());
+}
+
+QSize KBgBoard::minimumSizeHint() const
+{
+ return QSize(MINIMUM_CHECKER_SIZE * 15, MINIMUM_CHECKER_SIZE * 11);
+}
+
+QSize KBgBoard::sizeHint() const {
+ return QSize(MINIMUM_CHECKER_SIZE *15*4,MINIMUM_CHECKER_SIZE*11*2);
+}
+
diff --git a/kbackgammon/kbgboard.h b/kbackgammon/kbgboard.h
new file mode 100644
index 00000000..e2f35f68
--- /dev/null
+++ b/kbackgammon/kbgboard.h
@@ -0,0 +1,967 @@
+/* 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.
+
+*/
+
+
+/*
+
+ This file contains the declaration and definition of a couple of
+ classes related to the KBgBoard class.
+
+ Effort has been made to keep this class general. Please comment on that
+ if you want to use it in your own project. Most of the stuff is private
+ and/or in utility classes that shouldn't be used directly. All public
+ interfaces are in teh beginning of the file.
+
+*/
+
+#ifndef KBGBOARD_H
+#define KBGBOARD_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qspinbox.h>
+#include <qpushbutton.h>
+#include <qcheckbox.h>
+#include <qdialog.h>
+#include <qstring.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qbitmap.h>
+#include <qregexp.h>
+#include <qradiobutton.h>
+#include <qwidget.h>
+#include <qptrlist.h>
+#include <qtimer.h>
+#include <qlabel.h>
+#include <qcursor.h>
+#include <qpopupmenu.h>
+#include <qcombobox.h>
+#include <kdialogbase.h>
+#include <kfontdialog.h>
+
+#include "kbgstatus.h"
+
+/*
+ * Just some internal constants and classes
+ */
+const int US = KBgStatus::US;
+const int THEM = KBgStatus::THEM;
+const int BOTH = KBgStatus::BOTH;
+
+const int HOME_US_LEFT = 101;
+const int HOME_US_RIGHT = 102;
+const int HOME_THEM_LEFT = 103;
+const int HOME_THEM_RIGHT = 104;
+const int BAR_US = 105;
+const int BAR_THEM = 106;
+const int HOME_US = 107;
+const int HOME_THEM = 108;
+
+const int SHORT_MOVE_NONE = 0;
+const int SHORT_MOVE_SINGLE = 1;
+const int SHORT_MOVE_DOUBLE = 2;
+
+class KBgBoard;
+class KBgBoardCell;
+class KBgBoardMove;
+class KBgBoardHome;
+class KBgBoardBar;
+class KBgBoardField;
+class KBgBoardQDice;
+
+
+/**
+ * The KBgBoard class.
+ *
+ * This class handles all game operations of the client.
+ */
+class KBgBoard : public QWidget
+{
+ Q_OBJECT
+
+ friend class KBgBoardCell;
+ friend class KBgBoardHome;
+ friend class KBgBoardBar;
+ friend class KBgBoardField;
+
+ public:
+
+ /**
+ * Constructor and destructor. Parameter as usual.
+ */
+ KBgBoard(QWidget *parent = 0, const char *name = 0,
+ QPopupMenu *menu = 0);
+ virtual ~KBgBoard();
+
+ /**
+ * Returns the current read/write status of the board.
+ */
+ bool movingAllowed() const;
+
+ /**
+ * Returns the current up-to-the-second pip count (including
+ * the moves that have been done.
+ */
+ int getPipCount(const int& w) const;
+
+ /**
+ * Sets the momentary short move mode. The parameter should
+ * be one of the constanst SHORT_MOVE_NONE, SHORT_MOVE_SINGLE
+ * or SHORT_MOVE_DOUBLE. This the automatic moving of a checker
+ * with the shortest possible move away from the current field.
+ */
+ void setShortMoveMode(int m);
+
+ /**
+ * Returns the current short move mode.
+ */
+ int getShortMoveMode();
+
+ /**
+ * Sets the background color and passes the info to the
+ * child widgets
+ */
+ virtual void setBackgroundColor(const QColor &col);
+
+ /**
+ * Prints the baord along with some basic info onto the
+ * painetr p. It is assumed that this painter is a postscript
+ * printer. Hence the plot is black/white only.
+ */
+ void print(QPainter *p);
+
+ /**
+ * Get whose turn it is - US, THEM or 0
+ */
+ int getTurn() const;
+
+ /**
+ * Allows for overriding the current turn color in edit mode.
+ */
+ void storeTurn(const int pcs);
+
+ /**
+ * Retrurns the current edit mode status.
+ */
+ bool getEditMode() const;
+
+ /**
+ * Get a new value for the cube from the user - this opens a dialog
+ */
+ void queryCube();
+
+ /**
+ * Get the font the board cells should use for the display of
+ * numbers and cube value.
+ */
+ QFont getFont() const;
+
+ /**
+ * This function has to be reimplemented to provide a minimum size for
+ * the playing area.
+ */
+ QSize minimumSizeHint() const;
+public slots:
+
+ /**
+ * This allows the users of this widget to dis- and enable
+ * mouse events. In effect this triggers the read/write
+ * flag of the board.
+ */
+ void allowMoving(const bool fl);
+
+ /**
+ * Overwritten resize event handler.
+ *
+ * We overwrite the handler to make sure that all the cells are
+ * repainted as well.
+ */
+ virtual void resizeEvent(QResizeEvent *);
+
+ /**
+ * Undo the last move.
+ */
+ void undoMove();
+
+ /**
+ * Redo a previously undone move
+ */
+ void redoMove();
+
+ /**
+ * This is the most important public member. It takes
+ * a board status - s. the KBgBoardStatus class -
+ * and sets the board accordingly.
+ */
+ void setState(const KBgStatus &);
+
+ /**
+ * Set the context menu
+ */
+ void setContextMenu(QPopupMenu *menu);
+
+ /**
+ * Get the current state of the board.
+ */
+ KBgStatus *getState(KBgStatus *st) const;
+
+ /**
+ * Sets the edit mode of the board. In that mode the board can be
+ * modified arbitrarily.
+ */
+ void setEditMode(const bool m);
+
+ /**
+ * Allows the users of the board classe to set the font to be used
+ * on the board. Note that the fontsize is dynamically set
+ */
+ void setFont(const QFont& f);
+
+ /**
+ * Write the current configuration to the application's data base
+ */
+ void saveConfig();
+
+ /**
+ * Restore the stored configuration or start with reasonable defaults
+ */
+ void readConfig();
+
+ signals:
+
+ /**
+ * The text identifies the current game status - could be put
+ * into the main window caption
+ */
+ void statText(const QString &msg);
+
+ /**
+ * The cells connect to this signal and it tells them that it is
+ * time to update their content now if necessary.
+ */
+ void finishedUpdate();
+
+ /**
+ * The user has requested the dice to be rolled. Emit the
+ * request to somebody who knows how to do that.
+ */
+ void rollDice(const int w);
+
+ /**
+ * Ask the server to double
+ */
+ void doubleCube(const int w);
+
+ /**
+ * Once the moves are all made, build a server command and send
+ * them out.
+ */
+ void currentMove(QString *s);
+
+ /* ************************************************** */
+ /* ************************************************** */
+
+ /* Everything below this line is private and it */
+ /* shouldn't be used by users of this widget. */
+
+ /* This means the whole file! All following */
+ /* classes and members are private. */
+
+ /* ************************************************** */
+ /* ************************************************** */
+
+protected:
+ virtual QSize sizeHint() const;
+
+ QColor baseColors[2];
+ QFont boardFont;
+ KBgBoardCell* cells[30];
+ bool computePipCount;
+
+ private:
+
+ /**
+ * Emits a currentMove string to whomever cares.
+ */
+ void sendMove();
+
+ /**
+ * Emit a request for doubling.
+ */
+ void getDoubleCube(const int w);
+
+ /**
+ * Get pieces on who's bar - US or THEM
+ */
+ int getOnBar( int who ) const;
+
+ /**
+ * Get who's dice num - who = US or THEM, num = 0 or 1
+ */
+ int getDice( int w, int n ) const;
+
+ /**
+ * Get the number on the cube
+ */
+ int getCube() const;
+
+ /**
+ * Ask the server for rolling
+ */
+ void getRollDice(const int w);
+
+ /**
+ * Tell the board that we kicked a piece off and store
+ * the information
+ */
+ void kickedPiece();
+
+ /**
+ * Check whether a move off is possible by checking that all pieces
+ * are either in the home board or already off
+ */
+ bool moveOffPossible() const;
+
+ /**
+ * Check whether who (US or THEM) can double
+ */
+ bool canDouble( int who ) const;
+
+ /**
+ * The cells have to tell us if the change the number of pieces, since
+ * we check that sometimes
+ */
+ void updateField( int f, int v );
+
+ /**
+ * Convert an ID from to the board numbers
+ */
+ int IDtoNum(const int ID ) const;
+
+ /**
+ * Checks if there is a possibility to get from src to dest.
+ */
+ int checkMultiMove( int src, int dest, int m[4] );
+
+ /**
+ * Checks whether the dice allow a move from src to dest (ID's)
+ */
+ bool diceAllowMove( int src, int dest );
+
+ /**
+ * Make a move from src to dest. The numbers are cellID's.
+ */
+ void makeMove( int src, int dest );
+
+ /**
+ * Translates a field number to a pointer to the cell.
+ */
+ KBgBoardCell* getCell(int num);
+
+ /**
+ * Draws a piece on the painter p, with the upper left corner
+ * of the enclosing rectangle being (x,y)
+ */
+ void drawCircle(QPainter *p, int x, int y, int pcs, int diam,
+ int col, bool upper, bool outer) const;
+
+ /**
+ * Draws an anti-aliased checker on the painter p.
+ */
+ void drawChecker(QPainter *p, int x, int y, int pcs, int diam,
+ int col, bool upper) const;
+
+ /**
+ * Draws a simple 2-color checker on the painter p. This is intended
+ * for printing.
+ */
+ void drawSimpleChecker(QPainter *p, int x, int y, int pcs,
+ int diam) const;
+
+ /**
+ * Given a position on the board, return the cell under the mouse pointer
+ */
+ KBgBoardCell* getCellByPos(const QPoint& p) const;
+
+ /**
+ * Name says it all, doesn't it?
+ */
+ void showContextMenu();
+
+ /**
+ * Temporary replace the cursor, saves the old one
+ */
+ void replaceCursor(const QCursor& c);
+
+ /**
+ * Restore the previously stored cursor.
+ */
+ void restoreCursor();
+
+ /**
+ * Given the sign of p, return the current base color
+ */
+ QColor getCheckerColor(int p) const;
+
+ /**
+ * Small utility function for makeMove - just for readability
+ */
+ int makeMoveHelper(int si, int sf, int delta);
+
+ /**
+ * Private data members - no description needed
+ */
+ QPopupMenu *contextMenu;
+ QPtrList<KBgBoardMove> moveHistory;
+ QPtrList<KBgBoardMove> redoHistory;
+ int direction, color;
+ int hasmoved;
+ bool allowmoving, editMode;
+ int storedTurn;
+ int onbar[2];
+ int onhome[2];
+ int board[26];
+ int dice[2][2];
+ int possMoves[7];
+ int cube;
+ int checkerDiam;
+ bool cubechanged;
+ bool maydouble[2];
+ int shortMoveMode;
+ QCursor *savedCursor;
+};
+
+/**
+ * Base class for the cells on the board
+ *
+ * This base class provides all the necessary functions of a cell
+ * on a backgammon board. It has a bunch of virtual functions that
+ * are overloaded in the derived classes.
+ */
+class KBgBoardCell : public QLabel
+{
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor and destructor
+ */
+ KBgBoardCell(QWidget * parent, int numID);
+ virtual ~KBgBoardCell();
+
+ /**
+ * sets the number and color of checkers on this cell
+ * takes care of repainting
+ */
+ virtual void cellUpdate(const int p, const bool cubechanged = false) = 0;
+
+ /**
+ * Draws the content of the cell on the painter *p
+ */
+ virtual void paintCell(QPainter *p, int xo = 0, int yo = 0,
+ double sf = 1.0) const;
+
+ /**
+ * Updates all the status variables at once
+ */
+ virtual void statusUpdate(int dir, int col);
+
+protected:
+
+ /**
+ * Draw vertical lines around the board.
+ */
+ void drawVertBorder(QPainter *p, int xo, int yo, double sf = 1.0) const;
+ void drawOverlappingCheckers(QPainter *p, int xo, int yo,
+ double sf = 1.0) const;
+ void drawCube(QPainter *p, int who, int xo, int yo, double sf = 1.0) const;
+
+ /**
+ * Puts a piece of color on a field
+ */
+ void putPiece( int newColor );
+
+ /**
+ * Removes a piece from a field. Returns true if success or false else
+ * (i.e. there is no piece on this field.
+ */
+ bool getPiece();
+
+ /**
+ * Return the number of this cell
+ */
+ virtual int getNumber() const;
+
+ /**
+ * Return the suggested diameter of a piece
+ */
+ int getCheckerDiameter() const;
+ int getCellColor();
+
+ /**
+ * Do we allow a drop of the DragEvent ? This checks the payload and
+ * reacts on it.
+ */
+ virtual bool dropPossible(int fromCellID, int newColor) = 0;
+
+ protected:
+
+ /**
+ * Overwrite how a cell draws itself
+ */
+ virtual void drawContents(QPainter *);
+
+ /**
+ * Status numbers that store the current board status.
+ */
+ int mouseButton;
+ int direction;
+ int color;
+
+ /**
+ * How many pieces are we currently holding ?
+ */
+ int pcs;
+
+ /**
+ * Our own ID
+ */
+ int cellID;
+
+ /**
+ * Indicates whether this cell needs to repaint itself after
+ * the board has been processed.
+ */
+ bool stateChanged;
+ bool colorChanged, directionChanged;
+
+ /**
+ * the board and the pieces are one unit (none makes
+ * sense without the other). So the pieces know and access their parent.
+ */
+ KBgBoard *board;
+ void checkAndMakeShortMove(QMouseEvent *e, int m);
+
+ /**
+ * Returns the bounding rectangle of the cube on this cell
+ */
+ QRect cubeRect( int who, bool big, double sf = 1.0 ) const;
+
+ /**
+ * Returns the bounding rectangle of the dice i on this cell
+ */
+ QRect diceRect(int i, bool big, double sf = 1.0, double scale = 0.45) const;
+ bool dragInProgress;
+
+ protected slots:
+ /**
+ * Refreshes the widget. This is essentially a call to update().
+ */
+ virtual void refresh();
+
+ /**
+ * Can we currently drag from this field ?
+ */
+ virtual bool dragPossible() const = 0;
+
+ /**
+ * Possibly initiate a drag.
+ */
+ virtual void mouseMoveEvent( QMouseEvent * );
+ virtual void mousePressEvent(QMouseEvent *e);
+
+ /**
+ * Make the shortes possible move away from this cell
+ */
+ void makeShortMove();
+ void makeShortMoveHelper(int s, int d);
+
+ /**
+ * Catch a single left click and perhapes make a move.
+ */
+ virtual void mouseReleaseEvent( QMouseEvent *e );
+
+ /**
+ * Catch a double left click and perhapes make a move.
+ */
+ virtual void mouseDoubleClickEvent( QMouseEvent *e );
+};
+
+/**
+ * The homes are derived from the regular cells. They just overwrite
+ * some members.
+ */
+class KBgBoardHome : public KBgBoardCell
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * sets the number and color of checkers on this cell
+ * takes care of repainting
+ */
+ virtual void cellUpdate(const int p, const bool cubechanged = false);
+
+ /*
+ * Draws the content of the cell on the painter *p
+ */
+ virtual void paintCell(QPainter *p, int xo = 0, int yo = 0,
+ double sf = 1.0) const;
+
+ /**
+ * Constructor and destructor
+ */
+ KBgBoardHome( QWidget * parent, int numID);
+ virtual ~KBgBoardHome();
+
+ /**
+ * Check whether a drop on the home cell is possible.
+ */
+ virtual bool dropPossible(int fromCellID, int newColor);
+
+ protected:
+ /**
+ * Determine whether a drag from the home is possible.
+ */
+ virtual bool dragPossible() const;
+
+ /**
+ * Get the double clicks
+ */
+ virtual void mouseDoubleClickEvent( QMouseEvent *e );
+
+ /**
+ * The homes contain dice and cube. This draws them.
+ */
+ void drawDiceAndCube(QPainter *p, int who, int xo, int yo,
+ double sf) const;
+
+ void drawDiceFrame(QPainter *p, int col, int num, int xo, int yo,
+ bool big, double sf) const;
+ void drawDiceFace(QPainter *p, int col, int num, int who, int xo,
+ int yo, double sf) const;
+
+ private:
+ /**
+ * Save old dice to avoid repainting
+ */
+ int savedDice[2];
+
+};
+
+/**
+ * The bars are derived from the regular cells. They just overwrite
+ * some members.
+ */
+class KBgBoardBar : public KBgBoardCell
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * sets the number and color of checkers on this cell
+ * takes care of repainting
+ */
+ virtual void cellUpdate(const int p, const bool cubechanged = false);
+
+ /**
+ * Draws the content of the cell on the painter *p
+ */
+ virtual void paintCell(QPainter *p, int xo = 0, int yo = 0,
+ double sf = 1.0) const;
+
+ /**
+ * Constructor
+ */
+ KBgBoardBar( QWidget * parent, int numID );
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgBoardBar();
+
+ /**
+ * Check whether a drop on the bar cell is possible.
+ */
+ virtual bool dropPossible(int fromCellID, int newColor);
+
+ protected:
+ /**
+ * Determine whether a drag from the bar is possible.
+ */
+ virtual bool dragPossible() const;
+ /**
+ * Get the double clicks
+ */
+ virtual void mouseDoubleClickEvent(QMouseEvent *e);
+};
+
+/**
+ * The fields are derived from the regular cells. They just overwrite
+ * some members.
+ */
+class KBgBoardField : public KBgBoardCell
+{
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor and destructor
+ */
+ KBgBoardField( QWidget * parent, int numID);
+ virtual ~KBgBoardField();
+
+ /**
+ * sets the number and color of checkers on this cell
+ * takes care of repainting
+ */
+ virtual void cellUpdate(const int p, const bool cubechanged = false);
+
+ /**
+ * Draws the content of the cell on the painter *p
+ */
+ virtual void paintCell(QPainter *p, int xo = 0, int yo = 0,
+ double sf = 1.0) const;
+
+ /**
+ * Check whether a drop on the field cell is possible.
+ */
+ virtual bool dropPossible(int fromCellID, int newColor);
+
+ protected:
+ /**
+ * Determine whether a drag from the field is possible.
+ */
+ virtual bool dragPossible() const;
+
+ /**
+ * Return the y-coordinate of the number of the field.
+ */
+ int numberBase() const;
+};
+
+/**
+ * Internal class for storing a move in the undo history buffer.
+ */
+class KBgBoardMove
+{
+ public:
+ /**
+ * Accepts source, destination and the dice that made this move
+ * possible. Set the kicked flag to false.
+ */
+ KBgBoardMove(int src, int dest, int delta)
+ {s = src; d = dest; l = delta; k = false;}
+
+ /**
+ * Set this move to be a kick
+ */
+ void setKicked(bool kicked) {k = kicked;}
+
+ /**
+ * Look up the source
+ */
+ int source() const {return s;}
+
+ /**
+ * Look up the destination
+ */
+ int destination() const {return d;}
+
+ /**
+ * Look up the dice that made this move
+ */
+ int length() const {return l;}
+
+ /**
+ * Check whether the move kicked a piece
+ */
+ bool wasKicked() const {return k;}
+
+ private:
+ /**
+ * Source, destination, dice
+ */
+ int s, d, l;
+
+ /**
+ * Kicked move ?
+ */
+ bool k;
+};
+
+/**
+ * Simple dialog that allows to query the user for dice values.
+ *
+ * A very simple dialog with two SpinBoxes and two buttons.
+ */
+class KBgBoardQDice : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor and destructor
+ */
+ KBgBoardQDice(const char *name = 0);
+ virtual ~KBgBoardQDice();
+
+protected:
+
+ /**
+ * Spin boxes and buttons are children
+ */
+ QSpinBox *sb[2];
+ QPushButton *ok;
+ QPushButton *cancel;
+
+public slots:
+
+ /**
+ * Get the face values.
+ */
+ int getDice(int n);
+};
+
+
+/**
+ * Simple dialog that allows to query the user for the cube value.
+ */
+class KBgBoardQCube : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor and destructor
+ */
+ KBgBoardQCube(int val, bool us, bool them);
+ virtual ~KBgBoardQCube();
+
+protected:
+
+ /**
+ * Spin boxes and buttons are children
+ */
+ QComboBox *cb[2];
+ QPushButton *ok;
+ QPushButton *cancel;
+
+public slots:
+
+ /**
+ * Get the face values.
+ */
+ int getCubeValue();
+ int getCubeOwner();
+
+protected slots:
+
+ /**
+ * These slots are needed to get consistent relations
+ * between the two combo boxes.
+ */
+ void changePlayer(int val);
+ void changeValue(int player);
+
+};
+
+
+/**
+ * Extension of the KBgBoard class that can add itself
+ * to a QTabDialog for configuration.
+ */
+class KBgBoardSetup : public KBgBoard
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgBoardSetup(QWidget *parent = 0, const char *name = 0,
+ QPopupMenu *menu = 0);
+
+ /**
+ * Lets the board put its setup pages into the notebook nb
+ */
+ void getSetupPages(KDialogBase *nb);
+
+public slots:
+
+ /**
+ * Setup changes are confirmed. Store them.
+ */
+ void setupOk();
+
+ /**
+ * Setup has been cancelled. Undo the color changes
+ */
+ void setupCancel();
+
+ /**
+ * Load default values for user settings
+ */
+ void setupDefault();
+
+protected slots:
+
+ /**
+ * Open a color dialog for the background color
+ */
+ void selectBackgroundColor();
+
+ /**
+ * Open a color dialog for the first checker color
+ */
+ void selectBaseColorOne();
+
+ /**
+ * Open a color dialog for the second checker color
+ */
+ void selectBaseColorTwo();
+
+private:
+
+ /**
+ * Save settings before the user changed them
+ */
+ KFontChooser *kf;
+
+ QRadioButton *rbMove[3];
+
+ QColor saveBackgroundColor;
+ QColor saveBaseColors[2];
+
+ /**
+ * Need these to change their colors
+ */
+ QPushButton *pbc_1, *pbc_2, *pbc_3;
+ QCheckBox *cbp;
+};
+
+#endif // KBGBOARD_H
diff --git a/kbackgammon/kbgstatus.cpp b/kbackgammon/kbgstatus.cpp
new file mode 100644
index 00000000..1215324e
--- /dev/null
+++ b/kbackgammon/kbgstatus.cpp
@@ -0,0 +1,544 @@
+/* Yo Emacs, this -*- C++ -*-
+
+ Copyright (C) 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.
+
+*/
+
+#include "kbgstatus.h"
+#include "kbgstatus.moc"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+
+/*
+ * Parse a rawboard description from FIBS and initialize members.
+ */
+KBgStatus::KBgStatus(const QString &rawboard)
+{
+ /*
+ * This is the format string from hell...
+ */
+ const char *format = ("%*[^:]%*[:]%99[^:]%*[:]%99[^:]%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]"
+ "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]%i%*[:]"
+ "%i%*[:]%i%*[:]");
+
+ char opponent[100], player[100];
+
+ QString cap;
+
+ int board[26], ldice[2][2], maydouble[2], scratch[4], onhome[2], onbar[2];
+ int points[2];
+ int tomove, lturn, color, cube, direction, redoubles, bar, home, length;
+
+ // split the incoming line at colons - latin1() is fine, since the string comes from FIBS.
+ sscanf (rawboard.latin1(), format,
+ player, opponent,
+ &length,
+ &points[0], &points[1],
+ &board[ 0], &board[ 1], &board[ 2], &board[ 3], &board[ 4], &board[ 5],
+ &board[ 6], &board[ 7], &board[ 8], &board[ 9], &board[10], &board[11],
+ &board[12], &board[13], &board[14], &board[15], &board[16], &board[17],
+ &board[18], &board[19], &board[20], &board[21], &board[22], &board[23],
+ &board[24], &board[25],
+ &lturn,
+ &ldice[US ][0], &ldice[US ][1], &ldice[THEM][0], &ldice[THEM][1],
+ &cube,
+ &maydouble[US], &maydouble[THEM],
+ &doubled_,
+ &color,
+ &direction,
+ &home, &bar,
+ &onhome[US], &onhome[THEM], // on home
+ &onbar[US], &onbar[THEM], // on bar
+ &tomove,
+ &scratch[2], &scratch[3], // forced move & did crawford
+ &redoubles);
+
+ player_[US] = player;
+ player_[THEM] = opponent;
+
+ setCube(cube, maydouble[US], maydouble[THEM]);
+ setDirection(direction);
+ setColor(color);
+ for (int i = 1; i < 25; i++) {
+ if (board[i] == 0 || color == board[i]/abs(board[i]))
+ setBoard(i, US, abs(board[i]));
+ else
+ setBoard(i, THEM, abs(board[i]));
+ }
+ setDice(US , 0, ldice[US ][0]);
+ setDice(US , 1, ldice[US ][1]);
+ setDice(THEM, 0, ldice[THEM][0]);
+ setDice(THEM, 1, ldice[THEM][1]);
+
+ setHome(US, onhome[US ]);
+ setHome(THEM, onhome[THEM]);
+
+ setBar(US, board[ bar]);
+ setBar(THEM, board[25-bar]);
+
+ setPoints(US, points[0]);
+ setPoints(THEM, points[1]);
+
+ if (lturn == 0)
+ setLength(-1);
+ else
+ setLength(length);
+
+ int t = lturn*color;
+ if (t > 0) setTurn(US);
+ if (t < 0) setTurn(THEM);
+ if (t == 0) setTurn(NONE);
+}
+
+/*
+ * Constructor initializes the status to an empty board with cube one
+ * and empty dice.
+ */
+KBgStatus::KBgStatus()
+ : QObject()
+{
+ /*
+ * Initialize members
+ */
+ for (int i = 0; i < 26; ++i)
+ setBoard(i, US, 0);
+
+ for (int i = US; i <= THEM; i++) {
+ setDice (i, 0, 0);
+ setDice (i, 1, 0);
+ setHome (i, 0);
+ setBar (i, 0);
+ setPoints(i, -1);
+ setPlayer(i, QString::null);
+ }
+ setColor(White, US);
+ setCube(1, BOTH); // also initializes maydouble
+ setDirection(1);
+ setLength(-1);
+ setTurn(NONE);
+
+ // initialize members without assignment functions
+ doubled_ = 0;
+}
+
+/*
+ * Copy constructor calls private utility function.
+ */
+KBgStatus::KBgStatus(const KBgStatus &rhs)
+ : QObject()
+{
+ copy(rhs);
+}
+
+/*
+ * Destructor
+ */
+KBgStatus::~KBgStatus()
+{
+ // nothing to do
+}
+
+/*
+ * Assignment operator shares a lot of code with the copy
+ * constructor.
+ */
+KBgStatus& KBgStatus::operator=(const KBgStatus &rhs)
+{
+ if (this == &rhs) return *this;
+ copy(rhs);
+ return *this;
+}
+
+void KBgStatus::copy(const KBgStatus &rhs)
+{
+ for (int i = 0; i < 26; i++)
+ board_[i] = rhs.board_[i];
+
+ for (int i = US; i <= THEM; i++) {
+
+ home_[i] = rhs.home_[i];
+ bar_ [i] = rhs.bar_ [i];
+ dice_[i][0] = rhs.dice_[i][0];
+ dice_[i][1] = rhs.dice_[i][1];
+
+ maydouble_[i] = rhs.maydouble_[i];
+ player_ [i] = rhs.player_ [i];
+ points_ [i] = rhs.points_ [i];
+ }
+
+ cube_ = rhs.cube_;
+ direction_ = rhs.direction_;
+ color_ = rhs.color_;
+ turn_ = rhs.turn_;
+ doubled_ = rhs.doubled_;
+}
+
+
+/*
+ * Access functions
+ */
+int KBgStatus::board(const int &i) const
+{
+ return ((0 < i && i < 25) ? color_*board_[i] : 0);
+}
+
+int KBgStatus::home(const int &w) const
+{
+ return ((w == US || w == THEM) ? color_*home_[w] : 0);
+}
+
+int KBgStatus::bar(const int &w) const
+{
+ return ((w == US || w == THEM) ? color_*bar_[w] : 0);
+}
+
+int KBgStatus::color(const int &w) const
+{
+ return ((w == THEM) ? -color_ : color_);
+}
+
+int KBgStatus::direction() const
+{
+ return direction_;
+}
+
+int KBgStatus::dice(const int &w, const int &n) const
+{
+ if ((w == US || w == THEM) && (n == 0 || n == 1))
+ return dice_[w][n];
+ else
+ return 0;
+}
+
+int KBgStatus::cube(const int &w) const
+{
+ if (w == US || w == THEM)
+ return ((maydouble_[w]) ? cube_ : -cube_);
+ return 0;
+}
+
+int KBgStatus::points(const int& w) const
+{
+ return ((w == US || w == THEM) ? points_[w] : -1);
+}
+
+QString KBgStatus::player(const int &w) const
+{
+ return ((w == US || w == THEM) ? player_[w] : QString::null);
+}
+
+int KBgStatus::length() const
+{
+ return length_;
+}
+
+int KBgStatus::turn() const
+{
+ return turn_;
+}
+
+bool KBgStatus::doubled() const
+{
+ return doubled_;
+}
+
+
+/*
+ * Assignment functions
+ */
+void KBgStatus::setBoard(const int &i, const int &w, const int &v)
+{
+ if (0 < i && i < 25) {
+ if (w == US)
+ board_[i] = abs(v);
+ else if (w == THEM)
+ board_[i] = -abs(v);
+ }
+}
+
+void KBgStatus::setHome(const int &w, const int &v)
+{
+ if (w == US)
+ home_[w] = abs(v);
+ else if (w == THEM)
+ home_[w] = -abs(v);
+}
+
+void KBgStatus::setBar(const int& w, const int& v)
+{
+ if (w == US)
+ bar_[w] = abs(v);
+ else if (w == THEM)
+ bar_[w] = -abs(v);
+}
+
+void KBgStatus::setColor(const int &c, const int &w)
+{
+ if (w == US)
+ color_ = ((c < 0) ? Black : White);
+ else if (w == THEM)
+ color_ = ((c < 0) ? White : Black);
+}
+
+void KBgStatus::setDirection(const int &dir)
+{
+ direction_ = ((dir < 0) ? -1 : +1);
+}
+
+void KBgStatus::setDice(const int &w, const int &n, const int &v)
+{
+ if ((w == US || w == THEM) && (n == 0 || n == 1)) {
+ if (0 <= v && v <= 6)
+ dice_[w][n] = v;
+ else
+ dice_[w][n] = 0;
+ }
+}
+
+void KBgStatus::setCube(const int &c, const bool &us, const bool &them)
+{
+ int w = NONE;
+ if (us) w = US;
+ if (them) w = THEM;
+ if (us && them) w = BOTH;
+ setCube(c, w);
+}
+
+void KBgStatus::setCube(const int &c, const int &w)
+{
+ // assume that int has at least 32 bits...
+ for (int i = 0; i < 31; i++) {
+ if (1<<i == (cube_ = c)) break;
+ cube_ = 0;
+ }
+ maydouble_[US ] = (w == US || w == BOTH);
+ maydouble_[THEM] = (w == THEM || w == BOTH);
+}
+
+void KBgStatus::setPoints(const int &w, const int &p)
+{
+ if (w == US || w == THEM)
+ points_[w] = p;
+}
+
+void KBgStatus::setPlayer(const int &w, const QString &name)
+{
+ if (w == US || w == THEM)
+ player_[w] = name;
+}
+
+void KBgStatus::setLength(const int &l)
+{
+ length_ = l;
+}
+
+void KBgStatus::setTurn(const int &w)
+{
+ if (w == US || w == THEM || w == BOTH)
+ turn_ = w;
+}
+
+
+/*
+ * Utility functions
+ */
+int KBgStatus::moves() const
+{
+ int start, dir;
+
+ /*
+ * Return an error if it isn't anybodies turn.
+ */
+ if ((turn() != US) && (turn() != THEM))
+ return -1;
+
+ /*
+ * Determine tha direction of the current move
+ */
+ if ((turn() == US && direction() < 0) || (turn() == THEM && direction() > 0)) {
+ start = 25;
+ dir = -1;
+ } else {
+ start = 0;
+ dir = 1;
+ }
+
+ /*
+ * Get the current dice transferred into the move[] array. The
+ * final zero is a marker
+ */
+ int move[5] = {0, 0, 0, 0, 0};
+ move[0] = dice(turn(), 0);
+ move[1] = dice(turn(), 1);
+ if (move[0] == move[1]) {
+ move[3] = move[2] = move[0];
+ // saves some work further down
+ if (move[0] == 0)
+ return 0;
+
+ }
+
+ bool doubledice = (move[3] != 0);
+ int count = 4;
+
+ /*
+ * Get a copy of ourselves. That way we can mess around with
+ * the internals of the game.
+ */
+ KBgStatus sc(*this);
+
+ /*
+ * Start with getting all checkers off the bar
+ */
+ while (count > 0 && sc.bar(turn()) != 0) {
+ if (move[--count] != 0) {
+ if (color(turn())*sc.board(start+dir*move[count]) >= -1) {
+ sc.setBar(turn(), abs(sc.bar(turn()))-1);
+ sc.setBoard(start + dir*move[count], turn(), 1);
+ move[count] = 0;
+ }
+ }
+ }
+
+ /*
+ * Collect remaining moves in the beginning of the move array
+ */
+ int j = 0;
+ for (int i = 0; i < 4; i++) {
+ if ((move[j] = move[i]))
+ ++j;
+ if (i > j) move[i] = 0;
+ }
+
+
+ /*
+ * Find number of remaining moves
+ */
+ int moves = 0;
+ move[4] = 0;
+ while (move[moves])
+ ++moves;
+
+ /*
+ * Done or no more moves because the bar is not empty
+ */
+ if (sc.bar(turn()) != 0 || move[0] == 0)
+ return (move[3] ? 4 - moves : 2 - moves);
+
+ /*
+ * Try to find possible moves on the board
+ */
+ if (moves == 1 || move[0] == move[1]) {
+
+ /*
+ * Order doesn't matter, all moves are equal
+ */
+ while (--moves >= 0 && movePossible(sc, move[moves], start, dir));
+ moves++;
+ return (doubledice ? 4 - moves : 2 - moves);
+
+ } else {
+
+ /*
+ * Order does matter; try both ways.
+ */
+ moves = 0;
+ for (int i = 0; i < 25; i++) {
+ if (movePossible(sc, move[0], start + i*dir, dir)) {
+ moves = 1;
+ if (movePossible(sc, move[1], start, dir)) {
+ return 2;
+ }
+ }
+ // Restore scratch copy...
+ sc = *this;
+ }
+ for (int i = 0; i < 25; i++) {
+
+ if (movePossible(sc, move[1], start + i*dir, dir)) {
+ moves = 1;
+ if (movePossible(sc, move[0], start, dir)) {
+ return 2;
+ }
+ }
+ // Restore scratch copy...
+ sc = *this;
+ }
+ return moves;
+ }
+}
+
+bool KBgStatus::movePossible(KBgStatus &sc, int a, int start, int dir) const
+{
+ /*
+ * Determine where the first checker in moving direction is
+ * located
+ */
+ int first = (dir > 0) ? 1 : 24;
+ int last = (dir > 0) ? 25 : 0;
+ while (first != last && color(turn())*sc.board(first) <= 0)
+ first += dir;
+
+ /*
+ * Are we moving off ?
+ */
+ bool off = false;
+ if ((dir > 0 && first > 18) || (dir < 0 && first < 7))
+ off = true;
+
+ /*
+ * Find a move by exhaustive search.
+ */
+ while (true) {
+
+ start += dir;
+ int final = start+dir*a;
+
+ /*
+ * Make absolutely sure that the loop terminates eventually
+ */
+ if (start <= 0 || start >= 25)
+ return false;
+
+ if (color(turn())*sc.board(start) > 0) {
+
+ if (0 < final && final < 25 && color(turn())*sc.board(final) >= -1) {
+ sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
+ sc.setBoard(final, turn(), abs(sc.board(final)) + 1);
+ return true;
+ } else if (off && (final == 0 || final == 25)) {
+ sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
+ sc.setHome(turn(), abs(sc.home(turn())) + 1);
+ return true;
+ } else if (off && first == start && (final > 24 || final < 1)) {
+ sc.setBoard(start, turn(), abs(sc.board(start)) - 1);
+ sc.setHome(turn(), abs(sc.home(turn())) + 1);
+ return true;
+ }
+ }
+ }
+}
+
+// EOF
diff --git a/kbackgammon/kbgstatus.h b/kbackgammon/kbgstatus.h
new file mode 100644
index 00000000..5543e1ca
--- /dev/null
+++ b/kbackgammon/kbgstatus.h
@@ -0,0 +1,310 @@
+/*
+ Copyright (C) 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$
+
+*/
+
+#ifndef KBGSTATUS_H
+#define KBGSTATUS_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qobject.h>
+
+
+/**
+ * This is a the fundamental class of the KBg* hierarchy. It represents
+ * the state of backgammon games.
+ *
+ * The game states can be initialized in a variety of information and
+ * are meant to be passed to the board. In addition to that, the class
+ * has several utility functions that are helpful for engines that
+ * maintain a local state.
+ *
+ * @short The backgammon status object
+ * @author Jens Hoefkens <jens@hoefkens.com>
+ * @version $Id$
+ */
+class KBgStatus : public QObject
+{
+
+ Q_OBJECT
+
+ public:
+
+ /**
+ * These numbers are used to distinguish the two players. The
+ * notion of US and THEM is a leftover from ancient times.
+ */
+ enum {NONE = -1, US = 0, THEM = 1, BOTH = 2};
+
+ /**
+ * The names are just to distinguish the two different colors.
+ */
+ enum {Black = -1, White = +1};
+
+ /**
+ * The default constructor initializes the status to an "empty"
+ * state. The board and dice are empty and the cube shows 1.
+ */
+ KBgStatus();
+
+ /**
+ * Constructor from a FIBS rawboard string
+ */
+ KBgStatus(const QString &rawboard);
+
+ /**
+ * Copy constructor
+ */
+ KBgStatus(const KBgStatus &rhs);
+
+ /**
+ * Assignment operator
+ */
+ KBgStatus& operator=(const KBgStatus &rhs);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgStatus();
+
+
+ /*
+ * The absolute value of the return value is the number of
+ * checkers on the i-th field (or zero if i is out of
+ * bounds). If the return value has the same sign as the
+ * current color of US, it belongs to US, otherwise it belongs
+ * to THEM.
+ */
+ int board(const int &i) const;
+
+ /*
+ * The absolute value of the return value is the number of
+ * checkers on the home of player w (or zero if i is out of
+ * bounds). If the return value has the same sign as the
+ * current color of US, it belongs to US, otherwise it belongs
+ * to THEM.
+ *
+ * The encoding of the color is slighly redundant. See also board(...).
+ */
+ int home(const int &w = US) const;
+
+ /*
+ * The absolute value of the return value is the number of
+ * checkers on the bar of player w (or zero if i is out of
+ * bounds). If the return value has the same sign as the
+ * current color of US, it belongs to US, otherwise it belongs
+ * to THEM.
+ *
+ * The encoding of the color is slighly redundant. See also board(...).
+ */
+ int bar(const int &w = US) const;
+
+ /*
+ * Return the current color of player w. If w is invalid, the
+ * color of US is returned. The return value will be either
+ * Black or White.
+ */
+ int color(const int& w = US) const;
+
+ /*
+ * Returns the current direction of the game. It is -1 if US
+ * plays from 24 to 1 and +1 if US plays from 1 to 24.
+ */
+ int direction() const;
+
+ /*
+ * Returns the n-th dice of player w. If w is invalid or if n
+ * is out of bounds, return zero. Also, if the dice haven't
+ * been set, zero is returned.
+ */
+ int dice(const int &w, const int &n) const;
+
+ /*
+ * Returns the value of the cube. If w can double, the return
+ * value is positive, if w may not double, the negative value
+ * of the cube is returned. If w is not legal, zero is
+ * returned.
+ */
+ int cube(const int &w = US) const;
+
+ /*
+ * Return the points of w in th ecurrent game. Negative values
+ * indicate that either w was not a legal player ID or that
+ * the engine doesn't have any information on points.
+ */
+ int points(const int &w) const;
+
+ /*
+ * Return the name of player w. If w is out of bounds or if
+ * the player names have not been set, QString::null is
+ * returned.
+ */
+ QString player(const int &w = US) const;
+
+ /*
+ * Return the length of the game. Negative values should be used to
+ * indicate that the game is over. Zero indicates that the game is
+ * unlimited.
+ */
+ int length() const;
+
+ /*
+ * Return whose turn it is. The possible return values are US,
+ * THEM, and NONE.
+ */
+ int turn() const;
+
+ /*
+ * Return true if the cube has just been offered. If this
+ * information is not available or if this is not the case,
+ * return false.
+ */
+ bool doubled() const;
+
+ /*
+ * Set the number of checkers of player w on the i-th field to
+ * the absolute value of v. If either i or w are out of bound,
+ * nothing is done.
+ *
+ * Internally, positive numbers are stored for US and negative
+ * ones for THEM. While this coding is redundant, it is
+ * consistent with the storing of board positions.
+ */
+ void setBoard(const int &i, const int &w, const int &v);
+
+ /*
+ * Set the number of checkers on the home of player w to the
+ * absolute value of v. If w is out of bound, nothing is done.
+ *
+ * Internally, positive numbers are stored for US and negative
+ * ones for THEM. While this coding is redundant, it is
+ * consistent with the storing of board positions. See also
+ * setBoard(...).
+ */
+ void setHome(const int &w = US, const int &v = 0);
+
+ /*
+ * Set the number of checkers on the bar of player w to the
+ * absolute value of v. If w is out of bound, nothing is done.
+ *
+ * Internally, positive numbers are stored for US and negative
+ * ones for THEM. While this coding is redundant, it is
+ * consistent with the storing of board positions. See also
+ * setBoard(...).
+ */
+ void setBar(const int &w = US, const int &v = 0);
+
+ /*
+ * This function sets the color of player w to c. Negative
+ * values of c translate to Black for US (and White for
+ * THEM). Non-negative values for c translate to White for US
+ * and Black for THEM.
+ */
+ void setColor(const int& col, const int& w = US);
+
+ /*
+ * Set the direction of the game. If dir is negative, US plays
+ * from 24 to 1. If dir is positive, US plays from 1 to 24.
+ */
+ void setDirection(const int &dir);
+
+ /*
+ * Set the n-th dice of player w to v. Nothing is done if w is
+ * invalid or if n is out of bounds. If v is invalid, the
+ * value zero is assigned (i.e., the dice is invalidated).
+ */
+ void setDice(const int &w, const int &n, const int &v);
+
+ /*
+ * Set the cube to c us indicates if US can double, them
+ * indicates if THEM can double.
+ *
+ * This function is depreciated.
+ */
+ void setCube(const int &c, const bool &us, const bool &them);
+
+ /*
+ * Set the cube to c, which must be a legal value (i.e., a
+ * power of 2). w indicates who can double. Legal values are
+ * NONE, US, THEM, and BOTH.
+ */
+ void setCube(const int &c, const int &w);
+
+ /*
+ * Set the points of w in the current game to p. Nothing is
+ * done if w is illegal.
+ */
+ void setPoints(const int &w, const int &p);
+
+ /*
+ * Set the name of player w to name. If w is out of bound,
+ * nothing is done.
+ */
+ void setPlayer(const int &w, const QString &name);
+
+ /*
+ * Set the length of the game. Negative values should be used to
+ * indicate that the game is over. Zero indicates that the game
+ * is unlimited.
+ */
+ void setLength(const int &l);
+
+ /*
+ * Set the turn to w. Legal values for w are US, THEM, and
+ * NONE (which should indicate that the game is over).
+ */
+ void setTurn(const int &w);
+
+ /*
+ * Return the number of possible moves basesd on the current
+ * dice, checkers, etc.
+ */
+ int moves() const;
+
+ private:
+
+ /*
+ * Determine if there is any possibility to move a steps
+ * anywhere starting from start or later into direction
+ * dir in the game given by sc.
+ */
+ bool movePossible(KBgStatus &sc, int a, int start, int dir) const;
+
+ /*
+ * Copy constr. and assignment share a lot of code.
+ */
+ void copy(const KBgStatus &rhs);
+
+ /*
+ * Private variables with self-expalanatory names.
+ */
+ QString player_[2];
+
+ int board_[26], home_[2], bar_[2], dice_[2][2], points_[2];
+ int color_, direction_, cube_, length_, turn_;
+ int doubled_;
+
+ bool maydouble_[2];
+};
+
+#endif // KBGSTATUS_H
diff --git a/kbackgammon/kbgtextview.cpp b/kbackgammon/kbgtextview.cpp
new file mode 100644
index 00000000..b99a2f27
--- /dev/null
+++ b/kbackgammon/kbgtextview.cpp
@@ -0,0 +1,104 @@
+/* Yo Emacs, this -*- C++ -*-
+
+ Copyright (C) 1999,2000 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.
+
+*/
+
+#include "kbgtextview.moc"
+#include "kbgtextview.h"
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kcmenumngr.h>
+#include <kfontdialog.h>
+#include <qfont.h>
+#include <kapplication.h>
+
+#include <iostream>
+
+// == advanced text control ====================================================
+
+/*
+ * Constructor
+ */
+KBgTextView::KBgTextView(QWidget *parent, const char *name)
+ : KTextBrowser(parent, name)
+{
+ clear();
+ setLinkUnderline(true);
+}
+
+/*
+ * Destructor
+ */
+KBgTextView::~KBgTextView()
+{
+ // empty
+}
+
+/*
+ * Write the string l to the TextView and put the cursor at the end of
+ * the current text
+ */
+void KBgTextView::write(const QString &l)
+{
+ append("<font face=\"" + font().family() + "\">" + l + "</font><br>\n");
+ scrollToBottom();
+}
+
+/*
+ * Clears the view by overwriting the text with an empty string.
+ */
+void KBgTextView::clear()
+{
+ setText("");
+}
+
+/*
+ * Open a font-selection dialog.
+ */
+void KBgTextView::selectFont()
+{
+ QFont f = font();
+ KFontDialog::getFont(f, false, this, true);
+ setFont(f);
+}
+
+/*
+ * Restore the previously stored settings
+ */
+void KBgTextView::readConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ // nothing to restore
+}
+
+/*
+ * Save the current settings to disk
+ */
+void KBgTextView::saveConfig()
+{
+ KConfig* config = kapp->config();
+ config->setGroup(name());
+
+ // nothing to save
+}
+
+// EOF
diff --git a/kbackgammon/kbgtextview.h b/kbackgammon/kbgtextview.h
new file mode 100644
index 00000000..887136f4
--- /dev/null
+++ b/kbackgammon/kbgtextview.h
@@ -0,0 +1,81 @@
+/* Yo Emacs, this -*- C++ -*-
+
+ Copyright (C) 1999,2000 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.
+
+*/
+
+#ifndef __KBGTEXTVIEW_H
+#define __KBGTEXTVIEW_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ktextbrowser.h>
+#include <qstring.h>
+
+
+/**
+ * A small extension to the QTextView control.
+ */
+class KBgTextView : public KTextBrowser
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor
+ */
+ KBgTextView(QWidget *parent = 0, const char *name = 0);
+
+ /**
+ * Destructor
+ */
+ virtual ~KBgTextView();
+
+public slots:
+
+ /**
+ * Restore previously saved setting or provides defaults
+ */
+ void readConfig();
+
+ /**
+ * Save current settings
+ */
+ void saveConfig();
+
+ /**
+ * Simple interface to the non-slot function selectFont()
+ */
+ void selectFont();
+
+ /**
+ * Clears the view by setting the text to ""
+ */
+ void clear();
+
+ /**
+ * Write the string at the end of the buffer and scroll to
+ * the end
+ */
+ void write(const QString &);
+};
+
+#endif // __KBGTEXTVIEW_H
diff --git a/kbackgammon/main.cpp b/kbackgammon/main.cpp
new file mode 100644
index 00000000..fe20cc7c
--- /dev/null
+++ b/kbackgammon/main.cpp
@@ -0,0 +1,67 @@
+/* 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.
+
+*/
+
+#include <kapplication.h>
+#include <qstring.h>
+#include <klocale.h>
+#include <kaboutdata.h>
+#include <kcmdlineargs.h>
+
+#include "kbg.h"
+#include "version.h"
+
+static const char description[] = I18N_NOOP("A Backgammon program for KDE");
+static const char notice[] = I18N_NOOP("This is a graphical backgammon program. It supports "
+ "backgammon games\nwith other players, games against "
+ "computer engines like GNU bg and even\n"
+ "on-line games on the 'First Internet Backgammon Server'.");
+
+/*
+ * Main program doesn't do much - more or less standard stuff. Right
+ * after creating a KBg object, control is passed to it.
+ */
+int main(int argc, char *argv[])
+{
+ KAboutData about(PROG_NAME, I18N_NOOP("KBackgammon"), PROG_VERSION, description,
+ KAboutData::License_GPL, "(C) 1999-2001 Jens Hoefkens", notice);
+
+ about.addAuthor ("Jens Hoefkens", I18N_NOOP("Author & maintainer"),
+ "jens@hoefkens.com");
+
+ about.addCredit ("Bo Thorsen", I18N_NOOP("Initial anti-aliasing of the board"),
+ "gobo@imada.sdu.dk");
+
+ KCmdLineArgs::init(argc, argv, &about);
+
+ KApplication app;
+ KGlobal::locale()->insertCatalogue("libkdegames");
+
+ if (app.isRestored())
+ RESTORE(KBg)
+ else {
+ KBg* widget = new KBg;
+ app.setMainWidget(widget);
+ widget->readConfig();
+ widget->show();
+ }
+ return app.exec();
+}
+
diff --git a/kbackgammon/pics/Makefile.am b/kbackgammon/pics/Makefile.am
new file mode 100644
index 00000000..faf4aad5
--- /dev/null
+++ b/kbackgammon/pics/Makefile.am
@@ -0,0 +1,6 @@
+pics_DATA = kbackgammon-double.xpm kbackgammon-chat.png
+
+picsdir = $(kde_datadir)/kbackgammon/pics
+
+EXTRA_DIST = $(pics_DATA)
+
diff --git a/kbackgammon/pics/kbackgammon-chat.png b/kbackgammon/pics/kbackgammon-chat.png
new file mode 100644
index 00000000..65369f6d
--- /dev/null
+++ b/kbackgammon/pics/kbackgammon-chat.png
Binary files differ
diff --git a/kbackgammon/pics/kbackgammon-double.xpm b/kbackgammon/pics/kbackgammon-double.xpm
new file mode 100644
index 00000000..c8ef69b0
--- /dev/null
+++ b/kbackgammon/pics/kbackgammon-double.xpm
@@ -0,0 +1,29 @@
+/* XPM */
+static char *kbackgammon[]={
+"22 22 4 1",
+". c None",
+"a c #000000",
+"b c #a0a0a0",
+"# c #c3c3c3",
+"......................",
+"......................",
+"......................",
+"......................",
+"......................",
+".............###......",
+"............aaaaa.....",
+"...........aab..aa....",
+"...........a....aa....",
+"...............aaa....",
+"...aa..aa.....aaa.....",
+"...aabaab....aaa......",
+"....aaa.....aab.......",
+"....aaa....aab........",
+"...aa.aa...aa.........",
+"..aa...aa..aaaaaaa....",
+"...........baaaaab....",
+"......................",
+"......................",
+"......................",
+"......................",
+"......................"};
diff --git a/kbackgammon/sounds/Makefile.am b/kbackgammon/sounds/Makefile.am
new file mode 100644
index 00000000..ae7f255f
--- /dev/null
+++ b/kbackgammon/sounds/Makefile.am
@@ -0,0 +1,8 @@
+
+sounds_DATA = kbackgammon-lost.wav kbackgammon-won.wav kbackgammon-roll.wav kbackgammon-move.wav
+
+soundsdir = $(kde_datadir)/kbackgammon/sounds
+
+EXTRA_DIST = $(sounds_DATA)
+
+
diff --git a/kbackgammon/sounds/kbackgammon-lost.wav b/kbackgammon/sounds/kbackgammon-lost.wav
new file mode 100644
index 00000000..a27c8e15
--- /dev/null
+++ b/kbackgammon/sounds/kbackgammon-lost.wav
Binary files differ
diff --git a/kbackgammon/sounds/kbackgammon-move.wav b/kbackgammon/sounds/kbackgammon-move.wav
new file mode 100644
index 00000000..e867f675
--- /dev/null
+++ b/kbackgammon/sounds/kbackgammon-move.wav
Binary files differ
diff --git a/kbackgammon/sounds/kbackgammon-roll.wav b/kbackgammon/sounds/kbackgammon-roll.wav
new file mode 100644
index 00000000..29744c92
--- /dev/null
+++ b/kbackgammon/sounds/kbackgammon-roll.wav
Binary files differ
diff --git a/kbackgammon/sounds/kbackgammon-won.wav b/kbackgammon/sounds/kbackgammon-won.wav
new file mode 100644
index 00000000..7fd28abf
--- /dev/null
+++ b/kbackgammon/sounds/kbackgammon-won.wav
Binary files differ
diff --git a/kbackgammon/version.h b/kbackgammon/version.h
new file mode 100644
index 00000000..11d5dbec
--- /dev/null
+++ b/kbackgammon/version.h
@@ -0,0 +1,32 @@
+/* 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.
+
+*/
+
+
+#ifndef __KBG_VERSION_H
+#define __KBG_VERSION_H
+
+
+#define PROG_NAME "kbackgammon"
+#define PROG_VERSION "2.6.0"
+
+#define PROG_COOKIE 10500 // see also kdebug.areas
+
+#endif // __KBG_VERSION_H