diff options
Diffstat (limited to 'libksirtet/lib')
37 files changed, 4008 insertions, 0 deletions
diff --git a/libksirtet/lib/CHANGELOG b/libksirtet/lib/CHANGELOG new file mode 100644 index 00000000..84c03edb --- /dev/null +++ b/libksirtet/lib/CHANGELOG @@ -0,0 +1,39 @@ +0.1.8 (11 April 2001) + * better Player/Meeting "choose boxes" + * use KExtendedSocket (get rid of custom utilities) + and hopefully fix network game + +0.1.7 + * resize handle removed from statusbar of netmeeting dialog + +0.1.6 + * fixed a bug in key configuration + * players name access improved + * some internal cleanups + use of KDialogBase + +0.1.5 + * many bug fixes for network game + * added a framework for simple multiplayers games + * Wizard-like configuration + * enhanced keys configuration + +0.1.4 + * all data transport is now done via QDataStream (no more bitorder/storage + problems). + * QDataStream use make things easier from the user point of view (at least + I think so). + * big cleaning : the library restricts itself to data transport between + boards and to game configuration. The library doesn't want to and doesn't + have to manage things like game pause or gameover of a specific player ... + all those things must be done by the game programmer. + +0.1.3 + * ported to QT 2.0 (hard way : now we send QString over the network :) + +0.1.2 + * finally THE bug has been found (eight months later) ! + so network game seems stable. + * lots of bug fixes + +0.1.1 + * first code (June 1998 : pause in dvplt) diff --git a/libksirtet/lib/LICENSE b/libksirtet/lib/LICENSE new file mode 100644 index 00000000..6afe98e6 --- /dev/null +++ b/libksirtet/lib/LICENSE @@ -0,0 +1,18 @@ +kdemultiplayers library +----------------------- +Copyright (c) 1998-2001 Nicolas HADACEK ([email protected]) + + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Library 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 Library General Public License for more details. + +You should have received a copy of the GNU Library 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. diff --git a/libksirtet/lib/Makefile.am b/libksirtet/lib/Makefile.am new file mode 100644 index 00000000..890ac458 --- /dev/null +++ b/libksirtet/lib/Makefile.am @@ -0,0 +1,21 @@ +INCLUDES = $(all_includes) + +# Don't compile with hidden symbols since we are a library. +if disable_VISIBILITY +KDE_CXXFLAGS = -fvisibility=default +endif + +noinst_LTLIBRARIES = libksirtetmultiplayers.la + +noinst_HEADERS = defines.h types.h miscui.h wizard.h pline.h meeting.h \ + socket.h smanager.h internal.h keys.h mp_board.h mp_option.h \ + mp_interface.h mp_simple_types.h mp_simple_board.h \ + mp_simple_interface.h + +libksirtetmultiplayers_la_SOURCES = miscui.cpp types.cpp defines.cpp \ + socket.cpp smanager.cpp pline.cpp \ + wizard.cpp \ + meeting.cpp keys.cpp mp_interface.cpp internal.cpp \ + mp_simple_types.cpp mp_simple_board.cpp mp_simple_interface.cpp + +METASOURCES = AUTO diff --git a/libksirtet/lib/README b/libksirtet/lib/README new file mode 100644 index 00000000..442c9c30 --- /dev/null +++ b/libksirtet/lib/README @@ -0,0 +1,63 @@ +kdemultiplayers library +----------------------- +Copyright (c) 1998-2000 Nicolas HADACEK ([email protected]) +Distributed under the GNU Library General Public License + +Introduction +------------ +The "kdemultiplayers" library is an attempt to address the realization of +multiplayer games localy on a computer (the players have each a "board" on +the screen and they use the same keyboard) or/and by network (between several +computers). The implementation is not so simple but this library gives +a basic API which allows a lot of flexibility. + +Note: The headers which defines the API have names beginning with "mp". + +The library provides three services : + * a set of dialog widgets to allow the players to configure/create/join + a game. + * management of all the sending/receiving data between + player boards so you as a game programmer will *not* have to deal with + sockets or network programming. + * a framework for simple multiplayers game (for instance ksirtet) with a + very simple API ("mp_simple" headers). + +Semantics +--------- +"player" : an individual which take part in the game (it can also be an AI). +"AI" : artificial intelligence (a game that is played by a program). +"board" : there is one board per player ; it is the widget where the player + acts in the game. +"host" : a computer which hosts one or several players. +"server" : the host that has created the game. +"client" : a host that is not the server. + + +Basic description of the API +---------------------------- +From the game programmer point of view there should be no difference between +a local multiplayers game (one host with several players) and a network game +(several hosts with one or more players). + +Each player has a board which is a widget inherited from the class "LocalBoard" +(see "mp_board.h"). + +On each host there must be a class inherited from "MPInterface" +which manages local boards and the data transport with other hosts. +(see "mp_interface.h"). + +During the game at given intervals of time, the server asks data from clients +and from its local boards. After treating this data, the server dispatches back +results to all boards. + +General advice +-------------- +There should be no blocking operation or long computation as it will freeze +the user front-end and will also block the multiplayers data exchanges +which operate by timer signals. + +There are general rules to avoid such blocking : + * answer to X/QT events such as keyboard/mouse/window events with + short methods. + * use timer(s) to make things evolve in time or to break long + computation in shorter parts. diff --git a/libksirtet/lib/TODO b/libksirtet/lib/TODO new file mode 100644 index 00000000..54a924bc --- /dev/null +++ b/libksirtet/lib/TODO @@ -0,0 +1,18 @@ +* change to an event-driven data exchange framework (it currently uses a timer + on the server side) -> we probably need a way to ensure clients are not + dead (?) + +* grid/row layout of boards +* better dialogs ... +* check the 64bit fix in "types.h" + +* user help (add help button to wizard) +* tooltips !! +* API documentation + +* heavy test of network game +* test option widget in netmeeting (--> needs fixing in NetMeeting class) +* test of options for players and computers + +* keys configuration : probably need to change KKeyDialog (the msgbox for + duplicate keys is so annoying ...) + unused key finder diff --git a/libksirtet/lib/defines.cpp b/libksirtet/lib/defines.cpp new file mode 100644 index 00000000..7c897dd9 --- /dev/null +++ b/libksirtet/lib/defines.cpp @@ -0,0 +1,24 @@ +#include "defines.h" + +#include <klocale.h> + +void errorBox(const QString &msg1, const QString &msg2, QWidget *parent) +{ + QString str; + if ( msg2.isNull() ) str = msg1; + else str = i18n("%1:\n%2").arg(msg1).arg(msg2); + KMessageBox::error(parent, str); +} + +QString socketError(const KExtendedSocket *socket) +{ + return KExtendedSocket::strError(socket->status(), socket->systemError()); +} + +bool checkSocket(int res, const KExtendedSocket *socket, + const QString &msg, QWidget *parent) +{ + if ( res==0 ) return false; + errorBox(msg, socketError(socket), parent); + return true; +} diff --git a/libksirtet/lib/defines.h b/libksirtet/lib/defines.h new file mode 100644 index 00000000..9015c6b1 --- /dev/null +++ b/libksirtet/lib/defines.h @@ -0,0 +1,35 @@ +#ifndef DEFINES_H +#define DEFINES_H + +#include <kmessagebox.h> +#include <kextsock.h> + +// constants +#define TALKER_MAX_LENGTH 35 +#define NAME_MAX_LENGTH 15 + +// config keys +#define MP_GROUP "Multi-Players" +#define MP_GAMETYPE "Game type" +#define MP_PLAYER_NAME "Player name #%1" +#define MP_PLAYER_TYPE "Player type #%1" +#define MP_SERVER_ADDRESS "Server address" +#define MP_PORT "Port" + +void errorBox(const QString &msg1, const QString &msg2, QWidget *parent); +QString socketError(const KExtendedSocket *socket); +bool checkSocket(int res, const KExtendedSocket *, + const QString &msg, QWidget *parent); + +#define R_ERROR_BOX(msg1, msg2) { \ + errorBox(msg1, msg2, this); \ + return; \ +} + +template <class Type> +bool XOR(Type a, Type b) +{ + return ( (!a && b) || (a && !b) ); +} + +#endif // DEFINES_H diff --git a/libksirtet/lib/internal.cpp b/libksirtet/lib/internal.cpp new file mode 100644 index 00000000..c7b69f9e --- /dev/null +++ b/libksirtet/lib/internal.cpp @@ -0,0 +1,278 @@ +#include "internal.h" +#include "internal.moc" + +#include <qptrlist.h> +#include <klocale.h> +#include "mp_interface.h" + +#define DATA_ERROR(i) { dataError(i); return; } +#define READ_ERROR(i) { readError(i); return; } +#define WRITE_ERROR(i) { writeError(i); return; } +#define BROKE_ERROR(i) { brokeError(i); return; } +#define LAG_ERROR { lagError(); return; } + + +//----------------------------------------------------------------------------- +void Local::dataError(uint i) +{ + qWarning("MP : Invalid data from board #%i", i); +} + +void Local::readData(bool inverse) +{ + // read data from local boards + for (uint i=0; i<ios.size(); i++) { + boards[i].ptr->dataOut(ios[i]->writing); + if (inverse) ios[i]->writingToReading(); + } +} + +void Local::writeData(bool inverse) +{ + // write data to local boards + for (uint i=0; i<ios.size(); i++) { + if (inverse) ios[i]->writingToReading(); + boards[i].ptr->dataIn(ios[i]->reading); + if ( !ios[i]->reading.readOk() ) DATA_ERROR(i); + } +} + +void Local::treatData() +{ + readData(TRUE); + interface->treatData(); + // check reading stream + for (uint i=0; i<ios.size(); i++) + if ( !ios[i]->reading.readOk() ) DATA_ERROR(i); + writeData(TRUE); +} + +//----------------------------------------------------------------------------- +Server::Server(uint _interval) +: interval(_interval) +{ + timer.start(interval); +} + +void Server::congestion() +{ + qWarning("MP : congestion occurred !!"); +} + +void Server::serverTimeout() +{ + ctimer.start(2*interval, TRUE); + timeout(); +} + +//----------------------------------------------------------------------------- +Network::Network(MPInterface *_interface, + QValueList<MPInterface::Data> &_boards, + const QPtrList<RemoteHostData> &rhd) +: Local(_interface, _boards) +{ + RemoteData rd; + QPtrListIterator<RemoteHostData> it(rhd); + for (; it.current(); ++it) { + rd.socket = it.current()->socket; + rd.socket->notifier()->setEnabled(TRUE); + connect(rd.socket->notifier(), SIGNAL(activated(int)), + SLOT(notifier(int))); + uint nb = it.current()->bds.count(); + Q_ASSERT( nb>=1 ); + rd.array = new BufferArray(nb); + for (uint k=0; k<it.current()->bds.count(); k++) + rd.names += it.current()->bds[k].name; + remotes += rd; + } +} + +Network::~Network() +{ + for (uint i=0; i<remotes.count(); i++) { + delete remotes[i].socket; + delete remotes[i].array; + } +} + +uint Network::nbPlayers() const +{ + uint nb = Local::nbPlayers(); + for (uint i=0; i<remotes.count(); i++) nb += remotes[i].array->size(); + return nb; +} + +QString Network::playerName(uint i) const +{ + uint l = Local::nbPlayers(); + if ( i<l ) return Local::playerName(i); + for (uint k=0; k<remotes.count(); k++) { + uint ll = remotes[k].array->size(); + if ( i<(l+ll) ) return remotes[k].names[i-l]; + l += ll; + } + return QString::null; // dummy +} + +IOBuffer *Network::ioBuffer(uint i) const +{ + if ( i<Local::nbPlayers() ) return Local::ioBuffer(i); + i -= Local::nbPlayers(); + for (uint k=0; k<remotes.count(); k++) { + if ( i<remotes[k].array->size() ) return (*remotes[k].array)[i]; + i -= remotes[k].array->size(); + } + Q_ASSERT(FALSE); + return 0; +} + +void Network::readError(uint i) +{ + disconnectHost(i, i18n("Unable to read socket")); +} + +void Network::writeError(uint i) +{ + disconnectHost(i, i18n("Unable to write to socket")); +} + +void Network::brokeError(uint i) +{ + disconnectHost(i, i18n("Link broken")); +} + +void Network::disconnectHost(uint i, const QString &msg) +{ + delete remotes[i].socket; + delete remotes[i].array; + remotes.remove(remotes.at(i)); + interface->hostDisconnected(i, msg); +} + +//----------------------------------------------------------------------------- +LocalServer::LocalServer(MPInterface *_interface, + QValueList<MPInterface::Data> &_boards, + uint _interval) +: Local(_interface, _boards), Server(_interval) +{ + connect(&timer, SIGNAL(timeout()), SLOT(timeoutSlot())); + connect(&ctimer, SIGNAL(timeout()), SLOT(congestionTimeoutSlot())); + serverTimeout(); +} + +//----------------------------------------------------------------------------- +NetworkServer::NetworkServer(MPInterface *_interface, + QValueList<MPInterface::Data> &_boards, + const QPtrList<RemoteHostData> &rhd, uint _interval) +: Network(_interface, _boards, rhd), Server(_interval), + nbReceived(remotes.count()) +{ + connect(&timer, SIGNAL(timeout()), SLOT(timeoutSlot())); + connect(&ctimer, SIGNAL(timeout()), SLOT(congestionTimeoutSlot())); + // to catch unexpected data + for (uint i=0; i<remotes.count(); i++) remotes[i].received = TRUE; + nbReceived = remotes.count(); +// let the client the time to create itself ... +// serverTimeout(); +} + +void NetworkServer::timeout() +{ + if ( nbReceived<remotes.count() ) LAG_ERROR; + nbReceived = 0; + for (uint i=0; i<remotes.count(); i++) remotes[i].received = FALSE; + // send MF_ASK : asking for data from clients + for (uint i=0; i<remotes.count(); i++) { + remotes[i].socket->writingStream() << MF_Ask; +// debug("SERVER : send ask flag"); + if ( !remotes[i].socket->write() ) WRITE_ERROR(i); + } +} + +void NetworkServer::notifier(int fd) +{ + uint i; + for (i=0; i<remotes.count(); i++) + if ( remotes[i].socket->fd()==fd ) break; + Q_ASSERT( i<remotes.count() ); + + if ( remotes[i].received ) READ_ERROR(i); + switch ( remotes[i].socket->read() ) { + case -1: READ_ERROR(i); + case 0: BROKE_ERROR(i); + } + + remotes[i].received = TRUE; + nbReceived++; + + ReadingStream &s = remotes[i].socket->readingStream(); +// debug("SERVER : notifier + read (fd=%i i=%i size=%i)", fd, i, +// s.size()); + s >> *remotes[i].array; + if ( !s.readOk() ) DATA_ERROR(i); + + // all data from clients received + if ( nbReceived==remotes.count() ) treatData(); +} + +void NetworkServer::writeData(bool inverse) +{ + Local::writeData(inverse); + for (uint i=0; i<remotes.count(); i++) { + WritingStream &s = remotes[i].socket->writingStream(); + s << MF_Data; + s << *remotes[i].array; + s.writeRawBytes(globalStream()->buffer().data(), + globalStream()->size()); +// debug("SERVER : write data (size= 1 + %i + %i=%i)", +// globalStream()->size(), s.size()-globalStream()->size()-1, +// s.size()); + if ( !remotes[i].socket->write() ) WRITE_ERROR(i); + } + globalStream()->clear(); +} + +void NetworkServer::lagError() +{ + for (uint i=0; i<remotes.count(); i++) + if ( !remotes[i].received ) + disconnectHost(i, i18n("Client has not answered in time")); +} + +//----------------------------------------------------------------------------- +void Client::notifier(int) +{ + switch ( remotes[0].socket->read() ) { + case -1: READ_ERROR(0); + case 0: BROKE_ERROR(0); + } + ReadingStream &s = remotes[0].socket->readingStream(); + MetaFlag mf; + s >> mf; + if ( !s.readOk() ) DATA_ERROR(0); +// debug("CLIENT : reading stream (size=%i flag=%i)", s.size(), +// (int)mf); + switch(mf) { + case MF_Ask: + // write data from local boards to server socket (cleaning + // of writing stream is done in write()) + readData(FALSE); + remotes[0].socket->writingStream() << ios; +// debug("CLIENT : send ios (size=%i)", +// remotes[0].socket->writingStream().size()); + if ( !remotes[0].socket->write() ) WRITE_ERROR(0); + break; + case MF_Data: + // read data from server to interface & local boards +// debug("CLIENT : before receive ios (at=%i)", s.device()->at()); + s >> ios; +// debug("CLIENT : after receive ios (at=%i)", s.device()->at()); + interface->dataFromServer(s); +// debug("CLIENT : after dataFromServer (at=%i)", s.device()->at()); + if ( !s.readOk() ) DATA_ERROR(0); + writeData(FALSE); + break; + default: DATA_ERROR(0); + } + if ( !s.atEnd() ) qWarning("CLIENT : remaining data"); +} diff --git a/libksirtet/lib/internal.h b/libksirtet/lib/internal.h new file mode 100644 index 00000000..4dece16a --- /dev/null +++ b/libksirtet/lib/internal.h @@ -0,0 +1,152 @@ +#ifndef INTERNAL_H +#define INTERNAL_H + +#include <qtimer.h> +#include <qstringlist.h> + +#include "socket.h" +#include "mp_interface.h" + +class MPBoard; +class RemoteHostData; + +//----------------------------------------------------------------------------- +class Local +{ + public: + Local(MPInterface *_interface, QValueList<MPInterface::Data> &_boards) + : interface(_interface), ios(_boards.count()), boards(_boards) {} + virtual ~Local() {} + + virtual uint nbPlayers() const { return boards.count(); } + virtual QString playerName(uint i) const { return boards[i].name; } + virtual IOBuffer *ioBuffer(uint i) const { return ios[i]; } + virtual void writeData(bool inverse); + virtual WritingStream *globalStream() { return 0; } + + protected: + MPInterface *interface; + BufferArray ios; + + void dataError(uint i); + void readData(bool inverse); + void treatData(); + + private: + QValueList<MPInterface::Data> boards; +}; + +//----------------------------------------------------------------------------- +class Server +{ + public: + Server(uint _interval); + virtual ~Server() {} + + protected: + WritingStream stream; + QTimer timer, ctimer; + + virtual void timeout() = 0; + void serverTimeout(); + void congestion(); + + private: + uint interval; +}; + +//----------------------------------------------------------------------------- +class Network : public QObject, public Local +{ + Q_OBJECT + + public: + Network(MPInterface *_interface, QValueList<MPInterface::Data> &_boards, + const QPtrList<RemoteHostData> &rhd); + virtual ~Network(); + + virtual uint nbPlayers() const; + QString playerName(uint i) const; + IOBuffer *ioBuffer(uint i) const; + + protected slots: + virtual void notifier(int fd) = 0; + + protected: + class RemoteData { + public: + RemoteData() {} + Socket *socket; + BufferArray *array; + bool received; + QStringList names; + }; + QValueList<RemoteData> remotes; + + void readError(uint i); + void writeError(uint i); + void brokeError(uint i); + void disconnectHost(uint i, const QString &msg); +}; + +//----------------------------------------------------------------------------- +class LocalServer : public QObject, public Local, public Server +{ + Q_OBJECT + + public: + LocalServer(MPInterface *_interface, + QValueList<MPInterface::Data> &_boards, uint _interval); + + WritingStream *globalStream() { return &stream; } + + private slots: + void timeoutSlot() { serverTimeout(); } + void congestionTimeoutSlot() { congestion(); } + + private: + void timeout() { treatData(); } +}; + +//----------------------------------------------------------------------------- +class NetworkServer : public Network, public Server +{ + Q_OBJECT + + public: + NetworkServer(MPInterface *_interface, + QValueList<MPInterface::Data> &_boards, + const QPtrList<RemoteHostData> &rhd, uint _interval); + + void writeData(bool inverse); + WritingStream *globalStream() { return &stream; } + + private slots: + void timeoutSlot() { serverTimeout(); } + void congestionTimeoutSlot() { congestion(); } + void notifier(int fd); + + private: + uint nbReceived; + + void lagError(); + void timeout(); +}; + +//----------------------------------------------------------------------------- +class Client : public Network +{ + Q_OBJECT + + public: + Client(MPInterface *_interface, QValueList<MPInterface::Data> &_boards, + const QPtrList<RemoteHostData> &rhd) + : Network(_interface, _boards, rhd) {} + + uint nbPlayers() const { return Local::nbPlayers(); } + + private slots: + void notifier(int fd); +}; + +#endif // INTERNAL_H diff --git a/libksirtet/lib/keys.cpp b/libksirtet/lib/keys.cpp new file mode 100644 index 00000000..879f0bfa --- /dev/null +++ b/libksirtet/lib/keys.cpp @@ -0,0 +1,104 @@ +#include "keys.h" +#include "keys.moc" + +#include <qsignal.h> +#include <kkeydialog.h> +#include <klocale.h> + + +KeyData::KeyData(uint maxNb, uint nbActions, const ActionData *data, + QObject *parent) + : QObject(parent), _maxNb(maxNb) +{ + _data.duplicate(data, nbActions); + + for (uint n=0; n<maxNb; n++) + for (uint i=0; i<=n; i++) + _keycodes[n][i].fill(0, nbActions); +} + +void KeyData::setKeycodes(uint nb, uint index, const int *keycodes) +{ + Q_ASSERT( nb!=0 && nb-1<_maxNb && index<nb ); + for (uint n=nb-1; n<_maxNb; n++) + for (uint k=0; k<_data.size(); k++) + if ( n==nb-1 || _keycodes[n][index][k]==0 ) + _keycodes[n][index][k] = keycodes[k]; +} + +void KeyData::setCurrentNb(uint nb) +{ + Q_ASSERT( nb<_maxNb ); + clear(); + _cols.fill(0, nb); +} + +void KeyData::clear() +{ + for (uint i=0; i<_cols.size(); i++) + delete _cols[i]; + _cols.resize(0); +} + +void KeyData::createActionCollection(uint index, QWidget *receiver) +{ + Q_ASSERT( index<_cols.size() ); + _cols[index] = new KActionCollection(receiver, this); + for (uint k=0; k<_data.size(); k++) { + QString label = i18n(_data[k].label); + QString name = QString("%2 %3").arg(index+1).arg(_data[k].name); + const char *slot = (_data[k].slotRelease ? 0 : _data[k].slot); + KAction *a = new KAction(label, _keycodes[_cols.size()-1][index][k], + receiver, slot, _cols[index], name.utf8()); + a->setEnabled(false); + if ( slot==0 ) { + SpecialData data; + data.enabled = false; + data.pressed = new QSignal(this); + data.pressed->connect(receiver, _data[k].slot); + data.released = new QSignal(this); + data.released->connect(receiver, _data[k].slotRelease); + _specActions[a] = data; + } + } + _cols[index]->readShortcutSettings(group()); +} + +void KeyData::setEnabled(uint index, bool enabled) +{ + for (uint k=0; k<_cols[index]->count(); k++) { + QMap<KAction *, SpecialData>::Iterator it = + _specActions.find(_cols[index]->action(k)); + if ( it==_specActions.end() ) + _cols[index]->action(k)->setEnabled(enabled); + else (*it).enabled = enabled; + } +} + +void KeyData::addKeys(KKeyDialog &d) +{ + for (uint i=0; i<_cols.size(); i++) + d.insert(_cols[i], i18n("Shortcuts for player #%1/%2").arg(i+1) + .arg(_cols.size())); +} + +void KeyData::save() +{ + for (uint i=0; i<_cols.size(); i++) + _cols[i]->writeShortcutSettings(group()); +} + +void KeyData::keyEvent(QKeyEvent *e, bool pressed) +{ + if ( e->isAutoRepeat() ) return; + + KKey key(e); + QMap<KAction *, SpecialData>::Iterator it = _specActions.begin(); + for(; it!=_specActions.end(); ++it) { + if ( !it.data().enabled ) continue; + if ( !it.key()->shortcut().contains(key) ) continue; + if (pressed) it.data().pressed->activate(); + else it.data().released->activate(); + } + e->ignore(); +} diff --git a/libksirtet/lib/keys.h b/libksirtet/lib/keys.h new file mode 100644 index 00000000..07c08419 --- /dev/null +++ b/libksirtet/lib/keys.h @@ -0,0 +1,42 @@ +#ifndef KEYS_H +#define KEYS_H + +#include <qmap.h> +#include <kaction.h> + +#include "mp_interface.h" + + +class KeyData : public QObject +{ + Q_OBJECT + public: + KeyData(uint maxNb, uint nbActions, const ActionData *, + QObject *parent); + void setKeycodes(uint nb, uint i, const int *keycodes); + + void setCurrentNb(uint nb); + void clear(); + void createActionCollection(uint index, QWidget *receiver); + void setEnabled(uint index, bool enabled); + void addKeys(KKeyDialog &); + void save(); + + void keyEvent(QKeyEvent *e, bool pressed); + + private: + uint _maxNb; + QMemArray<ActionData> _data; + QMap<int, QMap<int, QMemArray<int> > > _keycodes; + QMemArray<KActionCollection *> _cols; + struct SpecialData { + bool enabled; + QSignal *pressed, *released; + }; + QMap<KAction *, SpecialData> _specActions; + + QString group() const + { return QString("Keys (%1 humans)").arg(_cols.size()); } +}; + +#endif // KEYS_H diff --git a/libksirtet/lib/libksirtet_export.h b/libksirtet/lib/libksirtet_export.h new file mode 100644 index 00000000..819e0029 --- /dev/null +++ b/libksirtet/lib/libksirtet_export.h @@ -0,0 +1,35 @@ +/* + This file is part of libkexif project + Copyright (c) 2005 Laurent Montel <[email protected]> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library 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 + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef _LIBKEXIF_EXPORT_H +#define _LIBKEXIF_EXPORT_H + +#ifdef KDEMACROS_USABLE +#include <kdemacros.h> +#endif + +#ifdef KDE_EXPORT +#define LIBKSIRTET_EXPORT KDE_EXPORT +#else +#define LIBKSIRTET_EXPORT +#endif + +#endif /* _LIBKEXIF_EXPORT_H */ + diff --git a/libksirtet/lib/meeting.cpp b/libksirtet/lib/meeting.cpp new file mode 100644 index 00000000..2cb6e285 --- /dev/null +++ b/libksirtet/lib/meeting.cpp @@ -0,0 +1,575 @@ +#include "meeting.h" + +#include <qmessagebox.h> +#include <qpushbutton.h> + +#include <klocale.h> +#include <kmessagebox.h> + +#include "defines.h" +#include "mp_option.h" + +#define LIST_INTERVAL 0 + + +NetMeeting::NetMeeting(const cId &_id, Socket *socket, + MPOptionWidget *option, + bool _server, QWidget *parent, const char * name) +: KDialogBase(Plain, i18n("Network Meeting"), + (_server ? Ok|Cancel|Help : Cancel|Help), + (_server ? Ok : Cancel), parent, name), + server(_server), ow(option), id(_id), socketRemoved(FALSE) +{ + sm.append(socket, SocketManager::ReadWrite); + sm[0]->notifier()->setEnabled(TRUE); + +/* top layout */ + QVBoxLayout *top = new QVBoxLayout(plainPage(), spacingHint()); + top->setResizeMode(QLayout::Fixed); + + // server line + spl = new MeetingLine(server, server, true, plainPage()); + top->addWidget(spl); + + // widget list + wl = new WidgetList<MeetingLine>(LIST_INTERVAL, plainPage()); + wl->hide(); + top->addWidget(wl); + + labWait = new QLabel(i18n("Waiting for clients"), plainPage()); + labWait->setAlignment(AlignCenter); + top->addWidget(labWait); + + // options widget +// if (ow) top->addWidget(ow); #### FIXME + + // status bar + status = new QStatusBar(plainPage()); + status->setSizeGripEnabled(false); + top->addWidget(status); + + // buttons + enableButtonSeparator(TRUE); + if (server) { + setButtonOK(i18n("Start Game")); + enableButtonOK(FALSE); + } + setButtonCancel(server ? i18n("Abort") : i18n("Quit")); + enableButton(Help, FALSE); +} + +NetMeeting::~NetMeeting() +{} + +void NetMeeting::appendLine(const MeetingLineData &pld, bool server) +{ + MeetingLine *pl; + pl = new MeetingLine(pld.own, server, false, wl); + if (pld.own) connect(pl, SIGNAL(textChanged(const QString &)), + SLOT(textChanged(const QString &))); + else message(i18n("A new client has just arrived (#%1)") + .arg(wl->size()+1)); + pl->setData(pld.ed); + connect(pl, SIGNAL(typeChanged(MeetingCheckBox::Type)), + SLOT(typeChanged(MeetingCheckBox::Type))); + wl->append(pl); + waiting(); +} + +void NetMeeting::removeLine(uint i) +{ + wl->remove(i); + waiting(); +} + +void NetMeeting::waiting() +{ + if ( wl->size() ) { + labWait->hide(); + wl->show(); + } else { + labWait->show(); + wl->hide(); + } + if (server) enableButtonOK(ready()); +} + +void NetMeeting::setType(const TypeInfo &ti) +{ + if ( ti.i==0 ) spl->setType(ti.type); // in fact should not append + else { + wl->widget(ti.i-1)->setType(ti.type); + if (server) enableButtonOK(ready()); + } +} + +void NetMeeting::setText(const TextInfo &ti) +{ + if ( ti.i==0 ) spl->setText(ti.text); + else wl->widget(ti.i-1)->setText(ti.text); +} + +bool NetMeeting::ready() const +{ + int nbReady = 0; + for(uint k=0; k<wl->size(); k++) { + switch ( wl->widget(k)->type() ) { + case MeetingCheckBox::Ready : nbReady++; break; + case MeetingCheckBox::NotReady : return FALSE; + default : break; + } + } + return ( nbReady!=0 ); +} + +void NetMeeting::cleanReject(const QString &str) +{ + sm.clean(); // remove the sockets immediately to avoid possible further mess + if ( !str.isEmpty() ) + KMessageBox::information(this, str, caption()); + KDialogBase::reject(); +} + +#define WRITE(i) if ( !sm[i]->write() ) { writeError(i); return; } +#define CHECK_READ(i) if ( !sm[i]->readingStream().readOk() ) { dataError(i); return; } + +// Read incoming data +void NetMeeting::readNotifier(int fd) +{ + int i = sm.find(fd); + Q_ASSERT( i!=-1 ); + switch ( sm[i]->read() ) { + case -1: readError(i); break; + case 0: brokeError(i); break; + default: readData(i); + } +} + +void NetMeeting::readData(uint i) +{ + // get message type + MeetingMsgFlag mt; + sm[i]->readingStream() >> mt; + CHECK_READ(i); + switch (mt) { + case EndFlag: endFlag(i); break; + case NewFlag: newFlag(i); break; + case Mod_TextFlag: modTextFlag(i); break; + case Mod_TypeFlag: modTypeFlag(i); break; + case IdFlag: idFlag(i); break; + case DelFlag: delFlag(i); break; + case Mod_OptFlag: modOptFlag(i); break; + case PlayFlag: playFlag(i); break; + default: dataError(i); + } + + if (socketRemoved) socketRemoved = FALSE; + else if ( !sm[i]->readingStream().atEnd() ) + readData(i); // more pending data +} + +void NetMeeting::readError(uint i) + { netError(i, i18n("Error reading data from")); } +void NetMeeting::dataError(uint i) + { netError(i, i18n("Unknown data from")); } +void NetMeeting::writeError(uint i) + { netError(i, i18n("Error writing to")); } +void NetMeeting::brokeError(uint i) + { netError(i, i18n("Link broken or empty data from")); } + +bool NetMeeting::checkState(uint i, PlayerState s) +{ + bool ok = ( players[i]==s ); + if (!ok) dataError(i); + return ok; +} + +bool NetMeeting::checkAndSetState(uint i, PlayerState os, PlayerState ns) +{ + bool ok = checkState(i, os); + if (ok) players[i] = ns; + return ok; +} + +void NetMeeting::reject() +{ + // send an End flag + sm.commonWritingStream() << EndFlag; + writeToAll(); + + cleanReject(); +} + +void NetMeeting::accept() +{ + KDialogBase::accept(); +} + +void NetMeeting::message(const QString &str) +{ + status->message(str, 3000); +} + +/** ServerNetMeeting *********************************************************/ +ServerNetMeeting::ServerNetMeeting(const cId &id, + const RemoteHostData &r, MPOptionWidget *option, + QPtrList<RemoteHostData> &arhd, QWidget *parent, const char * name) +: NetMeeting(id, r.socket, option, TRUE, parent, name), rhd(arhd) +{ + connect(sm[0]->notifier(), SIGNAL(activated(int)), SLOT(newHost(int))); + players.append(Accepted); // server + + // set server line + ExtData ed(r.bds, "", MeetingCheckBox::Ready); + spl->setData(ed); + connect(spl, SIGNAL(textChanged(const QString &)), + SLOT(textChanged(const QString &))); + + // options signal + if (ow) connect(ow, SIGNAL(changed()), SLOT(optionsChanged())); +} + +void ServerNetMeeting::writeToAll(uint i) +{ + for (uint k=1; k<sm.size(); k++) { + if ( k==i ) continue; + if ( !sm.writeCommon(k) ) writeError(k); + } + sm.commonWritingStream().clear(); +} + +void ServerNetMeeting::netError(uint i, const QString &type) +{ + Q_ASSERT( i!=0 ); + disconnectHost(i, i18n("%1 client #%2: disconnect it").arg(type).arg(i)); +} + +void ServerNetMeeting::disconnectHost(uint i, const QString &str) +{ + sm.remove(i, true); + socketRemoved = TRUE; + if ( players[i]==Accepted ) { + removeLine(i-1); + + // Send a Del message to all (other) clients + sm.commonWritingStream() << DelFlag << i; + writeToAll(); + } + players.remove(players.at(i)); + message(str); +} + +void ServerNetMeeting::newHost(int) +{ + KExtendedSocket *s; + int res = sm[0]->accept(s); + if ( res!=0 ) { + message(i18n("Failed to accept incoming client:\n%1") + .arg(socketError(s))); + return; + } + players.append(NewPlayer); + Socket *socket = new Socket(s, true); + uint i = sm.append(socket, SocketManager::ReadWrite); + connect(sm[i]->notifier(), SIGNAL(activated(int)), + SLOT(readNotifier(int))); + sm[i]->notifier()->setEnabled(TRUE); +} + +void ServerNetMeeting::idFlag(uint i) +{ + bool b = checkAndSetState(i, NewPlayer, IdChecked); + Q_ASSERT(b); + + // get client id + cId clientId; + sm[i]->readingStream() >> clientId; + CHECK_READ(i); + + // compare id + id.check(clientId); + + // send result to client + Stream &s = sm[i]->writingStream(); + s << IdFlag << id; + WRITE(i); + + // if not accepted : remove socket and player from list + if ( !id.accepted() ) + disconnectHost(i, i18n("Client rejected for incompatible ID")); +} + +void ServerNetMeeting::endFlag(uint i) +{ + disconnectHost(i, i18n("Client #%1 has left").arg(i)); +} + +void ServerNetMeeting::newFlag(uint i) +{ + checkAndSetState(i, IdChecked, Accepted); + + // get line infos from new client (GameData struct) + MeetingLineData pld; + sm[i]->readingStream() >> pld.ed.bds; + CHECK_READ(i); + + // complete the MeetingLineData struct with initial values + pld.own = FALSE; // client line + pld.ed.type = MeetingCheckBox::NotReady; // not ready by default + pld.ed.text = ""; // empty line to begin with + appendLine(pld, TRUE); + + // send to the new client already present lines including its own + // (New flag + MeetingLineData struct) + spl->data(pld.ed); + sm[i]->writingStream() << NewFlag << pld.ed; + for(uint k=1; k<sm.size(); k++) { + wl->widget(k-1)->data(pld.ed); + pld.own = ( k==i ); + sm[i]->writingStream() << NewFlag << pld; + } + WRITE(i); + + // send to all other clients the new line (New flag + MeetingLineData struct) + wl->widget(i-1)->data(pld.ed); + pld.own = FALSE; + sm.commonWritingStream() << NewFlag << pld; + writeToAll(i); +} + +void ServerNetMeeting::modTextFlag(uint i) +{ + checkState(i-1, Accepted); + + // the client i has just sent a new text (QString) + TextInfo ti; + sm[i]->readingStream() >> ti.text; + CHECK_READ(i); + ti.i = i; + setText(ti); + + // send it to all other clients (Mod_Text flag + TextInfo struct) + sm.commonWritingStream() << Mod_TextFlag << ti; + writeToAll(i); +} + +void ServerNetMeeting::modTypeFlag(uint i) +{ + checkState(i-1, Accepted); + + // a client has just sent a new TCB type (TCB type) + TypeInfo ti; + sm[i]->readingStream() >> ti.type; + CHECK_READ(i); + ti.i = i; + setType(ti); + + // send it to all other clients (Mod_Type flag + TypeInfo struct) + sm.commonWritingStream() << Mod_TypeFlag << ti; + writeToAll(i); +} + +void ServerNetMeeting::textChanged(const QString &text) +{ + // server line text changed : send to every clients (Mod_Text flag + TextInfo struct) + TextInfo ti; ti.i = 0; ti.text = text; + sm.commonWritingStream() << Mod_TextFlag << ti; + writeToAll(); +} + +void ServerNetMeeting::typeChanged(MeetingCheckBox::Type type) +{ + Q_ASSERT( sender()!=spl ); // server TCB not modifiable + // the server has changed a client TCB + + // find the changed TCB index + TypeInfo ty; + ty.type = type; + for (ty.i=0; ty.i<wl->size(); ty.i++) + if ( sender()==wl->widget(ty.i) ) break; + ty.i++; + + // TCB change : send to every clients (Mod_Type flag + TypeInfo struct) + sm.commonWritingStream() << Mod_TypeFlag << ty; + writeToAll(); + if (server) enableButtonOK(ready()); +} + +void ServerNetMeeting::accept() +{ + Q_ASSERT( ready() && rhd.count()==0 ); + + // stop receiving data from clients (will be buffered by OS) + for (uint k=0; k<sm.size(); k++) disconnect(sm[k]->notifier()); + sm.remove(0, true); + + // check which client will play and fill RemoteHostData array + ExtData ed; + bool willPlay; + for (uint k=1; k<players.count(); k++) { + willPlay = FALSE; + + if ( players[k]==Accepted ) { // client with lines + wl->widget(k-1)->data(ed); + if ( ed.type==MeetingCheckBox::Ready ) { + willPlay = TRUE; + RemoteHostData *r = new RemoteHostData; + r->socket = sm[0]; + r->bds = ed.bds; + rhd.append(r); + } + + // send play message to client (Play flag + // + bool [accepted/rejected]) + sm[0]->writingStream() << PlayFlag << (Q_UINT8)willPlay; + // if write failed and the client is not playing : silently + // put it aside ... + if ( !sm[0]->write() && willPlay ) { + cleanReject(i18n("Unable to write to client #%1 at game " + "beginning.")); + return; + } + } + + sm[0]->notifier()->setEnabled(false); + sm.remove(0, !willPlay); + } + + NetMeeting::accept(); +} + +void ServerNetMeeting::optionsChanged() +{ + sm.commonWritingStream() << Mod_OptFlag; + ow->dataOut( sm.commonWritingStream() ); + writeToAll(); +} + +/** ClientNetMeeting *********************************************************/ +ClientNetMeeting::ClientNetMeeting(const cId &id, + const RemoteHostData &rhd, MPOptionWidget *option, + QWidget *parent, const char * name) +: NetMeeting(id, rhd.socket, option, FALSE, parent, name), bds(rhd.bds) +{ + connect(sm[0]->notifier(), SIGNAL(activated(int)), + SLOT(readNotifier(int))); + players.append(NewPlayer); // server player + + // Send id to server (Id flag + Id struct) + sm.commonWritingStream() << IdFlag << id; + writeToAll(); // what happens if there is a message box appearing before exec() call ?? +} + +void ClientNetMeeting::netError(uint, const QString &str) +{ + cleanReject(i18n("%1 server: aborting connection.").arg(str)); +} + +void ClientNetMeeting::writeToAll(uint) +{ + if ( !sm.writeCommon(0) ) writeError(0); + sm.commonWritingStream().clear(); +} + +void ClientNetMeeting::idFlag(uint) +{ + checkAndSetState(0, NewPlayer, IdChecked); + + // read Id result (Id flag + Id struct) + cId serverId; + sm[0]->readingStream() >> serverId; + CHECK_READ(0); + + // check result + if ( !serverId.accepted() ) cleanReject(serverId.errorMessage(id)); + else { + // send client info (New flag + GameData struct) + sm.commonWritingStream() << NewFlag << bds; + writeToAll(); + } +} + +void ClientNetMeeting::newFlag(uint) +{ + if ( players[0]==IdChecked ) { + ExtData ed; + sm[0]->readingStream() >> ed; + spl->setData(ed); + players[0] = Accepted; + } else { + MeetingLineData pld; + sm[0]->readingStream() >> pld; + appendLine(pld, FALSE); + } + CHECK_READ(0); +} + +void ClientNetMeeting::modTextFlag(uint) +{ + // receive new text from server (TextInfo struct) + TextInfo ti; + sm[0]->readingStream() >> ti; + CHECK_READ(0); + setText(ti); +} + +void ClientNetMeeting::modTypeFlag(uint) +{ + // receive new type from server (TypeInfo struct) + TypeInfo ti; + sm[0]->readingStream() >> ti; + CHECK_READ(0); + setType(ti); +} + +void ClientNetMeeting::delFlag(uint) +{ + // receive client number (uint) + uint k; + sm[0]->readingStream() >> k; + CHECK_READ(0); + removeLine(k-1); + message(i18n("Client %1 has left").arg(k)); +} + +void ClientNetMeeting::textChanged(const QString &text) +{ + // text changed : send to server (Mod_Text flag + QString) + sm.commonWritingStream() << Mod_TextFlag << text; + writeToAll(); +} + +void ClientNetMeeting::typeChanged(MeetingCheckBox::Type type) +{ + // type changed : send to server (Mod_Type flag + TCB) + sm.commonWritingStream() << Mod_TypeFlag << type; + writeToAll(); +} + +void ClientNetMeeting::playFlag(uint) +{ + // receive accept or reject (bool) + Q_UINT8 i; + sm[0]->readingStream() >> i; + CHECK_READ(0); + sm[0]->notifier()->setEnabled(false); + sm.remove(0, i==0); + socketRemoved = true; + if (i) accept(); + else cleanReject(i18n("The game has begun without you\n" + "(You have been excluded by the server).")); +} + +void ClientNetMeeting::modOptFlag(uint) +{ + // read new option data + ow->dataIn( sm[0]->readingStream() ); + CHECK_READ(0); +} + +void ClientNetMeeting::endFlag(uint) +{ + // abort from server + cleanReject(i18n("The server has aborted the game.")); +} +#include "meeting.moc" diff --git a/libksirtet/lib/meeting.h b/libksirtet/lib/meeting.h new file mode 100644 index 00000000..cb534404 --- /dev/null +++ b/libksirtet/lib/meeting.h @@ -0,0 +1,137 @@ +#ifndef MEETING_H +#define MEETING_H + +#include <qstatusbar.h> +#include <kdialogbase.h> +#include "smanager.h" +#include "pline.h" +#include "types.h" + +class MPOptionWidget; + +/** Internal class : net meeting. */ +class NetMeeting : public KDialogBase +{ + Q_OBJECT + + public: + // "gameName" and "gameId" are QByteArray because they are + // used for ID comparing between games. + NetMeeting(const cId &id, Socket *, MPOptionWidget *option, bool server, + QWidget *parent = 0, const char * name = 0); + virtual ~NetMeeting(); + + protected slots: + void readNotifier(int socket); + virtual void textChanged(const QString &) = 0; + virtual void typeChanged(MeetingCheckBox::Type) = 0; + virtual void reject(); + virtual void accept(); + + protected: + enum PlayerState { NewPlayer, IdChecked, Accepted }; + QValueList<PlayerState> players; + bool server; + MeetingLine *spl; + WidgetList<MeetingLine> *wl; + SocketManager sm; + MPOptionWidget *ow; + cId id; + bool socketRemoved; + + void appendLine(const MeetingLineData &pld, bool server); + void removeLine(uint i); + void setType(const TypeInfo &ti); + void setText(const TextInfo &ti); + + void cleanReject(const QString &str = QString::null); + bool checkState(uint i, PlayerState s); + bool checkAndSetState(uint i, PlayerState os, PlayerState ns); + bool ready() const; + + virtual void idFlag(uint i) { dataError(i); } + virtual void newFlag(uint i) { dataError(i); } + virtual void endFlag(uint i) { dataError(i); } + virtual void modTypeFlag(uint i) { dataError(i); } + virtual void modTextFlag(uint i) { dataError(i); } + virtual void delFlag(uint i) { dataError(i); } + virtual void modOptFlag(uint i) { dataError(i); } + virtual void playFlag(uint i) { dataError(i); } + + virtual void netError(uint i, const QString &str) = 0; + virtual void writeToAll(uint i=0) = 0; + void readError(uint i); + void writeError(uint i); + void dataError(uint i); + void brokeError(uint i); + void message(const QString &str); + + private: + QLabel *labWait; + QStatusBar *status; + + void waiting(); + void readData(uint i); +}; + +class ServerNetMeeting : public NetMeeting +{ + Q_OBJECT + + public: + ServerNetMeeting(const cId &id, + const RemoteHostData &rhd, MPOptionWidget *options, + QPtrList<RemoteHostData> &arhd, + QWidget *parent = 0, const char * name = 0); + + private slots: + void newHost(int); + void textChanged(const QString &text); + void typeChanged(MeetingCheckBox::Type); + void accept(); + void optionsChanged(); + + private: + QPtrList<RemoteHostData> &rhd; + + void idFlag(uint i); + void newFlag(uint i); + void endFlag(uint i); + void modTypeFlag(uint i); + void modTextFlag(uint i); + + void netError(uint i, const QString &str); + void writeToAll(uint i = 0); + void disconnectHost(uint i, const QString &str); +}; + +class ClientNetMeeting : public NetMeeting +{ + Q_OBJECT + + public: + ClientNetMeeting(const cId &id, + const RemoteHostData &rhd, MPOptionWidget *options, + QWidget *parent = 0, const char * name = 0); + + private slots: + void textChanged(const QString &text); + void typeChanged(MeetingCheckBox::Type); + + private: + QValueList<BoardData> bds; + + void idFlag(uint); + void newFlag(uint); + void endFlag(uint); + void delFlag(uint); + void modTypeFlag(uint); + void modTextFlag(uint); + void modOptFlag(uint); + void playFlag(uint); + + void writeToAll(uint i=0); + void netError(uint, const QString &str); +}; + +#endif // MEETING_H diff --git a/libksirtet/lib/miscui.cpp b/libksirtet/lib/miscui.cpp new file mode 100644 index 00000000..28f00914 --- /dev/null +++ b/libksirtet/lib/miscui.cpp @@ -0,0 +1,58 @@ +#include "miscui.h" +#include "miscui.moc" + +#include <qlayout.h> + +#include <klocale.h> + + +//----------------------------------------------------------------------------- +MeetingCheckBox::MeetingCheckBox(Type type, bool owner, bool server, + QWidget *parent) + : QWidget(parent, "meeting_check_box") +{ + QVBoxLayout *vbox = new QVBoxLayout(this); + + _ready = new QCheckBox(i18n("Ready"), this); + vbox->addWidget(_ready); + _ready->setEnabled(owner); + connect(_ready, SIGNAL(clicked()), SLOT(changedSlot())); + + _excluded = new QCheckBox(i18n("Excluded"), this); + vbox->addWidget(_excluded); + _excluded->setEnabled(server); + connect(_excluded, SIGNAL(clicked()), SLOT(changedSlot())); + + setType(type); +} + +void MeetingCheckBox::setType(Type type) +{ + _ready->setChecked( type==Ready ); + _excluded->setChecked( type==Excluded ); +} + +MeetingCheckBox::Type MeetingCheckBox::type() const +{ + if ( _excluded->isChecked() ) return Excluded; + if ( _ready->isChecked() ) return Ready; + return NotReady; +} + +void MeetingCheckBox::changedSlot() +{ + emit changed(type()); +} + +//----------------------------------------------------------------------------- +PlayerComboBox::PlayerComboBox(Type type, bool canBeEmpty, bool acceptAI, + QWidget *parent) + : QComboBox(parent, "player_combo_box") +{ + insertItem(i18n("Human")); + if (acceptAI) insertItem(i18n("AI")); + if (canBeEmpty) insertItem(i18n("None")); + setCurrentItem(type); + + connect(this, SIGNAL(activated(int)), SIGNAL(changed(int))); +} diff --git a/libksirtet/lib/miscui.h b/libksirtet/lib/miscui.h new file mode 100644 index 00000000..7de5dfbc --- /dev/null +++ b/libksirtet/lib/miscui.h @@ -0,0 +1,43 @@ +#ifndef MISCUI_H +#define MISCUI_H + +#include <qcombobox.h> +#include <qcheckbox.h> + + +//----------------------------------------------------------------------------- +class MeetingCheckBox : public QWidget +{ + Q_OBJECT + public: + enum Type { Ready, NotReady, Excluded }; + MeetingCheckBox(Type, bool owner, bool server, QWidget *parent); + + void setType(Type); + Type type() const; + + signals: + void changed(int); + + private slots: + void changedSlot(); + + private: + QCheckBox *_ready, *_excluded; +}; + +//----------------------------------------------------------------------------- +class PlayerComboBox : public QComboBox +{ + Q_OBJECT + public: + enum Type { Human = 0, AI, None }; + PlayerComboBox(Type, bool canBeNone, bool acceptAI, QWidget *parent); + + Type type() const { return (Type)currentItem(); } + + signals: + void changed(int); +}; + +#endif // MISCUI_H diff --git a/libksirtet/lib/mp_board.h b/libksirtet/lib/mp_board.h new file mode 100644 index 00000000..fbd1e01f --- /dev/null +++ b/libksirtet/lib/mp_board.h @@ -0,0 +1,51 @@ +#ifndef MP_BOARD_H +#define MP_BOARD_H + +#include <qwidget.h> + +/** + * The MP_Board class is the base widget from which each individual + * board should inheritate ; you must implement its virtual methods. + */ +class MPBoard : public QWidget +{ + Q_OBJECT + + public: + MPBoard(QWidget *parent, const char *name=0) + : QWidget(parent, name) {} + virtual ~MPBoard() {} + + /** + * This method is called once at the board creation. + * @param AI is TRUE if the player is not human. + * @param multiplayers is TRUE if the game is not a single player game. + * @param first is TRUE if this board is the first local one. + */ + virtual void init(bool AI, bool multiplayers, bool server, bool first, + const QString &name) = 0; + + /** + * Put data on the stream. + * + * This method is the communication way out. The data given here will + * be the only information that will go to the server. + */ + virtual void dataOut(QDataStream &) = 0; + + /** + * Get data from the stream. + * + * This method is the communication way in. The data given here will be + * the only information that you will receive from the server. + */ + virtual void dataIn(QDataStream &) = 0; + + signals: + /** + * Call this signal to enable/disable the keys associated with a board. + */ + void enableKeys(bool); +}; + +#endif // MP_BOARD_H diff --git a/libksirtet/lib/mp_interface.cpp b/libksirtet/lib/mp_interface.cpp new file mode 100644 index 00000000..61742562 --- /dev/null +++ b/libksirtet/lib/mp_interface.cpp @@ -0,0 +1,274 @@ +#include "mp_interface.h" +#include "mp_interface.moc" + +#include <qpainter.h> +#include <qlayout.h> + +#include <klocale.h> +#include <kmessagebox.h> +#include <kapplication.h> +#include <kkeydialog.h> + +#include "defines.h" +#include "types.h" +#include "meeting.h" +#include "internal.h" +#include "keys.h" +#include "wizard.h" + +/*****************************************************************************/ +/* Constructor & Destructor */ +/*****************************************************************************/ +MPInterface::MPInterface(const MPGameInfo &_gameInfo, + uint nbActions, const ActionData *data, + QWidget *parent, const char *name) +: QWidget(parent, name), internal(0), gameInfo(_gameInfo), nbLocalHumans(0) +{ + Q_ASSERT( gameInfo.maxNbLocalPlayers>=1 ); + + hbl = new QHBoxLayout(this, 0, 5); + hbl->setResizeMode( QLayout::Fixed ); + + _keyData = new KeyData(gameInfo.maxNbLocalPlayers, nbActions, data, this); +} + +MPInterface::~MPInterface() +{ + delete internal; +} + +/*****************************************************************************/ +/* Game creation */ +/*****************************************************************************/ +void MPInterface::clear() +{ + if (internal) { + stop(); + delete internal; + internal = 0; + _keyData->clear(); + } +} + +void MPInterface::dialog() +{ + clear(); + + // configuration wizard + ConnectionData cd; + MPWizard wiz(gameInfo, cd, this); + if ( wiz.exec()==QDialog::Rejected ) { + singleHuman(); // create a single game + return; + } + + // net meeting + QPtrList<RemoteHostData> rhd; + rhd.setAutoDelete(TRUE); + if (cd.network) { + cId id(kapp->name(), gameInfo.gameId); + MPOptionWidget *ow = newOptionWidget(); + NetMeeting *nm; + if (cd.server) nm = new ServerNetMeeting(id, cd.rhd, ow, rhd, this); + else nm = new ClientNetMeeting(id, cd.rhd, ow, this); + int res = nm->exec(); + if (ow) { + if (res) ow->saveData(); + delete ow; + } + delete nm; + if (!res) { + singleHuman(); + return; + } + } + + createLocalGame(cd); + if (cd.server) createServerGame(rhd); + else createClientGame(cd.rhd); +} + +void MPInterface::specialLocalGame(uint nbHumans, uint nbAIs) +{ + clear(); + + ConnectionData cd; + BoardData bd; + PlayerComboBox::Type t; + KConfigGroupSaver cg(kapp->config(), MP_GROUP); + for (uint i=0; i<(nbHumans+nbAIs); i++) { + bd.type = (i<nbHumans ? PlayerComboBox::Human : PlayerComboBox::AI); + bd.name = QString::null; + t = (PlayerComboBox::Type) + cg.config()->readNumEntry(QString(MP_PLAYER_TYPE).arg(i), + PlayerComboBox::None); + if ( bd.type==t ) + bd.name = cg.config()->readEntry(QString(MP_PLAYER_NAME).arg(i), + QString::null); + if ( bd.name.isNull() ) + bd.name = (i<nbHumans ? i18n("Human %1").arg(i+1) + : i18n("AI %1").arg(i-nbHumans+1)); + cd.rhd.bds += bd; + } + cd.server = TRUE; + cd.network = FALSE; + Q_ASSERT( (nbHumans+nbAIs)<=gameInfo.maxNbLocalPlayers ); + Q_ASSERT( gameInfo.AIAllowed || nbAIs==0 ); + + createLocalGame(cd); + QPtrList<RemoteHostData> rhd; + createServerGame(rhd); +} + +void MPInterface::createServerGame(const QPtrList<RemoteHostData> &rhd) +{ + internal = (rhd.count() + ? (Local *)new NetworkServer(this, boards, rhd, gameInfo.interval) + : (Local *)new LocalServer(this, boards, gameInfo.interval)); + init(); +} + +void MPInterface::createClientGame(const RemoteHostData &rhd) +{ + QPtrList<RemoteHostData> r; + r.append((RemoteHostData *)&rhd); + internal = new Client(this, boards, r); + init(); +} + +void MPInterface::createLocalGame(const ConnectionData &cd) +{ + _server = cd.server; + nbLocalHumans = 0; + for (uint i=0; i<cd.rhd.bds.count(); i++) + if ( cd.rhd.bds[i].type==PlayerComboBox::Human ) nbLocalHumans++; + + // add or remove boards + uint old_s = boards.count(); + uint new_s = cd.rhd.bds.count(); + for (uint i=new_s; i<old_s; i++) { + delete boards[i].ptr; + boards.remove(boards.at(i)); + } + Data d; + for(uint i=old_s; i<new_s; i++) { + d.ptr = newBoard(i); + hbl->addWidget(d.ptr); + d.ptr->show(); + connect(d.ptr, SIGNAL(enableKeys(bool)), SLOT(enableKeys(bool))); + boards += d; + } + + // init local boards + _keyData->setCurrentNb(nbLocalHumans); + int k = 0; + for (uint i=0; i<boards.count(); i++) { + bool h = ( cd.rhd.bds[i].type==PlayerComboBox::Human ); + boards[i].humanIndex = (h ? k : -1); + if (h) { + _keyData->createActionCollection(k, boards[i].ptr); + k++; + } + boards[i].name = cd.rhd.bds[i].name; + boards[i].ptr->init(!h, cd.network || boards.count()>1, _server, i==0, + cd.rhd.bds[i].name); + } +} + +/*****************************************************************************/ +/* Key management */ +/*****************************************************************************/ +void MPInterface::setDefaultKeycodes(uint nb, uint i, const int *def) +{ + _keyData->setKeycodes(nb, i, def); +} + +void MPInterface::addKeys(KKeyDialog &d) +{ + _keyData->addKeys(d); +} + +void MPInterface::saveKeys() +{ + _keyData->save(); +} + +void MPInterface::enableKeys(bool enable) +{ + if ( nbLocalHumans==0 ) return; + // find the sending board + uint i; + for (i=0; i<boards.count(); i++) if ( sender()==boards[i].ptr ) break; + int hi = boards[i].humanIndex; + if ( hi==-1 ) return; // AI board (no keys) + _keyData->setEnabled(hi, enable); +} + +void MPInterface::keyPressEvent(QKeyEvent *e) +{ + _keyData->keyEvent(e, true); +} + +void MPInterface::keyReleaseEvent(QKeyEvent *e) +{ + _keyData->keyEvent(e, false); +} + +/*****************************************************************************/ +/* Misc. functions */ +/*****************************************************************************/ +uint MPInterface::nbPlayers() const +{ + return internal->nbPlayers(); +} + +QString MPInterface::playerName(uint i) const +{ + Q_ASSERT(_server); + return internal->playerName(i); +} + +QDataStream &MPInterface::readingStream(uint i) const +{ + Q_ASSERT(_server); + return internal->ioBuffer(i)->reading; +} + +QDataStream &MPInterface::writingStream(uint i) const +{ + return internal->ioBuffer(i)->writing; +} + +QDataStream &MPInterface::dataToClientsStream() const +{ + Q_ASSERT(_server); + return *internal->globalStream(); +} + +void MPInterface::immediateWrite() +{ + internal->writeData(_server); +} + +void MPInterface::hostDisconnected(uint, const QString &msg) +{ + errorBox(msg, QString::null, this); + + if ( !disconnected ) { // to avoid multiple calls + disconnected = TRUE; + // the zero timer is used to be outside the "internal" class + QTimer::singleShot(0, this, SLOT(singleHumanSlot())); + } +} + +void MPInterface::singleHumanSlot() +{ + disconnected = FALSE; + singleHuman(); +} + +void MPInterface::paintEvent(QPaintEvent *e) +{ + QPainter p(this); + p.fillRect(e->rect(), darkGray); +} diff --git a/libksirtet/lib/mp_interface.h b/libksirtet/lib/mp_interface.h new file mode 100644 index 00000000..ad925cba --- /dev/null +++ b/libksirtet/lib/mp_interface.h @@ -0,0 +1,246 @@ +#ifndef MP_INTERFACE_H +#define MP_INTERFACE_H + +#include <qwidget.h> +#include <qvaluelist.h> +#include <qptrlist.h> + +#include "mp_board.h" +#include "mp_option.h" + +class QHBoxLayout; +class Local; +class ConnectionData; +class RemoteHostData; +class KeyData; +class KeyCollection; +class KKeyDialog; +class KAction; + +struct ActionData { + const char *label, *name; + const char *slot, *slotRelease; // if slotRelease!=0 + // : use keyPress/ReleaseEvent mecanism +}; + +/** + * This structure contains information about the game + * configuration. + */ +typedef struct { + /** The game version id used for identification (e.g. "4"). + * You should change this id when the game is made incompatible + * with previous version. (changes in data for example). + */ + const char *gameId; + + /** Maximum number of local players. */ + uint maxNbLocalPlayers; + + /** Interval (in msec.) between data exchange. */ + uint interval; + + /** If there are built-in artificial intelligences that can play. */ + bool AIAllowed; + + /** Slot for player/AI additional configuration. These must be SLOTs which + * take an "int" as parameter. It must open a setting + * dialog for the corresponding local player/computer and save the + * new settings in the config file. It should probably create a group + * with the given number in its name. + * If such a pointer is set to 0 : it means there is no perticular + * setting. + */ + const char *humanSettingSlot, *AISettingSlot; +} MPGameInfo; + +/** + * The MPInterface class is useful for multiplayers game + * management. Each game is represented by a class you have inherited + * from the @ref MPBoard class. + * + * Multiplayers games can take place with several (humans or eventually + * AIs) players on the same computer (they use the same keyboard and have + * each a @ref MPBoard widget on the screen) or/and network players. + * + * This class is intended to do all the hard work of sending/receiving data + * between the players and to send the keyboard events to the right + * @ref MPBoard. So multiplayers game should be completely transparent + * from your point of view. + * + * Note : The data exchange is done in background with a timer calling at given + * intervals the read/write methods. Obviously this kind of things can be done + * easily with threads but I have no experience with thread programming + * and not all people have thread libraries and a thread-safe system. + */ +class MPInterface : public QWidget +{ + Q_OBJECT + + public: + /** Constructor which takes a MPGameInfo struct as parameter. + */ + MPInterface(const MPGameInfo &gameInfo, + uint nbActions, const ActionData *data, + QWidget *parent = 0, const char *name = 0); + virtual ~MPInterface(); + + public slots: + /** Create a single player game for a human being. + * Call @ref stop if a game is already created. */ + void singleHuman() { specialLocalGame(1, 0); } + /** Create a local game opposing two human beings. + * Call @ref stop if a game is already created. */ + void humanVsHuman() { specialLocalGame(2, 0); } + /** Create a local game opposing a human with an AI. + * Call @ref stop if a game is already created. */ + void humanVsComputer() { specialLocalGame(1, 1); } + + /** Open a dialog to create a multiplayer game. + * Call @ref stop if a game is already created. */ + void dialog(); + + public: + virtual void addKeys(KKeyDialog &); + void saveKeys(); + + /** Called when a new game is created. At this point + * the number of players is known. */ + virtual void init() {} + + /** Called just before a new game is created (called by + * singleHuman, humanVsHuman, humanVsComputer and dialog). */ + virtual void stop() {} + + /** Called when the start button of the netmeeting is pressed. */ + virtual void start() {} + + /** + * Set keys configuration for the given number of human players. + * The size of the array is the number of defined actions. + */ + void setDefaultKeycodes(uint nbHumans, uint human, const int *keycodes); + + /** + * @return the total number of players. + * (If called from client : return the number of local boards). + */ + uint nbPlayers() const; + + /** + * @return true if the interface is the server. + */ + bool server() const { return _server; } + + /** @return the player name. + Do not call from client ! + */ + QString playerName(uint i) const; + + /** + * Create a new @ref MPBoard. + * + * @param i is the game index that goes from 0 to the number of + * local players : it can be used to retrieve configuration settings. + */ + virtual MPBoard *newBoard(uint i) = 0; + + /** + * This method must read data from each client with method + * @ref readingStream, do the needed treatement + * (for instance which players has lost, which data to be resent, ...) and + * then write the useful data to each client with method + * @ref writingStream. + * + * NB: this method is also called for single player games but + * you probably only want to check for game over condition (it depends + * on game implementation that you really return data to the board). + */ + virtual void treatData() = 0; + + /** @return the reading stream for board #i. + * Do not call from client ! + */ + QDataStream &readingStream(uint i) const; + + /** @return the writing stream for board #i. + */ + QDataStream &writingStream(uint i) const; + + /** + * Read data sent from server to clients "MultiplayersInterface" + * (this data is not addressed to boards). + * These are meta data that are not directly used in game. + * It can be used to display "game over" infos for all + * local games. + * NB: the use of this method is optional. + */ + virtual void dataFromServer(QDataStream &) {} + + /** Used by the server to write meta data to clients. + * NB: the use of this method is optional. + * Do not call from client ! + */ + QDataStream &dataToClientsStream() const; + + /** Write immediately data to clients and local boards. + * It is unlike the normal exchange which is driven + * by the timer of the server. Be aware of possible + * interactions. + */ + void immediateWrite(); + + /** + * This method should be overload if an option widget is used in the + * the "netmeeting" dialog). By default a + * null pointer is returned and so no option widget is shown. + * The option widget must be inherited from the @ref MPOptionWidget class. + */ + virtual MPOptionWidget *newOptionWidget() const { return 0; } + + /** Called when a network error occurred or when a host gets disconnected. + * The default implementation displays a message and calls singleHumans() + * ie it stops the current game. By overloading this method, it is + * possible to continue the game at this point with the remaining players. + */ + virtual void hostDisconnected(uint i, const QString &msg); + + protected: + void paintEvent(QPaintEvent *); + void keyPressEvent(QKeyEvent *); + void keyReleaseEvent(QKeyEvent *); + + private slots: + void enableKeys(bool enable); + void singleHumanSlot(); + + public: + class Data { + public: + Data() {} + MPBoard *ptr; + int humanIndex; + QString name; + }; + + private: + Local *internal; + const MPGameInfo gameInfo; + QValueList<Data> boards; + uint nbLocalHumans; + QHBoxLayout *hbl; + bool _server, disconnected; + + KeyData *_keyData; + QMemArray<KeyCollection *> _keyCol; + + void createServerGame(const QPtrList<RemoteHostData> &); + void createClientGame(const RemoteHostData &); + void createLocalGame(const ConnectionData &); + void specialLocalGame(uint nbHumans, uint nbComputers); + + void clear(); + void initKeys(uint nbHumans); +}; + +#endif // MP_INTERFACE_H diff --git a/libksirtet/lib/mp_option.h b/libksirtet/lib/mp_option.h new file mode 100644 index 00000000..94c59107 --- /dev/null +++ b/libksirtet/lib/mp_option.h @@ -0,0 +1,74 @@ +#ifndef MP_OPTION_H +#define MP_OPTION_H + +#include <qwidget.h> + +/** + * The OptionWidget is a base widget for the option widget in the + * "netmeeting" dialog. This option widget is optional (!). + * + * For example you will have : + * <PRE> + * class MyOptionWidget : public OptionWidget { ... }; + * class MyMultiPlayerInterface : public MultiPlayerInterface { ... }; + * + * OptionWidget *MyMultiPlayerInterface::newOptionWidget(bool server) const + * { return new MyOptionWidget(server); }; + * </PRE> + * + * The option widget must have two different behaviours for server and + * clients. The server is able to change the options but the clients are only + * able to see them. The library will catch the @ref #changed signal from + * the option widget and will send the changes to the clients. It uses the + * @ref #dataOut to obtain the data from the + * server option dialog and then on the client side, it sets the new data with + * the method @ref #dataIn. + * You must implement this three methods to have useful option widgets and be + * careful to emit the signal @ref #changed whenever the server widget is + * changed. In addition you'll need to implement the method @ref #saveData + * to save the configuration in the config file ; so that it will be available + * for the initialisation of @ref LocalBoard. + * It seems a good idea that the widget have the same layout + * on both (server and client) sides but with the inner widgets all disabled + * on the client side. + */ +class MPOptionWidget : public QWidget +{ + Q_OBJECT + + public: + MPOptionWidget(bool Server, QWidget *parent = 0, const char *name = 0) + : QWidget(parent, name), server(Server) {} + virtual ~MPOptionWidget() {} + + bool isServer() const { return server; } + + /** + * This method is used on the client side to set the data coming from + * the server widget. + */ + virtual void dataIn(QDataStream &s) = 0; + + /** This method is used on the server side to get the data. */ + virtual void dataOut(QDataStream &s) const = 0; + + /** + * When the game will begin (ie when the "netmeeting" is over and the + * server has press the "start game" button) this method will be called + * (for clients and server). It must save the settings in the config + * file. + */ + virtual void saveData() = 0; + + signals: + /** + * This signal must be called each time options are changed + * (by the server). + */ + void changed(); + + private: + bool server; +}; + +#endif // MP_OPTION_H diff --git a/libksirtet/lib/mp_simple_board.cpp b/libksirtet/lib/mp_simple_board.cpp new file mode 100644 index 00000000..f032c488 --- /dev/null +++ b/libksirtet/lib/mp_simple_board.cpp @@ -0,0 +1,84 @@ +#include "mp_simple_board.h" +#include "mp_simple_board.moc" + + +void MPSimpleBoard::init(bool AI, bool multiplayers, bool server, bool first, + const QString &name) +{ + state = BS_Init; + _init(AI, multiplayers, server, first, name); +} + +void MPSimpleBoard::dataIn(QDataStream &s) +{ + if ( s.atEnd() ) return; // no data + + IO_Flag f; + s >> f; + switch ( f.value() ) { + case IO_Flag::Init: initFlag(s); break; + case IO_Flag::Play: playFlag(s); break; + case IO_Flag::Pause: pauseFlag(); break; + case IO_Flag::GameOver: gameOverFlag(); break; + case IO_Flag::Stop: stopFlag(); break; + } +} + +void MPSimpleBoard::initFlag(QDataStream &s) +{ + state = BS_Play; + emit enableKeys(true); + _initFlag(s); +} + +void MPSimpleBoard::playFlag(QDataStream &s) +{ + Q_ASSERT( state==BS_Play ); + _playFlag(s); +} + +void MPSimpleBoard::pauseFlag() +{ + Q_ASSERT( state==BS_Play || state==BS_Pause ); + bool p = ( state==BS_Pause ); + state = (p ? BS_Play : BS_Pause); + emit enableKeys(p); + _pauseFlag(!p); +} + +void MPSimpleBoard::gameOverFlag() +{ + Q_ASSERT( BS_Play ); + _stop(TRUE); + state = BS_Stop; +} + +void MPSimpleBoard::stopFlag() +{ + _stop(FALSE); + state = BS_Standby; +} + +void MPSimpleBoard::_stop(bool gameover) +{ + if ( state==BS_Pause ) _pauseFlag(FALSE); + emit enableKeys(false); + _stopFlag(gameover); +} + +void MPSimpleBoard::dataOut(QDataStream &s) +{ + switch (state) { + case BS_Init: + _initDataOut(s); + state = BS_Standby; + return; + case BS_Play: _dataOut(s); return; + case BS_Stop: + _gameOverDataOut(s); + state = BS_Standby; + return; + case BS_Pause: return; + case BS_Standby: return; + } +} diff --git a/libksirtet/lib/mp_simple_board.h b/libksirtet/lib/mp_simple_board.h new file mode 100644 index 00000000..2131feb2 --- /dev/null +++ b/libksirtet/lib/mp_simple_board.h @@ -0,0 +1,45 @@ +#ifndef MP_SIMPLE_BOARD_H +#define MP_SIMPLE_BOARD_H + +#include "mp_board.h" +#include "mp_simple_types.h" + +#include <kdemacros.h> + +class KDE_EXPORT MPSimpleBoard : public MPBoard +{ + Q_OBJECT + + public: + MPSimpleBoard(QWidget *parent = 0, const char *name = 0) + : MPBoard(parent, name) {} + virtual ~MPSimpleBoard() {} + + void init(bool AI, bool multiplayers, bool server, bool first, + const QString &name); + void dataOut(QDataStream &s); + void dataIn(QDataStream &s); + + protected: + virtual void _init(bool AI, bool multiplayers, bool server, bool first, + const QString &name) = 0; + virtual void _initFlag(QDataStream &s) = 0; + virtual void _playFlag(QDataStream &s) = 0; + virtual void _pauseFlag(bool pause) = 0; + virtual void _stopFlag(bool gameover) = 0; + virtual void _dataOut(QDataStream &s) = 0; + virtual void _gameOverDataOut(QDataStream &s) = 0; + virtual void _initDataOut(QDataStream &s) = 0; + + private: + BoardState state; + + void initFlag(QDataStream &s); + void playFlag(QDataStream &s); + void pauseFlag(); + void stopFlag(); + void gameOverFlag(); + void _stop(bool button); +}; + +#endif // MP_SIMPLE_BOARD_H diff --git a/libksirtet/lib/mp_simple_interface.cpp b/libksirtet/lib/mp_simple_interface.cpp new file mode 100644 index 00000000..e75243a6 --- /dev/null +++ b/libksirtet/lib/mp_simple_interface.cpp @@ -0,0 +1,152 @@ +#include "mp_simple_interface.h" +#include "mp_simple_interface.moc" + +#include <kmessagebox.h> +#include <qtimer.h> +#include <klocale.h> +#include <kaction.h> +#include <kmainwindow.h> + + +#define PAUSE_ACTION \ + ((KToggleAction *)((KMainWindow *)topLevelWidget())->action("game_pause")) + +MPSimpleInterface::MPSimpleInterface(const MPGameInfo &gi, + uint nbActions, const ActionData *data, + QWidget *parent, const char *name) +: MPInterface(gi, nbActions, data, parent, name), state(SS_Standby) +{} + +void MPSimpleInterface::init() +{ + if ( server() ) { + state = SS_Standby; + first_init = TRUE; + } + _init(); +} + +void MPSimpleInterface::start() +{ + // WARNING : multiple calls can happen here (because button + // hiding is delayed) + state = SS_Init; +} + +void MPSimpleInterface::stop() +{ + state = SS_Standby; + SC_Flag f1(SC_Flag::Stop); + if ( server() ) dataToClientsStream() << f1; + IO_Flag f2(IO_Flag::Stop); + for (uint i=0; i<nbPlayers(); i++) writingStream(i) << f2; + immediateWrite(); +} + +void MPSimpleInterface::addKeys(KKeyDialog &d) +{ + if ( !isPaused() ) pause(); + MPInterface::addKeys(d); +} + +void MPSimpleInterface::pause() +{ + // WARNING : multiple calls can happen here (because button + // hiding is delayed) + switch (state) { + case SS_Play: + state = SS_PauseAsked; + break; + case SS_Pause: + state = SS_UnpauseAsked; + break; + default: break; + } +} + +void MPSimpleInterface::dataFromServer(QDataStream &s) +{ + if ( s.atEnd() ) return; // no data + + SC_Flag scf; + s >> scf; + switch (scf.value()) { + case SC_Flag::Stop: + KMessageBox::information(this, i18n("Server has left game!")); + QTimer::singleShot(0, this, SLOT(singleHuman())); + return; + case SC_Flag::GameOver: + _readGameOverData(s); + _showGameOverData(); + return; + } +} + +void MPSimpleInterface::treatData() +{ + switch (state) { + case SS_Init: treatInit(); break; + case SS_Play: treatPlay(); break; + case SS_Pause: break; + case SS_Stop: treatStop(); break; + case SS_Standby: break; + case SS_PauseAsked: treatPause(TRUE); break; + case SS_UnpauseAsked: treatPause(FALSE); break; + } +} + +void MPSimpleInterface::treatInit() +{ + state = SS_Play; + + if (first_init) { + _firstInit(); + first_init = FALSE; + } + + IO_Flag f(IO_Flag::Init); + for (uint i=0; i<nbPlayers(); i++) writingStream(i) << f; + _treatInit(); +} + +void MPSimpleInterface::treatPlay() +{ + PAUSE_ACTION->setEnabled(true); + PAUSE_ACTION->setChecked(false); + + bool end = _readPlayData(); + if (end) { + state = SS_Stop; + IO_Flag f(IO_Flag::GameOver); + for (uint i=0; i<nbPlayers(); i++) writingStream(i) << f; + return; + } + if ( nbPlayers()==1 ) return; // no need to send data for singleplayer game + IO_Flag f(IO_Flag::Play); + for(uint i=0; i<nbPlayers(); i++) writingStream(i) << f; + _sendPlayData(); +} + +void MPSimpleInterface::treatPause(bool pause) +{ + state = (pause ? SS_Pause : SS_Play); + IO_Flag f(IO_Flag::Pause); + for (uint i=0; i<nbPlayers(); i++) writingStream(i) << f; + + PAUSE_ACTION->setChecked(pause); +} + +void MPSimpleInterface::treatStop() +{ + state = SS_Standby; + + // read game over data + send them to all clients + QDataStream &s = dataToClientsStream(); + SC_Flag f(SC_Flag::GameOver); + s << f; + _sendGameOverData(s); + _showGameOverData(); + + PAUSE_ACTION->setEnabled(false); + PAUSE_ACTION->setChecked(false); +} diff --git a/libksirtet/lib/mp_simple_interface.h b/libksirtet/lib/mp_simple_interface.h new file mode 100644 index 00000000..84e6f444 --- /dev/null +++ b/libksirtet/lib/mp_simple_interface.h @@ -0,0 +1,48 @@ +#ifndef MP_SIMPLE_INTERFACE_H +#define MP_SIMPLE_INTERFACE_H + +#include "mp_interface.h" +#include "mp_simple_types.h" + +class MPSimpleInterface : public MPInterface +{ + Q_OBJECT + + public: + MPSimpleInterface(const MPGameInfo &gi, + uint nbActions, const ActionData *data, + QWidget *parent = 0, const char *name = 0); + + bool isPaused() const { return state==SS_Pause; } + + public slots: + void start(); + void pause(); + void addKeys(KKeyDialog &); + + protected: + virtual void _init() = 0; + virtual void _readGameOverData(QDataStream &s) = 0; + virtual void _sendGameOverData(QDataStream &s) = 0; + virtual void _showGameOverData() = 0; + virtual void _firstInit() = 0; + virtual void _treatInit() = 0; + virtual bool _readPlayData() = 0; + virtual void _sendPlayData() = 0; + + private: + ServerState state; + bool first_init; + + void treatData(); + void treatInit(); + void treatPlay(); + void treatPause(bool pause); + void treatStop(); + + void init(); + void stop(); + void dataFromServer(QDataStream &); +}; + +#endif // MP_SIMPLE_INTERFACE_H diff --git a/libksirtet/lib/mp_simple_types.cpp b/libksirtet/lib/mp_simple_types.cpp new file mode 100644 index 00000000..a470ce7c --- /dev/null +++ b/libksirtet/lib/mp_simple_types.cpp @@ -0,0 +1,6 @@ +#include "mp_simple_types.h" + +QDataStream &operator <<(QDataStream &s, const EnumClass &ec) + { s << (Q_UINT8)ec.f; return s; } +QDataStream &operator >>(QDataStream &s, EnumClass &ec) + { Q_UINT8 t; s >> t; ec.f = (int)t; return s; } diff --git a/libksirtet/lib/mp_simple_types.h b/libksirtet/lib/mp_simple_types.h new file mode 100644 index 00000000..2fd0c289 --- /dev/null +++ b/libksirtet/lib/mp_simple_types.h @@ -0,0 +1,36 @@ +#ifndef WF_TYPES_H +#define WF_TYPES_H + +#include <qdatastream.h> + +class EnumClass +{ + public: + EnumClass(int _f) : f(_f) {} + int f; +}; +QDataStream &operator <<(QDataStream &s, const EnumClass &f); +QDataStream &operator >>(QDataStream &s, EnumClass &f); + +class IO_Flag : public EnumClass +{ + public: + enum IOF { Init = 0, Play, Pause, Stop, GameOver }; + IO_Flag(IOF f = Init) : EnumClass(f) {} + IOF value() const { return (IOF)f; } +}; + +enum ServerState { SS_Init, SS_Play, SS_Pause, SS_Stop, SS_Standby, + SS_PauseAsked, SS_UnpauseAsked }; + +class SC_Flag : public EnumClass +{ + public: + enum SC { Stop = 0, GameOver }; + SC_Flag(SC f = Stop) : EnumClass(f) {} + SC value() const { return (SC)f; } +}; + +enum BoardState { BS_Init, BS_Play, BS_Pause, BS_Stop, BS_Standby }; + +#endif diff --git a/libksirtet/lib/pline.cpp b/libksirtet/lib/pline.cpp new file mode 100644 index 00000000..41faf6ac --- /dev/null +++ b/libksirtet/lib/pline.cpp @@ -0,0 +1,147 @@ +#include "pline.h" +#include "pline.moc" + +#include <qfont.h> +#include <qpushbutton.h> +#include <klocale.h> +#include "defines.h" + +#define THIN_BORDER 4 + +MeetingLine::MeetingLine(bool isOwner, bool serverIsReader, bool serverLine, + QWidget *parent, const char *name) +: QFrame(parent, name) +{ + setFrameStyle(Panel | (serverLine ? Raised : Plain)); + + // Top layout + hbl = new QHBoxLayout(this, THIN_BORDER + frameWidth()); + + /* TriCheckBox */ + tcb = new MeetingCheckBox(MeetingCheckBox::Ready, isOwner, serverIsReader, + this); + if ( !XOR(isOwner, serverIsReader) ) tcb->setEnabled(FALSE); + else connect(tcb, SIGNAL(changed(int)), SLOT(_typeChanged(int))); + hbl->addWidget(tcb); + + /* Name */ + lname = new QLabel(" ", this); + lname->setAlignment(AlignCenter); + lname->setFrameStyle(QFrame::Panel | QFrame::Sunken); + lname->setLineWidth(2); + lname->setMidLineWidth(3); + QFont f = lname->font(); + f.setBold(TRUE); + lname->setFont(f); + lname->setFixedSize(lname->fontMetrics().maxWidth()*NAME_MAX_LENGTH, + lname->sizeHint().height()); + hbl->addWidget(lname); + hbl->addStretch(1); + + // Nb humans + labH = new QLabel(this); + hbl->addWidget(labH); + + // Nb AIs + labAI = new QLabel(this); + hbl->addWidget(labAI); + + // talker + qle = new QLineEdit(this); + qle->setMaxLength(TALKER_MAX_LENGTH); + qle->setFont( QFont("fixed", 12, QFont::Bold) ); + qle->setFixedSize(qle->fontMetrics().maxWidth()*TALKER_MAX_LENGTH, + qle->sizeHint().height()); + connect(qle, SIGNAL(textChanged(const QString &)), + SLOT(_textChanged(const QString &))); + qle->setEnabled(isOwner); + hbl->addWidget(qle); +} + +void MeetingLine::setData(const ExtData &ed) +{ + bds = ed.bds; + uint nbh = 0, nba = 0; + for (uint i=0; i<bds.count(); i++) { + if ( bds[i].type==PlayerComboBox::Human ) nbh++; + else if ( bds[i].type==PlayerComboBox::AI ) nba++; + } + labH->setText(i18n("Hu=%1").arg(nbh)); + labAI->setText(i18n("AI=%1").arg(nba)); + lname->setText(bds[0].name); + setType(ed.type); + setText(ed.text); +} + +void MeetingLine::data(ExtData &ed) const +{ + ed.bds = bds; + ed.type = tcb->type(); + ed.text = text(); +} + +/*****************************************************************************/ +PlayerLine::PlayerLine(PlayerComboBox::Type type, const QString &txt, + bool humanSetting, bool AISetting, + bool canBeEmpty, bool acceptAI, + QWidget *parent, const char *name) +: QFrame(parent, name), hs(humanSetting), as(AISetting) +{ + setFrameStyle(Panel | Raised); + + // Top layout + QHBoxLayout *hbl; + hbl = new QHBoxLayout(this, THIN_BORDER + frameWidth()); + + /* CheckBox */ + pcb = new PlayerComboBox(type, canBeEmpty, acceptAI, this); + connect(pcb, SIGNAL(changed(int)), SLOT(typeChangedSlot(int))); + hbl->addWidget(pcb); + + /* Name */ + edit = new QLineEdit(txt, this); + edit->setMaxLength(NAME_MAX_LENGTH); + edit->setFixedSize(edit->fontMetrics().maxWidth()*(NAME_MAX_LENGTH+2), + edit->sizeHint().height()); + hbl->addWidget(edit); + + /* settings button */ + setting = new QPushButton(i18n("Settings"), this); + connect(setting, SIGNAL(clicked()), SLOT(setSlot())); + hbl->addWidget(setting); + + typeChangedSlot(type); +} + +void PlayerLine::typeChangedSlot(int t) +{ + edit->setEnabled(type()!=PlayerComboBox::None); + setting->setEnabled( (type()==PlayerComboBox::Human && hs) + || (type()==PlayerComboBox::AI && as) ); + emit typeChanged(t); +} + +void PlayerLine::setSlot() +{ + if ( type()==PlayerComboBox::Human ) emit setHuman(); + else emit setAI(); +} + +/*****************************************************************************/ +GWidgetList::GWidgetList(uint interval, QWidget *parent, const char * name) + : QWidget(parent, name), vbl(this, interval) +{ + widgets.setAutoDelete(TRUE); +} + +void GWidgetList::append(QWidget *wi) +{ + vbl.addWidget(wi); + wi->show(); + widgets.append(wi); +} + +void GWidgetList::remove(uint i) +{ + widgets.remove(i); +} diff --git a/libksirtet/lib/pline.h b/libksirtet/lib/pline.h new file mode 100644 index 00000000..2defd10a --- /dev/null +++ b/libksirtet/lib/pline.h @@ -0,0 +1,112 @@ +#ifndef PLINE_H +#define PLINE_H + +#include <qframe.h> +#include <qscrollbar.h> +#include <qlineedit.h> +#include <qlabel.h> +#include <qptrlist.h> +#include <qlayout.h> + +#include "types.h" + +class QPushButton; + +/** Internal class : display a "player line" in netmeeting. */ +class MeetingLine : public QFrame +{ + Q_OBJECT + + public: + MeetingLine(bool isOwner, bool readerIsServer, bool serverLine, + QWidget *parent, const char *name = 0); + + MeetingCheckBox::Type type() const { return tcb->type(); } + void setType(MeetingCheckBox::Type type) { tcb->setType(type); } + void setText(const QString &text) { qle->setText(text); } + + void setData(const ExtData &ed); + void data(ExtData &ed) const; + QString text() const { return qle->text(); } + + signals: + void typeChanged(MeetingCheckBox::Type); + void textChanged(const QString &); + + private slots: + void _typeChanged(int t) + { emit typeChanged((MeetingCheckBox::Type)t); } + void _textChanged(const QString &text) { emit textChanged(text); } + + protected: + QHBoxLayout *hbl; + + private: + MeetingCheckBox *tcb; + QLabel *lname, *labH, *labAI; + QValueList<BoardData> bds; + QLineEdit *qle; +}; + +class PlayerLine : public QFrame +{ + Q_OBJECT + + public: + PlayerLine(PlayerComboBox::Type type, const QString &txt, + bool humanSetting, bool AISetting, + bool canBeEmpty, bool acceptAI, + QWidget *parent = 0, const char *name = 0); + + PlayerComboBox::Type type() const { return pcb->type(); } + QString name() const { return edit->text(); } + + signals: + void setHuman(); + void setAI(); + void typeChanged(int); + + private slots: + void setSlot(); + void typeChangedSlot(int); + + private: + PlayerComboBox *pcb; + QLineEdit *edit; + QPushButton *setting; + bool hs, as; +}; + +/** Internal class : scrolable list of widgets. */ +class GWidgetList : public QWidget +{ + Q_OBJECT + + public: + GWidgetList(uint interval, QWidget *parent = 0, const char * name = 0); + + void remove(uint i); + uint size() const { return widgets.count(); } + + protected: + /** The widget must be created with this widget as parent. */ + void append(QWidget *); + QWidget *widget(uint i) { return widgets.at(i); } + + private: + QPtrList<QWidget> widgets; + QVBoxLayout vbl; +}; + +template <class Type> +class WidgetList : public GWidgetList +{ + public: + WidgetList(uint interval, QWidget *parent=0, const char *name=0) + : GWidgetList(interval, parent, name) {} + + void append(Type *w) { GWidgetList::append(w); } + Type *widget(uint i) { return (Type *)GWidgetList::widget(i); } +}; + +#endif // PLINE_H diff --git a/libksirtet/lib/smanager.cpp b/libksirtet/lib/smanager.cpp new file mode 100644 index 00000000..f6588546 --- /dev/null +++ b/libksirtet/lib/smanager.cpp @@ -0,0 +1,115 @@ +#include "smanager.h" + +#include <config.h> + +#include <ctype.h> +#include <netdb.h> +#include <sys/utsname.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <strings.h> + + +SocketManager::SocketManager() +{ + max_fd = 0; + FD_ZERO(&read_set); + FD_ZERO(&write_set); + nbWriteable = 0; +} + +SocketManager::~SocketManager() +{ + clean(); +} + +void SocketManager::clean() +{ + for(uint i=0; i<sockets.size(); i++) delete sockets[i]; + sockets.resize(0); +} + +int SocketManager::find(int fd) +{ + for(uint i=0; i<sockets.size(); i++) + if ( sockets[i]->fd()==fd ) return i; + return -1; +} + +uint SocketManager::append(Socket *socket, SocketProperty sp) +{ + uint s = sockets.size(); + sockets.resize(s+1); + sockets[s] = socket; + + max_fd = QMAX(max_fd, socket->fd()); + + if ( sp==ReadWrite || sp==ReadOnly ) FD_SET(socket->fd(), &read_set); + if ( sp==ReadWrite || sp==WriteOnly ) { + nbWriteable++; + FD_SET(socket->fd(), &write_set); + } + + return s; +} + +void SocketManager::remove(uint i, bool del) +{ + Socket *so = sockets[i]; + + uint s = sockets.size()-1; + for(uint j=i; j<s; j++) sockets[j] = sockets[j+1]; + sockets.resize(s); + + max_fd = 0; + for(uint j=0; j<s; j++) + max_fd = QMAX(max_fd, sockets[j]->fd()); + + int fd = so->fd(); + if ( FD_ISSET(fd, &read_set) ) FD_CLR(fd, &read_set); + if ( FD_ISSET(fd, &write_set) ) { + nbWriteable--; + FD_CLR(fd, &write_set); + } + + if (del) delete so; +} + +bool SocketManager::canWriteAll(uint sec, uint usec) +{ + write_tmp = write_set; + tv.tv_sec = sec; + tv.tv_usec = usec; + return ( select(max_fd+1, 0, &write_tmp, 0, &tv)==(int)nbWriteable ); +} + +bool SocketManager::canWrite(uint i, uint sec, uint usec) +{ + int fd = sockets[i]->fd(); + FD_ZERO(&write_tmp); + FD_SET(fd, &write_tmp); + tv.tv_sec = sec; + tv.tv_usec = usec; + return ( select(fd+1, 0, &write_tmp, 0, &tv)==1 ); +} + +bool SocketManager::checkPendingData(uint sec, uint usec) +{ + read_tmp = read_set; + tv.tv_sec = sec; + tv.tv_usec = usec; + return ( select(max_fd+1, &read_tmp, 0, 0, &tv)!=-1 ); +} + +bool SocketManager::dataPending(uint i) +{ + int fd = sockets[i]->fd(); + return FD_ISSET(fd, &read_tmp); +} + +bool SocketManager::writeCommon(uint i) +{ + return sockets[i]->write(writing.buffer()); +} diff --git a/libksirtet/lib/smanager.h b/libksirtet/lib/smanager.h new file mode 100644 index 00000000..a831b702 --- /dev/null +++ b/libksirtet/lib/smanager.h @@ -0,0 +1,88 @@ +#ifndef SMANAGER_H +#define SMANAGER_H + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> // Needed on some systems. +#endif + +#include <sys/time.h> + +#include "socket.h" + +/** + * The SocketManager class is useful to manage (rw, ro, wo) sockets. + * + * You must add the sockets you want to manage to this class before other + * operations. The sockets are stored in an array and other methods reference + * the sockets by their index in that array. + */ +class SocketManager +{ + public: + SocketManager(); + + /** Be aware that unremoved sockets will be closed there. */ + ~SocketManager(); + + /** Remove all sockets and close them. */ + void clean(); + + enum SocketProperty { ReadOnly, WriteOnly, ReadWrite }; + + /** @return the number of sockets. */ + uint size() const { return sockets.size(); } + + const Socket *operator[](uint i) const { return sockets[i]; } + Socket *operator [](uint i) { return sockets[i]; } + + /** @return the index of the socket (-1 if not present). */ + int find(int fd); + + /** + * Append a socket at the end of the array of sockets. + * @param sp determines if the socket will be used for ReadWrite, + * ReadOnly or WriteOnly operations. + * @return the index of the socket. + */ + uint append(Socket *, SocketProperty sp = ReadWrite); + + /** + * Remove the socket indexed <I>i</I>. Note that the following sockets in + * the array will have their index decremented by one. + * @param deleteSocket if true, the socket is deleted + */ + void remove(uint i, bool deleteSocket); + + /** @return TRUE if it is possible to write to all the writeable sockets. */ + bool canWriteAll(uint sec = 0, uint usec = 0); + + /** @return TRUE if it is possible to write to the specified socket. */ + bool canWrite(uint i, uint sec = 0, uint usec = 0); + + Stream &commonWritingStream() { return writing; } + bool writeCommon(uint i); // do not clear stream + + /** + * Check if there are pending data on at least one of the readeable + * socket. + */ + bool checkPendingData(uint sec = 0, uint usec = 0); + + bool dataPending(uint i); + + private: + QMemArray<Socket *> sockets; + + fd_set read_set, write_set, read_tmp, write_tmp; + struct timeval tv; + int max_fd; + uint nbWriteable; + + WritingStream writing; +}; + +#endif // SMANAGER_H diff --git a/libksirtet/lib/socket.cpp b/libksirtet/lib/socket.cpp new file mode 100644 index 00000000..9ef3ba3c --- /dev/null +++ b/libksirtet/lib/socket.cpp @@ -0,0 +1,80 @@ +#include "socket.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <sys/types.h> +#ifdef HAVE_SYS_SELECT_H +#include <sys/select.h> +#endif + +#include <ctype.h> +#include <netdb.h> +#include <sys/utsname.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <unistd.h> +#include <sys/ioctl.h> +#ifdef HAVE_SYS_FILIO_H +#include <sys/filio.h> // for FIONREAD +#endif + +Socket::Socket(KExtendedSocket *s, bool createNotifier, + QObject *parent, const char *name) +: _socket(s), _notifier(0) +{ + Q_ASSERT(s); + if (createNotifier) { + _notifier = new QSocketNotifier(s->fd(), QSocketNotifier::Read, + parent, name); + _notifier->setEnabled(FALSE); + } +} + +Socket::~Socket() +{ + delete _notifier; + delete _socket; +} + +bool Socket::write(const QByteArray &a) +{ + return ( _socket->writeBlock(a.data(), a.size())==(int)a.size() ); +} + +bool Socket::write() +{ + bool res = write(writing.buffer()); + writing.clear(); + return res; +} + +int Socket::pendingData() const +{ + int size = 0; + if ( ioctl(_socket->fd(), FIONREAD, (char *)&size)<0 ) return -1; + return size; +} + +int Socket::read() +{ + reading.clearRead(); + + int size = pendingData(); + if ( size==-1 ) return -1; + + reading.device()->close(); + int dec = reading.size(); + reading.buffer().resize(dec + size); + size = _socket->readBlock(reading.buffer().data() + dec, size); + if ( size==-1 ) reading.buffer().resize(dec); + reading.device()->open(IO_ReadOnly); + + return size; +} + +int Socket::accept(KExtendedSocket *&s) +{ + return _socket->accept(s); +} diff --git a/libksirtet/lib/socket.h b/libksirtet/lib/socket.h new file mode 100644 index 00000000..a2f47a63 --- /dev/null +++ b/libksirtet/lib/socket.h @@ -0,0 +1,65 @@ +#ifndef SOCKET_H +#define SOCKET_H + +#include <qsocketnotifier.h> + +#include <kextsock.h> + +#include "types.h" + + +class Socket +{ + public: + Socket(KExtendedSocket *, bool createNotifier = FALSE, + QObject *parent = 0, const char *name = 0); + + /** close the socket */ + ~Socket(); + + int fd() const { return _socket->fd(); } + + /** + * Accept a new socket. + */ + int accept(KExtendedSocket *&); + + /** + * @return the socket notifier associated with the socket + * (0 if none). + */ + QSocketNotifier *notifier() const { return _notifier; } + + /** + * Write data contained in the writing stream to the socket. + * It clears the stream. + * @return TRUE if all was written without error. + */ + bool write(); + bool write(const QByteArray &a); + + /** @return the QDataStream for writing. */ + WritingStream &writingStream() { return writing; } + + /** @return the size of pending data. */ + int pendingData() const; + + /** + * Read data from socket and append them to reading stream for the specified socket. + * The portion of the stream that has been read is cleared. + * @return the read size or -1 on error + */ + int read(); + + /** @return the reading stream. */ + ReadingStream &readingStream() { return reading; } + + private: + KExtendedSocket *_socket; + QSocketNotifier *_notifier; + + WritingStream writing; + ReadingStream reading; +}; + +#endif // SOCKET_H diff --git a/libksirtet/lib/types.cpp b/libksirtet/lib/types.cpp new file mode 100644 index 00000000..557fffff --- /dev/null +++ b/libksirtet/lib/types.cpp @@ -0,0 +1,254 @@ +#include "types.h" + +#include <klocale.h> +#include "version.h" + +cId::cId(const QString &_gameName, const QString &_gameId) +: libId(MULTI_ID), gameName(_gameName), gameId(_gameId) +{} + +void cId::check(const cId &id) +{ + if ( libId!=id.libId ) state = LibIdClash; + else if ( gameName!=id.gameName ) state = GameNameClash; + else if ( gameId!=id.gameId ) state = GameIdClash; + else state = Accepted; +} + +QString cId::errorMessage(const cId &id) const +{ + QString str = i18n("\nServer: \"%1\"\nClient: \"%2\""); + + switch (state) { + case Accepted: return QString::null; + case LibIdClash: + return i18n("The MultiPlayer library of the server is incompatible") + + str.arg(libId).arg(id.libId); + case GameNameClash: + return i18n("Trying to connect a server for another game type") + + str.arg(gameName).arg(id.gameName); + case GameIdClash: + return i18n("The server game version is incompatible") + + str.arg(gameId).arg(id.gameId); + } + Q_ASSERT(0); + return QString::null; +} + +QDataStream &operator << (QDataStream &s, const cId &id) +{ + s << id.libId << id.gameName << id.gameId << (Q_UINT8)id.state; + return s; +} + +QDataStream &operator >> (QDataStream &s, cId &id) +{ + Q_UINT8 state; + s >> id.libId >> id.gameName >> id.gameId >> state; + id.state = (cId::State)state; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const MeetingMsgFlag &f) +{ + s << (Q_UINT8)f; + return s; +} + +QDataStream &operator >> (QDataStream &s, MeetingMsgFlag &f) +{ + Q_UINT8 i; + s >> i; f = (MeetingMsgFlag)i; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const TextInfo &ti) +{ + s << (Q_UINT32)ti.i << ti.text; + return s; +} + +QDataStream &operator >> (QDataStream &s, TextInfo &ti) +{ + Q_UINT32 i; + s >> i >> ti.text; ti.i = i; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const MeetingCheckBox::Type &t) +{ + s << (Q_UINT8)t; + return s; +} + +QDataStream &operator >> (QDataStream &s, MeetingCheckBox::Type &t) +{ + Q_UINT8 i; + s >> i; t = (MeetingCheckBox::Type)i; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const TypeInfo &ti) +{ + s << (Q_UINT32)ti.i << ti.type; + return s; +} + +QDataStream &operator >> (QDataStream &s, TypeInfo &ti) +{ + Q_UINT32 i; + s >> i >> ti.type; ti.i = i; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const BoardData &bd) +{ + s << (Q_UINT8)bd.type << bd.name; + return s; +} + +QDataStream &operator >> (QDataStream &s, BoardData &bd) +{ + Q_UINT8 i; + s >> i >> bd.name; + bd.type = (PlayerComboBox::Type)i; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const ExtData &ed) +{ + s << ed.bds << ed.text << ed.type; + return s; +} + +QDataStream &operator >> (QDataStream &s, ExtData &ed) +{ + s >> ed.bds >> ed.text >> ed.type; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const MeetingLineData &pld) +{ + s << pld.ed << (Q_UINT8)pld.own; + return s; +} + +QDataStream &operator >> (QDataStream &s, MeetingLineData &pld) +{ + Q_UINT8 b; + s >> pld.ed >> b; pld.own = b; + return s; +} + +//----------------------------------------------------------------------------- +QDataStream &operator << (QDataStream &s, const MetaFlag &f) +{ + s << (Q_UINT8)f; + return s; +} + +QDataStream &operator >> (QDataStream &s, MetaFlag &f) +{ + Q_UINT8 i; + s >> i; f = (MetaFlag)i; + return s; +} + + +//----------------------------------------------------------------------------- +Stream::Stream(int _mode) +: mode(_mode) +{ + setDevice(&buf); + Q_ASSERT( _mode==IO_ReadOnly || _mode==IO_WriteOnly ); + buf.open(_mode); +} + +void Stream::clear() +{ + buf.close(); + buf.open(mode | IO_Truncate); +} + +void Stream::setArray(QByteArray a) +{ + buf.close(); + buf.setBuffer(a); + buf.open(mode); +} + +bool ReadingStream::readOk() +{ + return ( buf.status()==IO_Ok ); +} + +void ReadingStream::clearRead() +{ + int i = buf.at(); + if ( i==0 ) return; + buf.close(); + QByteArray a; + a.duplicate(buffer().data() + i, size() - i); + buf.setBuffer(a); + buf.open(IO_ReadOnly); +} + +//----------------------------------------------------------------------------- +void IOBuffer::writingToReading() +{ + // this should do the trick :) + reading.setArray(writing.buffer()); + QByteArray a; + writing.setArray(a); +} + +//----------------------------------------------------------------------------- +void BufferArray::clear(uint k) +{ + for (uint i=k; i<a.size(); i++) delete a[i]; +} + +BufferArray::~BufferArray() +{ + clear(0); +} + +void BufferArray::resize(uint nb) +{ + uint s = a.size(); + if ( nb<s ) clear(nb); + a.resize(nb); + for (uint i=s; i<nb; i++) a[i] = new IOBuffer; +} + +QDataStream &operator <<(QDataStream &s, const BufferArray &b) +{ + for (uint i=0; i<b.size(); i++) { + s.writeBytes(b[i]->writing.buffer().data(), b[i]->writing.size()); +// debug("BUFFERARRAY : << (i=%i size=%i)", i, b[i]->writing.size()); + b[i]->writing.clear(); + } + return s; +} + +QDataStream &operator >>(QDataStream &s, BufferArray &b) +{ + uint size; + char *c; + for (uint i=0; i<b.size(); i++) { + s.readBytes(c, size); + QByteArray a; + a.assign(c, size); + b[i]->reading.setArray(a); +// debug("BUFFERARRAY : >> (i=%i c=%i size=%i s=%i)", +// i, (int)c, size, b[i]->reading.size()); + } + return s; +} diff --git a/libksirtet/lib/types.h b/libksirtet/lib/types.h new file mode 100644 index 00000000..2ccdcba0 --- /dev/null +++ b/libksirtet/lib/types.h @@ -0,0 +1,197 @@ +#ifndef MTYPES_H +#define MTYPES_H + +#include <qstring.h> +#include <qbuffer.h> +#include <qvaluelist.h> + +#include "miscui.h" + + +/** Internal class : used for client identification. */ +class cId +{ + public: + cId() {} + cId(const QString &gameName, const QString &gameId); + + enum State { Accepted, LibIdClash, GameNameClash, GameIdClash }; + void check(const cId &id); + bool accepted() const { return state==Accepted; } + QString errorMessage(const cId &id) const; + + friend QDataStream &operator << (QDataStream &s, const cId &id); + friend QDataStream &operator >> (QDataStream &s, cId &id); + + private: + QString libId, gameName, gameId; + State state; +}; +QDataStream &operator << (QDataStream &s, const cId &id); +QDataStream &operator >> (QDataStream &s, cId &id); + +/** Flags used for the netmeeting. */ +enum MeetingMsgFlag + { IdFlag = 0, EndFlag, NewFlag, DelFlag, Mod_TextFlag, Mod_TypeFlag, Mod_OptFlag, PlayFlag }; +QDataStream &operator << (QDataStream &s, const MeetingMsgFlag &f); +QDataStream &operator >> (QDataStream &s, MeetingMsgFlag &f); + +/** Internal class : used in netmeeting to transport text line. */ +class TextInfo +{ + public: + TextInfo() {} + + uint i; + QString text; +}; +QDataStream &operator << (QDataStream &s, const TextInfo &ti); +QDataStream &operator >> (QDataStream &s, TextInfo &ti); + +/** Internal class : used in netmeeting to transport readiness status. */ +typedef struct { + uint i; + MeetingCheckBox::Type type; +} TypeInfo; +QDataStream &operator << (QDataStream &s, const MeetingCheckBox::Type &t); +QDataStream &operator >> (QDataStream &s, MeetingCheckBox::Type &t); +QDataStream &operator << (QDataStream &s, const TypeInfo &ti); +QDataStream &operator >> (QDataStream &s, TypeInfo &ti); + +/* Internal class : store game data. */ +class BoardData +{ + public: + BoardData() {} + + QString name; + PlayerComboBox::Type type; +}; +QDataStream &operator <<(QDataStream &, const BoardData &); +QDataStream &operator >>(QDataStream &, BoardData &); + +/* Internal class : store extended game data (used in netmeeting). */ +class ExtData +{ + public: + ExtData() {} + ExtData(const QValueList<BoardData> &_bds, const QString &_text, + MeetingCheckBox::Type _type) + : bds(_bds), text(_text), type(_type) {} + + QValueList<BoardData> bds; + QString text; + MeetingCheckBox::Type type; +}; +QDataStream &operator << (QDataStream &s, const ExtData &ed); +QDataStream &operator >> (QDataStream &s, ExtData &ed); + +/* Internal class : store meeting line data (in netmeeting). */ +class MeetingLineData +{ + public: + MeetingLineData() {} + + ExtData ed; + bool own; +}; +QDataStream &operator << (QDataStream &s, const MeetingLineData &pld); +QDataStream &operator >> (QDataStream &s, MeetingLineData &pld); + +/* Internal class : store remote host data. */ +class Socket; + +class RemoteHostData +{ + public: + RemoteHostData() : socket(0) {} + + Socket *socket; + QValueList<BoardData> bds; +}; + +/* Internal class : store connection data (used by config. wizard). */ +class ConnectionData +{ + public: + ConnectionData() {} + + bool network, server; + RemoteHostData rhd; +}; + +/** Flags used for network communication. */ +enum MetaFlag { MF_Ask = 0, MF_Data }; +QDataStream &operator << (QDataStream &s, const MetaFlag &f); +QDataStream &operator >> (QDataStream &s, MetaFlag &f); + +/** Internal class : encapsulate read/write QBuffer. */ +class Stream : public QDataStream +{ + public: + Stream(int mode); + + void clear(); + void setArray(QByteArray a); + + QByteArray buffer() const { return buf.buffer(); } + uint size() const { return buf.buffer().size(); } + + protected: + QBuffer buf; + + private: + int mode; +}; + +/** Internal class : encapsulate write QBuffer. */ +class WritingStream : public Stream +{ + public: + WritingStream() : Stream(IO_WriteOnly) {} +}; + +/** Internal class : encapsulate read QBuffer. */ +class ReadingStream : public Stream +{ + public: + ReadingStream() : Stream(IO_ReadOnly) {} + + bool readOk(); + void clearRead(); +}; + +/** Internal class : include a @ref ReadingStream and a @ref WritingStream. */ +class IOBuffer +{ + public: + IOBuffer() {} + + void writingToReading(); + + ReadingStream reading; + WritingStream writing; +}; + +/** Internal class : array of @ref IOBuffer. */ +class BufferArray +{ + public: + BufferArray() {} + BufferArray(uint nb) { resize(nb); } + ~BufferArray(); + + void resize(uint nb); + + uint size() const { return a.size(); } + IOBuffer *operator [](uint i) const { return a[i]; } + + private: + QMemArray<IOBuffer *> a; + + void clear(uint nb); +}; +QDataStream &operator <<(QDataStream &s, const BufferArray &b); +QDataStream &operator >>(QDataStream &s, BufferArray &b); + +#endif // MTYPES_H diff --git a/libksirtet/lib/version.h b/libksirtet/lib/version.h new file mode 100644 index 00000000..49dc7b6b --- /dev/null +++ b/libksirtet/lib/version.h @@ -0,0 +1,6 @@ +#define MULTI_VERSION "0.1.8" +#define MULTI_LONG_VERSION "0.1.8 (11 April 2001)" +#define MULTI_COPYLEFT "(c) 1998-2001, Nicolas Hadacek" + +#define MULTI_ID "003" // should be increased when incompatible + // changes are made. diff --git a/libksirtet/lib/wizard.cpp b/libksirtet/lib/wizard.cpp new file mode 100644 index 00000000..30d5a89d --- /dev/null +++ b/libksirtet/lib/wizard.cpp @@ -0,0 +1,229 @@ +#include "wizard.h" +#include "wizard.moc" + +#include <sys/types.h> +#include <netinet/in.h> + +#include <qvbuttongroup.h> +#include <qradiobutton.h> +#include <qhbox.h> +#include <qvbox.h> +#include <qsignalmapper.h> +#include <qvgroupbox.h> +#include <qgrid.h> +#include <qfile.h> + +#include <kapplication.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kdialogbase.h> + +#include "types.h" +#include "defines.h" +#include "socket.h" + +#ifdef __bsdi__ +#define IPPORT_USERRESERVED IPPORT_DYNAMIC +#endif +#if defined(__NetBSD__) || defined(__FreeBSD__) || defined(__sgi) +#define IPPORT_USERRESERVED IPPORT_RESERVED +#endif +#define MIN_USER_PORT (unsigned short int)IPPORT_USERRESERVED +#define MAX_USER_PORT 65535 + +MPWizard::MPWizard(const MPGameInfo &gi, ConnectionData &_cd, + QWidget *parent, const char *name) +: KWizard(parent, name, TRUE), cd(_cd) +{ +// setupTypePage(); // #### REMOVE NETWORK GAMES UNTIL FIXED + type = Local; + setupLocalPage(gi); +} + +//----------------------------------------------------------------------------- +void MPWizard::setupTypePage() +{ + KConfigGroupSaver cg(kapp->config(), MP_GROUP); + + typePage = new QVBox(this); + typePage->setMargin(KDialogBase::marginHint()); + + QVButtonGroup *vbg = new QVButtonGroup(typePage); + connect(vbg, SIGNAL(clicked(int)), SLOT(typeChanged(int))); + QRadioButton *b; + b = new QRadioButton(i18n("Create a local game"), vbg); + b = new QRadioButton(i18n("Create a network game"), vbg); + b = new QRadioButton(i18n("Join a network game"), vbg); + type = (Type)cg.config()->readNumEntry(MP_GAMETYPE, 0); + if ( type<0 || type>2 ) type = Local; + vbg->setButton(type); + + typePage->setSpacing(KDialogBase::spacingHint()); + net = new QVGroupBox(i18n("Network Settings"), typePage); + QGrid *grid = new QGrid(2, net); + lserver = new QLabel(" ", grid); + grid->setSpacing(KDialogBase::spacingHint()); + eserver = new QLineEdit(grid); + (void)new QLabel(i18n("Port:"), grid); + int p = cg.config()->readNumEntry(MP_PORT, (uint)MIN_USER_PORT); + eport = new KIntNumInput(p, grid); + eport->setRange(MIN_USER_PORT, MAX_USER_PORT, 1, false); + + addPage(typePage, i18n("Choose Game Type")); + setHelpEnabled(typePage, FALSE); + typeChanged(type); +} + +//----------------------------------------------------------------------------- +void MPWizard::setupLocalPage(const MPGameInfo &gi) +{ + localPage = new QVBox(this); + localPage->setMargin(KDialogBase::marginHint()); + + wl = new WidgetList<PlayerLine>(5, localPage); + QSignalMapper *husm = new QSignalMapper(this); + if (gi.humanSettingSlot) connect(husm, SIGNAL(mapped(int)), + gi.humanSettingSlot); + QSignalMapper *aism = new QSignalMapper(this); + if (gi.AISettingSlot) connect(aism, SIGNAL(mapped(int)), gi.AISettingSlot); + + KConfigGroupSaver cg(kapp->config(), MP_GROUP); + QString n; + PlayerComboBox::Type type; + PlayerLine *pl; + Q_ASSERT( gi.maxNbLocalPlayers>0 ); + for (uint i=0; i<gi.maxNbLocalPlayers; i++) { + type = (PlayerComboBox::Type) + cg.config()->readNumEntry(QString(MP_PLAYER_TYPE).arg(i), + (i==0 ? PlayerComboBox::Human : PlayerComboBox::None)); + n = cg.config()->readEntry(QString(MP_PLAYER_NAME).arg(i), + i18n("Player #%1").arg(i)); + + pl = new PlayerLine(type, n, gi.humanSettingSlot, gi.AISettingSlot, + i!=0, gi.AIAllowed, wl); + connect(pl, SIGNAL(typeChanged(int)), SLOT(lineTypeChanged(int))); + husm->setMapping(pl, i); + connect(pl, SIGNAL(setHuman()), husm, SLOT(map())); + aism->setMapping(pl, i); + connect(pl, SIGNAL(setAI()), aism, SLOT(map())); + wl->append(pl); + } + + ((QVBox *)localPage)->setSpacing(KDialogBase::spacingHint()); + +// keys = new QPushButton(i18n("Configure Keys..."), localPage); +// connect(keys, SIGNAL(clicked()), SLOT(configureKeysSlot())); + + addPage(localPage, i18n("Local Player's Settings")); + setHelpEnabled(localPage, FALSE); + lineTypeChanged(0); +} + +QString MPWizard::name(uint i) const +{ + QString s = wl->widget(i)->name(); + if ( s.length()==0 ) s = i18n("Player #%1").arg(i); + return s; +} + +void MPWizard::typeChanged(int t) +{ + type = (Type)t; + + QString str; + if ( type!=Client ) { + str = "localhost"; + lserver->setText(i18n("Hostname:")); + } else { + KConfigGroupSaver cg(kapp->config(), MP_GROUP); + str = cg.config()->readEntry(MP_SERVER_ADDRESS, + i18n("the.server.address")); + lserver->setText(i18n("Server address:")); + } + eserver->setText(str); + eserver->setEnabled(type==Client); + eport->setEnabled(type!=Local); + net->setEnabled(type!=Local); +} + +void MPWizard::lineTypeChanged(int) +{ + bool b = FALSE; + for (uint i=0; i<wl->size(); i++) + if ( wl->widget(i)->type()==PlayerComboBox::Human ) { + b = TRUE; + break; + } +// keys->setEnabled(b); +} + +void MPWizard::accept() +{ + KConfigGroupSaver cg(kapp->config(), MP_GROUP); + + cd.network = ( type!=Local ); + cd.server = ( type!=Client ); + + if (cd.network) { + //********************************************************** + // create socket + int flags = KExtendedSocket::inetSocket + | KExtendedSocket::streamSocket; + if (cd.server) flags |= KExtendedSocket::passiveSocket; + QString host = QFile::encodeName(eserver->text()); + KExtendedSocket *socket + = new KExtendedSocket(host, eport->value(), flags); + + // do lookup + int res = socket->lookup(); + if ( checkSocket(res, socket, i18n("Error looking up for \"%1\"") + .arg(host), this) ) { + delete socket; + return; + } + + // connect (client) or listen (server) + res = (cd.server ? socket->listen() : socket->connect()); + if ( checkSocket(res, socket, i18n("Error opening socket"), this) ) { + delete socket; + return; + } + + cd.rhd.socket = new Socket(socket, true); + + if ( !cd.server ) + cg.config()->writeEntry(MP_SERVER_ADDRESS, eserver->text()); + cg.config()->writeEntry(MP_PORT, eport->value()); + } + + BoardData bd; + for (uint i=0; i<wl->size(); i++) { + if ( wl->widget(i)->type()==PlayerComboBox::None ) continue; + bd.name = name(i); + bd.type = wl->widget(i)->type(); + cd.rhd.bds += bd; + } + + cg.config()->writeEntry(MP_GAMETYPE, (int)type); + for (uint i=0; i<wl->size(); i++) { + cg.config()->writeEntry(QString(MP_PLAYER_TYPE).arg(i), + (int)wl->widget(i)->type()); + cg.config()->writeEntry(QString(MP_PLAYER_NAME).arg(i), name(i)); + } + + KWizard::accept(); +} + +void MPWizard::showPage(QWidget *page) +{ + if ( page==localPage ) setFinishEnabled(localPage, TRUE); + KWizard::showPage(page); +} + +void MPWizard::configureKeysSlot() +{ + uint nb = 0; + for (uint i=0; i<wl->size(); i++) + if ( wl->widget(i)->type()==PlayerComboBox::Human ) nb++; + emit configureKeys(nb); +} diff --git a/libksirtet/lib/wizard.h b/libksirtet/lib/wizard.h new file mode 100644 index 00000000..29287508 --- /dev/null +++ b/libksirtet/lib/wizard.h @@ -0,0 +1,57 @@ +#ifndef WIZARD_H +#define WIZARD_H + +#include <qlabel.h> +#include <qlineedit.h> +#include <qvbox.h> +#include <qvgroupbox.h> +#include <qpushbutton.h> + +#include <knuminput.h> +#include <kconfig.h> +#include <kwizard.h> + +#include "pline.h" +#include "mp_interface.h" + +class ConnectionData; + +class MPWizard : public KWizard +{ + Q_OBJECT + + public: + MPWizard(const MPGameInfo &gi, ConnectionData &cd, + QWidget *parent = 0, const char *name = 0); + + void showPage(QWidget *page); + + signals: + void configureKeys(uint); + + protected slots: + void accept(); + + private slots: + void typeChanged(int t); + void lineTypeChanged(int); + void configureKeysSlot(); + + private: + ConnectionData &cd; + enum Type { Local, Server, Client }; + Type type; + QVBox *typePage, *localPage; + WidgetList<PlayerLine> *wl; + QLabel *lserver; + QLineEdit *eserver; + KIntNumInput *eport; + QVGroupBox *net; +// QPushButton *keys; + + void setupTypePage(); + void setupLocalPage(const MPGameInfo &gi); + QString name(uint i) const; +}; + +#endif // WIZARD_H |