diff options
Diffstat (limited to 'kbackgammon')
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], "<"); + line.replace(pat[HTML_gt], ">"); + + /* + * FIBS sometimes sends tabs, where it should send 8 spaces... + */ + line.replace(pat[TabChar], " "); + + switch (rxStatus) { + + case RxConnect: + handleMessageConnect(line, rawline); + break; + + case RxMotd: + handleMessageMotd(line); + return; + + case RxWhois: + handleMessageWhois(line); + break; + + case RxRating: + handleMessageRating(line); + break; + + case RxNewLogin: + handleMessageNewLogin(line); + break; + + case RxIgnore: + /* + * Ignore _ALL_ incoming strings - this is needed during the + * login phase, when the message box is open. + */ + break; + + case RxGoodbye: + /* + * Receive the logout sequence. The string will be flushed by the + * disconnectFIBS() callback + */ + rxCollect += QString("<font color=\"blue\"><pre>") + line + "</pre></font><br>"; + break; + + case RxNormal: + handleMessageNormal(line, rawline); + break; + + default: + /* + * This is a serious problem - latin1() is fine since the line comes from FIBS. + */ + std::cerr << "PROBLEM in KBgEngineFIBS::handleServerData: " << line.latin1() << std::endl; + } +} + +/* + * Handle messages during the RxWhois state + */ +void KBgEngineFIBS::handleMessageWhois(const QString &line) +{ + rxCollect += "<br> " + line; + if (line.contains(pat[WhoisE1]) || line.contains(pat[WhoisE2])) { + rxStatus = RxNormal; + emit infoText("<font color=\"darkgreen\">" + rxCollect + "<br></font>"); + } +} + +/* + * Handle messages during the RxRating state + */ +void KBgEngineFIBS::handleMessageRating(const QString &line) +{ + rxCollect += "<br>" + line; + if (line.contains(pat[EndRate]) && ++rxCount == 2) { + emit infoText("<font color=\"blue\">" + rxCollect + "<br></font>"); + rxStatus = RxNormal; + } +} + +/* + * Handle messages during the RxMotd state + */ +void KBgEngineFIBS::handleMessageMotd(const QString &line) +{ + if (line.contains(pat[MotdEnd])) { + rxStatus = RxNormal; + emit infoText("<font color=\"blue\"><pre>" + rxCollect + "</pre></font>"); + /* + * just to be on the safe side, we set the value of boardstyle. + * we do it here, since this is reasonably late in the login + * procedure + */ + emit serverString("set boardstyle 3"); + } else { + QString tline = line; + tline.replace(pat[BoxHori], "<br><hr>"); + tline.replace(pat[BoxVer1], ""); + tline.replace(pat[BoxVer2], ""); + rxCollect += "<br>" + tline; + } +} + +/* + * Handle messages during the RxConnect state + */ +void KBgEngineFIBS::handleMessageConnect(const QString &line, const QString &rawline) +{ + /* + * Two possibilities: either we are logged in or we sent bad password/login + */ + if (line.contains("login:")) { + /* + * This can only happen if the password/login is wrong. + */ + if (rxCollect.isEmpty()) { + rxStatus = RxIgnore; + int ret = KMessageBox::warningContinueCancel + ((QWidget *)parent(), i18n("There was a problem with " + "your login and password. " + "You can reenter\n" + "your login and password and " + "try to reconnect."), + i18n("Wrong Login/Password"), + i18n("Reconnect")); + if (ret == KMessageBox::Continue) { + infoFIBS[FIBSUser] = ""; + infoFIBS[FIBSPswd] = ""; + login = true; + connectFIBS(); // will reset the rxStatus + } else { + rxStatus = RxConnect; + emit serverString(""); + emit serverString(""); + } + return; + } + emit infoText("<hr><pre>" + rxCollect + "</pre><br>"); + rxCollect = ""; + return; + } + + /* + * Ok, we are logged in! Now receive personal information. These + * are completely useless but what the heck. + */ + if (line.contains(pat[Welcome])) { + char p[3][256]; + time_t tmp; + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%li%255s", p[0], p[1], &tmp, p[2]); + if (words >= 4) { + QDateTime d; d.setTime_t(tmp); + QString text = i18n("%1, last logged in from %2 at %3.").arg(p[1]).arg(p[2]).arg(d.toString()); + emit infoText("<hr><br>" + text); + playerlist->setName(p[1]); + } + return; + } + + /* + * Initial parsing of user options and making sure that settings needed + * by us are at the correct value. We use and ignore values according + * to the following list: + * + * p[ 0] - CLIP_OWN_INFO + * p[ 1] - name -- IGNORE + * OptAllowPip + * n[ 0] - autoboard -- IGNORE + * OptAutoDouble + * OptAutoMove + * n[ 1] - away -- IGNORE + * n[ 2] - bell -- IGNORE + * OptCrawford + * n[ 3] - double -- IGNORE + * n[ 4] - expierience -- IGNORE + * OptGreedy + * n[ 6] - moreboards -- IGNORE and set to YES + * OptMoves + * n[ 8] - notify -- IGNORE and set to YES + * rating - rating -- IGNORE + * OptRatings + * OptReady + * n[10] - redoubles -- IGNORE + * n[11] - report -- IGNORE and set to YES + * OptSilent + * p[3] - timezone + * + */ + if (line.contains(pat[OwnInfo])) { + + rxStatus = RxNormal; + + int fibsOptions[NumFIBSOpt]; + + char p[3][256]; + int n[12]; + double rating; + + // Using latin1() is okay, since the string comes from FIBS. + int words = sscanf (line.latin1(), "%255s%255s%i%i%i%i%i%i%i%i%i%i%i%i%i%lf%i%i%i%i%i%255s", + p[0], p[1], + &fibsOptions[OptAllowPip], + &n[0], + &fibsOptions[OptDouble], + &fibsOptions[OptAutoMove], // equivalent to OptDouble, can be ignored + &n[1], &n[2], + &fibsOptions[OptCrawford], + &n[3], &n[4], + &fibsOptions[OptGreedy], + &n[6], + &fibsOptions[OptMoves], + &n[8], + &rating, + &fibsOptions[OptRatings], + &fibsOptions[OptReady], + &n[10], &n[11], + &fibsOptions[OptSilent], + p[2]); + + if (words >= 22 && n[6] != 1) { + /* + * need to get boards after new dice have arrived + */ + emit infoText("<font color=\"red\">" + i18n("The moreboards toggle has been set.") + "</font>"); + emit serverString("toggle moreboards"); + } + if (words >= 22 && n[8] != 1) { + /* + * need to know who logs out + */ + emit infoText("<font color=\"red\">" + i18n("The notify toggle has been set.") + "</font>"); + emit serverString("toggle notify"); + } + if (words >= 22 && n[11] != 1) { + /* + * want to know who starts playing games + */ + emit infoText("<font color=\"red\">" + i18n("The report toggle has been set.") + "</font>"); + emit serverString("toggle report"); + } + + /* + * Set the correct toggles in the options menu + */ + fibsOpt[OptReady]->setChecked(fibsOptions[OptReady]); + fibsOpt[OptDouble]->setChecked(!fibsOptions[OptDouble]); + fibsOpt[OptRatings]->setChecked(fibsOptions[OptRatings]); + + return; + } + + /* + * The beginning of a new login procedure starts starts here + */ + if (line.contains(pat[OneName])) { + rxStatus = RxNewLogin; + emit infoText(QString("<font color=\"red\">") + rxCollect + "</font>"); + rxCollect = ""; + QString tmp = rawline; + handleServerData(tmp); + return; + } + + /* + * Still in the middle of the login sequence, still collecting information + */ + rxCollect += "<br>" + line; +} + +/* + * Handle messages during the RxNewLogin state + */ +void KBgEngineFIBS::handleMessageNewLogin(const QString &line) +{ + /* + * Request the new login + */ + if (line.contains(pat[OneName])) { + emit serverString(QString("name ") + infoFIBS[FIBSUser]); + return; + } + /* + * Ooops, user name already exists + */ + if (line.contains(pat[OthrNam])) { + QString text = i18n("The selected login is alreay in use! Please select another one."); + bool ret, first = true; + QString msg; + + do { + msg = (KLineEditDlg::getText(text, infoFIBS[FIBSUser], &ret, + (QWidget *)parent())).stripWhiteSpace(); + if (first) { + text += i18n("\n\nThe login may not contain spaces or colons!"); + first = false; + } + } while (msg.contains(' ') || msg.contains(':')); + + if (ret) { + infoFIBS[FIBSUser] = msg; + emit serverString("name " + msg); + } else + emit serverString("bye"); + + return; + } + /* + * first time we send the password + */ + if (line.contains(pat[YourNam])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * second time we send the password + */ + if (line.contains(pat[GivePwd])) { + emit serverString(infoFIBS[FIBSPswd]); + return; + } + /* + * at this point we are done creating the account + */ + if (line.contains(pat[RetypeP])) { + + QString text = i18n("Your account has been created. Your new login is <u>%1</u>. To fully activate " + "this account, I will now close the connection. Once you reconnect, you can start " + "playing backgammon on FIBS.").arg(infoFIBS[FIBSUser]); + emit infoText("<br><hr><font color=\"blue\">" + text + "</font><br><hr>"); + emit serverString("bye"); + rxStatus = RxNormal; + rxCollect = ""; + return; + } + return; +} + +/* + * Handle all normal messages - during the RxNormal state + */ +void KBgEngineFIBS::handleMessageNormal(QString &line, QString &rawline) +{ + + // - ignored ---------------------------------------------------------------------- + + /* + * For now, the waves are ignored. They should probably go into + * the chat window -- but only optional + */ + if (line.contains(pat[OneWave]) || line.contains(pat[TwoWave]) || line.contains(pat[YouWave])) { + + return; + } + + /* + * These messages used to go into the games window. If KBackgammon + * ever gets a games window, they should be in there. For now, they + * are ignored. + */ + else if (line.contains(pat[GameBG1]) || line.contains(pat[GameBG2]) || line.contains(pat[GameRE1]) || + line.contains(pat[GameRE2]) || line.contains(pat[GameEnd])) { + + return; + } + + /* + * Artefact caused by the login test procedure utilized. + */ + else if (line.contains(pat[NoLogin])) { + + return; + } + + /* + * Connection keep-alive response + */ + else if (line.contains(pat[KeepAlv])) { + + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Chat and personal messages - note that the chat window sends these messages + * back to us so we can display them if the user wants that. + */ + else if (line.contains(pat[ChatSay]) || line.contains(pat[ChatSht]) || line.contains(pat[ChatWis]) || + line.contains(pat[ChatKib]) || line.contains(pat[SelfSay]) || line.contains(pat[SelfSht]) || + line.contains(pat[SelfWis]) || line.contains(pat[SelfKib]) || line.contains(pat[SelfSlf]) || + line.contains(pat[MsgPers]) || line.contains(pat[MsgDeli]) || line.contains(pat[MsgSave])) { + + emit chatMessage(line); + return; + } + + // -------------------------------------------------------------------------------- + + /* + * Beginning of games. In all these cases we are playing and not watching. + */ + else if (line.contains(pat[MatchB1]) || line.contains(pat[MatchB2])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + } + else if (line.contains(pat[MatchB3]) || line.contains(pat[MatchB4])) { + + if (useAutoMsg[MsgBeg] && !autoMsg[MsgBeg].stripWhiteSpace().isEmpty()) + emit serverString("kibitz " + autoMsg[MsgBeg]); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * The help should be handled separately. A fairly complete implementation of a + * help parsing can be found in KFibs. + */ + else if (line.contains(pat[HelpTxt])) { + + // do nothing + } + + // -------------------------------------------------------------------------------- + + /* + * Simple cases without the need for many comments... + */ + else if (line.contains(pat[RawBord])) { + + /* + * Save the board string and create a new game state + */ + KBgStatus *st = new KBgStatus(currBoard = rawline); + + /* + * Save important state data and stop the timeout + */ + ct->stop(); + undoCounter = 0; + + pname[US ] = st->player(US); + pname[THEM] = st->player(THEM); + + playing = (QString("You") == pname[US]); + + toMove = st->moves(); + + /* + * Update the caption string + */ + if (st->turn() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - game over").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else if (st->length() < 0) + caption = i18n("%1 (%2) vs. %3 (%4) - unlimited match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)); + else + caption = i18n("%1 (%2) vs. %3 (%4) - %5 point match").arg(pname[US]). + arg(st->points(US)).arg(pname[THEM]).arg(st->points(THEM)). + arg(st->length()); + + emit statText(caption); + + /* + * Emit information and drop the state object + */ + emit allowMoving(playing && (st->turn() == US)); + emit newState(*st); + + delete st; + + /* + * Set the actions correctly + */ + emit allowCommand(Load, true ); + emit allowCommand(Undo, false); + emit allowCommand(Redo, false); + emit allowCommand(Done, false); + + return; + } + else if (line.contains(pat[PlsMove]) || line.contains(pat[YouMove])) { + + KNotifyClient::event("move", i18n("Please make your move")); + + } + + // -------------------------------------------------------------------------------- + + /* + * Being away and coming back + */ + else if (line.contains(pat[YouAway])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, true); + actBack->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu entry to join the match)"); + } + else if (line.contains(pat[YouBack])) { + + emit changePlayerStatus(infoFIBS[FIBSUser], KFibsPlayerList::Away, false); + actBack->setEnabled(false); + actAway->setEnabled(true); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the response of the user responding to double or resign + */ + else if (line.contains(pat[YouGive]) || line.contains(pat[RejCont]) || line.contains(pat[AcptWin])) { + + actAccept->setEnabled(false); + actReject->setEnabled(false); + } + + // -------------------------------------------------------------------------------- + + /* + * Catch the responses to newly set toggles + */ + else if (line.contains(pat[GreedyY]) || line.contains(pat[GreedyN])) { + + fibsOpt[OptGreedy]->setChecked(line.contains(pat[GreedyY])); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[DoubleY]) || line.contains(pat[DoubleN])) { + + fibsOpt[OptDouble]->setChecked(line.contains(pat[DoubleY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + else if (line.contains(pat[RatingY]) || line.contains(pat[RatingN])) { + + fibsOpt[OptRatings]->setChecked(line.contains(pat[RatingY])); + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * It's our turn to roll or double + */ + else if (line.contains(pat[YouTurn]) || line.contains(pat[YouRoll])) { + + emit allowCommand(Cube, playing); + emit allowCommand(Roll, playing); + + emit statText(caption); // force a pip count recomputation by the board + + KNotifyClient::event("roll or double", i18n("It's your turn to roll the dice or double the cube")); + } + + // -------------------------------------------------------------------------------- + + /* + * Got an invitation for a match + */ + else if (line.contains(pat[Invite0]) || line.contains(pat[Invite2]) || line.contains(pat[Invite3])) { + + rxCollect = rawline.left(rawline.find(' ')); + emit serverString("rawwho " + rxCollect); + + if (line.contains(pat[Invite0])) { + rawline.replace(pat[Invite1], ""); + rawline = rxCollect + " "+ rawline.left(rawline.find(' ')); + } else if (line.contains(pat[Invite2])) { + rawline = rxCollect + " r"; + } else if (line.contains(pat[Invite3])) { + invitations += rxCollect + " u"; + } + invitations += rawline; + return; // will be printed once the rawwho is received + } + + // - rx status changes ------------------------------------------------------------ + + else if (line.contains(pat[WhoisBG])) { + rxStatus = RxWhois; + rxCollect = QString("<br><u>") + line + "</u>"; + return; + } + else if (line.contains(pat[MotdBeg])) { + rxStatus = RxMotd; + rxCollect = ""; + return; + } + else if (line.contains(pat[BegRate])) { + rxStatus = RxRating; + rxCount = 0; + rxCollect = "<br>" + line; + return; + } + else if (line.contains(pat[Goodbye])) { + rxStatus = RxGoodbye; + rxCollect = "<br><hr><br>"; + handleServerData(rawline); // danger: recursion! + return; + } + + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + // -------------------------------------------------------------------------------- + + /* + * Continue a mutli game match? We have to either leave or continue + */ + else if (line.contains(pat[ConLeav])) { + actConti->setEnabled(true); + actLeave->setEnabled(true); + line.append("<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to leave or continue the match)")); + } + /* + * Beginning and end of user updates + */ + else if (line.contains(pat[WhoInfo])) { + rawline.replace(pat[WhoInfo], ""); + if (rawline.contains(QRegExp("^" + infoFIBS[FIBSUser] + " "))) { + int ready; + // Using latin1() is fine, since the string is coming from FIBS. + sscanf(rawline.latin1(), "%*s %*s %*s %i %*s %*s %*s %*s %*s %*s %*s %*s", &ready); + fibsOpt[OptReady]->setChecked(ready); + } + emit fibsWhoInfo(rawline); + return; + } + else if (line.contains(pat[WhoEnde])) { + emit fibsWhoEnd(); + return; + } + /* + * This message is ignored. The instruction is given elsewhere (and slightly + * delayed in the flow of time). + */ + if (line.contains(pat[TypJoin])) { + return; + } + /* + * Watching other players + */ + else if (line.contains(pat[BegWtch])) { + emit allowCommand(Load, true); + rawline.replace(pat[BegWtch], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + load(); + } + else if (line.contains(pat[EndWtch])) { + emit gameOver(); + } + /* + * Blinding of players, the actual blind is handled by + * the player list + */ + else if (line.contains(pat[BegBlnd])) { + rawline.replace(pat[BegBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, true); + line = "<font color=\"red\">" + line + "</font>"; + } + else if (line.contains(pat[EndBlnd])) { + rawline.replace(pat[EndBlnd], ""); + rawline.truncate(rawline.length()-1); + emit changePlayerStatus(rawline, KFibsPlayerList::Blind, false); + line = "<font color=\"red\">" + line + "</font>"; + } + /* + * Starting or reloading games or matches + */ + else if (line.contains(pat[BegGame])) { + rawline.replace(pat[BegGame], ""); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + else if (line.contains(pat[Reload1])) { + rawline.replace(pat[Reload1], ""); + rawline = rawline.left(rawline.find(' ')); + rawline.truncate(rawline.length()-1); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + else if (line.contains(pat[Reload2])) { + rawline.replace(pat[Reload2], ""); + emit fibsStartNewGame(rawline); + fibsOpt[OptDouble]->setChecked(true); + fibsOpt[OptGreedy]->setChecked(false); + actConti->setEnabled(false); + actLeave->setEnabled(false); + load(); + } + /* + * Opponent offered resignation or the cube. We have to accept + * or reject the offer. + */ + else if (line.contains(pat[RejAcpt])) { + actAccept->setEnabled(true); + actReject->setEnabled(true); + line += "<br><pre> </pre>" + i18n("(or use the corresponding menu " + "entry to accept or reject the offer)"); + } + /* + * This is strange: FIBS seems to not send a newline at the + * end of this pattern. So we work around that. + */ + else if (line.contains(pat[YouAcpt])) { + actAccept->setEnabled(false); + actReject->setEnabled(false); + rawline.replace(pat[YouAcpt], ""); + line.truncate(line.length()-rawline.length()); + if (!rawline.stripWhiteSpace().isEmpty()) { + handleServerData(rawline); + } + } + /* + * Ending of games + */ + else if (line.contains(pat[EndLose])) { + if (playing) { + KNotifyClient::event("game over l", i18n("Sorry, you lost the game.")); + if (useAutoMsg[MsgLos] && !autoMsg[MsgLos].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgLos]); + } + emit gameOver(); + } + else if (line.contains(pat[EndVict])) { + if (playing) { + KNotifyClient::event("game over w", i18n("Congratulations, you won the game!")); + if (useAutoMsg[MsgWin] && !autoMsg[MsgWin].stripWhiteSpace().isEmpty()) + emit serverString(QString("tell ") + pname[THEM] + " " + autoMsg[MsgWin]); + } + emit gameOver(); + } + else if (line.contains(pat[GameSav])) { + emit gameOver(); + } + /* + * User logs out. This has to be signalled to the player + * list. Get the true user names by working on the rawline. + */ + else if (line.contains(pat[UserLot])) { + rawline.replace(pat[UserLot], ""); + emit fibsLogout(rawline.left(rawline.find(' '))); + return; + } + /* + * Emit the name of the newly logged in user. + */ + else if (line.contains(pat[UserLin])) { + rawline.replace(pat[UserLin], ""); + emit fibsLogin(rawline.left(rawline.find(' '))); + return; + } + /* + * Special attention has to be paid to the proper setting of + * the 'boardstyle' variable, since we will not be able to display + * the board properly without it. + */ + else if (line.contains(pat[BoardSY])) { + // ignored + return; + } + else if (line.contains(pat[BoardSN])) { + emit serverString("set boardstyle 3"); + emit infoText(QString("<font color=\"red\"><br>") + + i18n("You should never set the 'boardstyle' variable " + "by hand! It is vital for proper functioning of " + "this program that it remains set to 3. It has " + "been reset for you.") + + "<br></font>"); + return; + } + /* + * This is the final fall through: if the line started with ** and + * hasn't been processed, make it red, since it is a server resp. + * to something we just did. + */ + else if (line.contains(pat[TwoStar])) { + line = "<font color=\"red\">" + line + "</font>"; + } + + // -------------------------------------------------------------------------------- + + /* + * Print whatever part of the line made it here + */ + emit infoText(line); +} + +// EOF + + +// == constructor, destructor and setup ======================================== + +/* + * Constructor + */ +KBgEngineFIBS::KBgEngineFIBS(QWidget *parent, QString *name, QPopupMenu *pmenu) + : KBgEngine(parent, name, pmenu) +{ + /* + * No connection, not playing, ready for login + */ + connection = new QSocket(parent, "fibs connection"); + playing = false; + login = true; + + connect(connection, SIGNAL(hostFound()), this, SLOT(hostFound())); + connect(connection, SIGNAL(connected()), this, SLOT(connected())); + connect(connection, SIGNAL(error(int)), this, SLOT(connError(int))); + connect(connection, SIGNAL(connectionClosed()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(delayedCloseFinished()), this, SLOT(connectionClosed())); + connect(connection, SIGNAL(readyRead()), this, SLOT(readData())); + + connect(this, SIGNAL(serverString(const QString &)), this, SLOT(sendData(const QString &))); + + /* + * No invitation dialog + */ + invitationDlg = 0; + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), this, SLOT(changeJoin(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), this, SLOT(cancelJoin(const QString &))); + connect(this, SIGNAL(gameOver()), this, SLOT(endGame())); + + /* + * Creating, initializing and connecting the player list + */ + playerlist = new KFibsPlayerList(0, "fibs player list"); + + connect(this, SIGNAL(fibsWhoInfo(const QString &)), playerlist, SLOT(changePlayer(const QString &))); + connect(this, SIGNAL(fibsLogout (const QString &)), playerlist, SLOT(deletePlayer(const QString &))); + connect(this, SIGNAL(fibsWhoEnd()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(fibsConnectionClosed()), playerlist, SLOT(stopUpdate())); + connect(this, SIGNAL(changePlayerStatus(const QString &, int, bool)), + playerlist, SLOT(changePlayerStatus(const QString &, int, bool))); + connect(playerlist, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(playerlist, SIGNAL(fibsInvite(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + + /* + * Create, initialize and connect the chat window + */ + chatWindow = new KBgChat(0, "chat window"); + + connect(this, SIGNAL(chatMessage(const QString &)), chatWindow, SLOT(handleData(const QString &))); + connect(this, SIGNAL(fibsStartNewGame(const QString &)), chatWindow, SLOT(startGame(const QString &))); + connect(this, SIGNAL(gameOver()), chatWindow, SLOT(endGame())); + connect(this, SIGNAL(fibsLogout (const QString &)), chatWindow, SLOT(deletePlayer(const QString &))); + connect(chatWindow, SIGNAL(fibsCommand(const QString &)), this, SLOT(handleCommand(const QString &))); + connect(chatWindow, SIGNAL(fibsRequestInvitation(const QString &)), this, SLOT(fibsRequestInvitation(const QString &))); + connect(chatWindow, SIGNAL(personalMessage(const QString &)), this, SLOT(personalMessage(const QString &))); + connect(playerlist, SIGNAL(fibsTalk(const QString &)), chatWindow, SLOT(fibsTalk(const QString &))); + + /* + * Creating, initializing and connecting the menu + * ---------------------------------------------- + */ + respMenu = new QPopupMenu(); + joinMenu = new QPopupMenu(); + cmdMenu = new QPopupMenu(); + optsMenu = new QPopupMenu(); + + /* + * Initialize the FIBS submenu - this is also put in the play menu + */ + conAction = new KAction(i18n("&Connect"), 0, this, SLOT( connectFIBS()), this); + newAction = new KAction(i18n("New Account"), 0, this, SLOT( newAccount()), this); + disAction = new KAction(i18n("&Disconnect"), 0, this, SLOT(disconnectFIBS()), this); + + conAction->setEnabled(true ); conAction->plug(menu); + disAction->setEnabled(false); disAction->plug(menu); + newAction->setEnabled(true ); newAction->plug(menu); + + menu->insertSeparator(); + + (invAction = new KAction(i18n("&Invite..."), 0, this, SLOT(inviteDialog()), this))->plug(menu); + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + cmdMenuID = menu->insertItem(i18n("&Commands"), cmdMenu); { + + (actAway = new KAction(i18n("Away"), 0, this, SLOT(away()), this))->plug(cmdMenu); + (actBack = new KAction(i18n("Back"), 0, this, SLOT(back()), this))->plug(cmdMenu); + + actAway->setEnabled(true); + actBack->setEnabled(false); + } + + /* + * Create the server side options. This is preliminary and needs more work. + * The available options are skewed, since they refelect the needs of the + * author. Contact jens@hoefkens.com if your favorite option is missing. + */ + optsMenuID = menu->insertItem(i18n("&Options"), optsMenu); { + + for (int i = 0; i < NumFIBSOpt; i++) + fibsOpt[i] = 0; + + fibsOpt[OptReady] = new KToggleAction(i18n("Ready to Play"), + 0, this, SLOT(toggle_ready()), this); + fibsOpt[OptRatings] = new KToggleAction(i18n("Show Rating Computations"), + 0, this, SLOT(toggle_ratings()), this); + fibsOpt[OptRatings]->setCheckedState(i18n("Hide Rating Computations")); + fibsOpt[OptGreedy] = new KToggleAction(i18n("Greedy Bearoffs"), + 0, this, SLOT(toggle_greedy()), this); + fibsOpt[OptDouble] = new KToggleAction(i18n("Ask for Doubles"), + 0, this, SLOT(toggle_double()), this); + + for (int i = 0; i < NumFIBSOpt; i++) + if (fibsOpt[i]) + fibsOpt[i]->plug(optsMenu); + + } + + /* + * Create and fill the response menu. This is for all these: type this or + * that messages from FIBS. + */ + respMenuID = menu->insertItem(i18n("&Response"), respMenu); { + + (actAccept = new KAction(i18n("Accept"), 0, this, SLOT(accept()), this))->plug(respMenu); + (actReject = new KAction(i18n("Reject"), 0, this, SLOT(reject()), this))->plug(respMenu); + + actAccept->setEnabled(false); + actReject->setEnabled(false); + + respMenu->insertSeparator(); + + (actConti = new KAction(i18n("Join"), 0, this, SLOT(match_conti()), this))->plug(respMenu); + (actLeave = new KAction(i18n("Leave"), 0, this, SLOT(match_leave()), this))->plug(respMenu); + + actConti->setEnabled(false); + actLeave->setEnabled(false); + } + + /* + * Create the join menu and do not fill it (this happens at first + * action setup. + */ + joinMenuID = menu->insertItem(i18n("&Join"), joinMenu); { + numJoin = -1; + + actJoin[0] = new KAction("", 0, this, SLOT(join_0()), this); + actJoin[1] = new KAction("", 0, this, SLOT(join_1()), this); + actJoin[2] = new KAction("", 0, this, SLOT(join_2()), this); + actJoin[3] = new KAction("", 0, this, SLOT(join_3()), this); + actJoin[4] = new KAction("", 0, this, SLOT(join_4()), this); + actJoin[5] = new KAction("", 0, this, SLOT(join_5()), this); + actJoin[6] = new KAction("", 0, this, SLOT(join_6()), this); + actJoin[7] = new KAction("", 0, this, SLOT(join_7()), this); + } + + menu->setItemEnabled(joinMenuID, false); + menu->setItemEnabled( cmdMenuID, false); + menu->setItemEnabled(respMenuID, false); + menu->setItemEnabled(optsMenuID, false); + + /* + * Continue with the FIBS menu + */ + menu->insertSeparator(); + + (listAct = new KToggleAction(i18n("&Player List"), 0, this, SLOT(showList()), this))->plug(menu); + (chatAct = new KToggleAction(i18n("&Chat"), 0, this, SLOT(showChat()), this))->plug(menu); + + connect(playerlist, SIGNAL(windowVisible(bool)), listAct, SLOT(setChecked(bool))); + connect(chatWindow, SIGNAL(windowVisible(bool)), chatAct, SLOT(setChecked(bool))); + + /* + * Create message IDs. This sets up a lot of regular expressions. + */ + initPattern(); + + /* + * Restore old settings + */ + readConfig(); + + // FIXME: open the child windows in start() and not here + + /* + * Update the menu actions + */ + listAct->setChecked(playerlist->isVisible()); + chatAct->setChecked(chatWindow->isVisible()); + + /* + * Initialize the keepalive timer FIXME: make this a setting + */ + keepalive = true; + + // FIXME: move the start to connect... + + keepaliveTimer = new QTimer(this); + connect(keepaliveTimer, SIGNAL(timeout()), this, SLOT(keepAlive())); + keepaliveTimer->start(1200000); +} + +/* + * Destructor deletes child objects if necessary + */ +KBgEngineFIBS::~KBgEngineFIBS() +{ + delete joinMenu; + delete respMenu; + delete cmdMenu; + delete optsMenu; + + delete connection; + delete invitationDlg; + + delete playerlist; + delete chatWindow; +} + + 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(" "), " "); + 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 Binary files differnew file mode 100644 index 00000000..794c94e0 --- /dev/null +++ b/kbackgammon/icons/hi128-app-kbackgammon.png diff --git a/kbackgammon/icons/hi16-app-kbackgammon.png b/kbackgammon/icons/hi16-app-kbackgammon.png Binary files differnew file mode 100644 index 00000000..39640210 --- /dev/null +++ b/kbackgammon/icons/hi16-app-kbackgammon.png diff --git a/kbackgammon/icons/hi16-app-kbackgammon_engine.png b/kbackgammon/icons/hi16-app-kbackgammon_engine.png Binary files differnew file mode 100644 index 00000000..bd2ba6a1 --- /dev/null +++ b/kbackgammon/icons/hi16-app-kbackgammon_engine.png diff --git a/kbackgammon/icons/hi22-app-kbackgammon.png b/kbackgammon/icons/hi22-app-kbackgammon.png Binary files differnew file mode 100644 index 00000000..882d0f9b --- /dev/null +++ b/kbackgammon/icons/hi22-app-kbackgammon.png diff --git a/kbackgammon/icons/hi32-app-kbackgammon.png b/kbackgammon/icons/hi32-app-kbackgammon.png Binary files differnew file mode 100644 index 00000000..8d4b3f1d --- /dev/null +++ b/kbackgammon/icons/hi32-app-kbackgammon.png diff --git a/kbackgammon/icons/hi32-app-kbackgammon_engine.png b/kbackgammon/icons/hi32-app-kbackgammon_engine.png Binary files differnew file mode 100644 index 00000000..871ef577 --- /dev/null +++ b/kbackgammon/icons/hi32-app-kbackgammon_engine.png diff --git a/kbackgammon/icons/hi48-app-kbackgammon.png b/kbackgammon/icons/hi48-app-kbackgammon.png Binary files differnew file mode 100644 index 00000000..b6b5f154 --- /dev/null +++ b/kbackgammon/icons/hi48-app-kbackgammon.png diff --git a/kbackgammon/icons/hi48-app-kbackgammon_engine.png b/kbackgammon/icons/hi48-app-kbackgammon_engine.png Binary files differnew file mode 100644 index 00000000..8291e82a --- /dev/null +++ b/kbackgammon/icons/hi48-app-kbackgammon_engine.png diff --git a/kbackgammon/icons/hi64-app-kbackgammon.png b/kbackgammon/icons/hi64-app-kbackgammon.png Binary files differnew file mode 100644 index 00000000..f76e4158 --- /dev/null +++ b/kbackgammon/icons/hi64-app-kbackgammon.png diff --git a/kbackgammon/icons/hi64-app-kbackgammon_engine.png b/kbackgammon/icons/hi64-app-kbackgammon_engine.png Binary files differnew file mode 100644 index 00000000..f29cb603 --- /dev/null +++ b/kbackgammon/icons/hi64-app-kbackgammon_engine.png 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>&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>&Command</text> + </Menu> + <Menu name="settings"><text>&Settings</text> + <Action name="options_textbar" append="show_merge"/> + </Menu> + <Menu name="help"><text>&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], + <urn, + &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 Binary files differnew file mode 100644 index 00000000..65369f6d --- /dev/null +++ b/kbackgammon/pics/kbackgammon-chat.png 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 Binary files differnew file mode 100644 index 00000000..a27c8e15 --- /dev/null +++ b/kbackgammon/sounds/kbackgammon-lost.wav diff --git a/kbackgammon/sounds/kbackgammon-move.wav b/kbackgammon/sounds/kbackgammon-move.wav Binary files differnew file mode 100644 index 00000000..e867f675 --- /dev/null +++ b/kbackgammon/sounds/kbackgammon-move.wav diff --git a/kbackgammon/sounds/kbackgammon-roll.wav b/kbackgammon/sounds/kbackgammon-roll.wav Binary files differnew file mode 100644 index 00000000..29744c92 --- /dev/null +++ b/kbackgammon/sounds/kbackgammon-roll.wav diff --git a/kbackgammon/sounds/kbackgammon-won.wav b/kbackgammon/sounds/kbackgammon-won.wav Binary files differnew file mode 100644 index 00000000..7fd28abf --- /dev/null +++ b/kbackgammon/sounds/kbackgammon-won.wav 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 |