diff options
Diffstat (limited to 'libksirtet')
93 files changed, 8633 insertions, 0 deletions
diff --git a/libksirtet/CHANGELOG b/libksirtet/CHANGELOG new file mode 100644 index 00000000..d7e56fd2 --- /dev/null +++ b/libksirtet/CHANGELOG @@ -0,0 +1 @@ +see "ksirtet" CHANGELOG diff --git a/libksirtet/DESIGN b/libksirtet/DESIGN new file mode 100644 index 00000000..f09b8539 --- /dev/null +++ b/libksirtet/DESIGN @@ -0,0 +1,57 @@ +'lo, time to explain how some things are designed in the generic class +of these games (ksirtet, kfouleggs and klickety) + +NB: You should read this file with a fixed font editor ... + +the following set of functions is implemented in the hierarchy of classes : +GenericTetris -> BaseBoard -> Board -> specific Board class for each game + +=============================================================================== +action 'Piece Drop Down' activated + | +pieceDropped() <-| + | | +oneLineDown() -> by timer -| + +=============================================================================== +if oneLineDown() make the piece touch the ground + | +pieceDropped() + | +_beforeGlue() <-| + | | +beforeGlue() -> by timer -| // here is implemented the bump effect + | (when done) +gluePiece() + | +_afterGlue() <-| + | | +afterGlue() -> by timer -| // here kfouleggs remove holes + | (when done) +needRemoving ? no -> _afterAfterRemove() + | +_beforeRemove() <-| + | | +beforeRemove() -> by timer -| // here blocks to be removed are highlighted + | (when done) +remove() + | +_afterRemove() <-| + | | +afterRemove() -> by timer -| // here is animated the fall of piece in + | // the holes leaved by removed blocks + | if needs removing again -> _beforeRemove() + | +_afterAfterRemove() ? no gift pending -> afterAfterRemove() + | +putGift() + | +_afterGift() <-| + | | +afterGift() -> by timer -| // here kfouleggs make the gift block to land + | +_afterAfterRemove() + | +afterAfterRemove() ? gameOver -> gameOver() + | +newPiece() diff --git a/libksirtet/LICENSE b/libksirtet/LICENSE new file mode 100644 index 00000000..d2793d2f --- /dev/null +++ b/libksirtet/LICENSE @@ -0,0 +1,22 @@ +LIBKSIRTET +---------- +Copyright (c) 1995 Eirik ENG +Copyright (c) 1996-2004 Nicolas HADACEK (hadacek@kde.org) + + +This file is part of the KDE ksirtet library +Copyright (C) 2001 Andreas Beckermann (b_mann@gmx.de) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Library General Public +License version 2 as published by the Free Software Foundation. + +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 Street, Fifth Floor, +Boston, MA 02110-1301, USA. diff --git a/libksirtet/Makefile.am b/libksirtet/Makefile.am new file mode 100644 index 00000000..14913f02 --- /dev/null +++ b/libksirtet/Makefile.am @@ -0,0 +1,4 @@ +SUBDIRS = base lib common + +messages: + $(XGETTEXT) base/*.cpp lib/*.cpp common/*.cpp -o $(podir)/libksirtet.pot diff --git a/libksirtet/README b/libksirtet/README new file mode 100644 index 00000000..f6f9b187 --- /dev/null +++ b/libksirtet/README @@ -0,0 +1,16 @@ +LIBKSIRTET: a set of libraries for use in tetris-alike games +------------------------------------------------------------ +Copyright (c) 1995 Eirik ENG +Copyright (c) 1996-2004 Nicolas HADACEK (hadacek@kde.org) +Distributed under the GNU Library General Public License + + +These libraries provides almost all functionnalities to make +a tetris-alike games. Moreover it gives multiplayers functionality +and a framework for "artificial intelligence" players. + +The basis of the core code (gtetris) has been written by Eirik Eng as +an example for the Qt distribution (see "README.gtetris"). + + +Enjoy ! diff --git a/libksirtet/README.gtetris b/libksirtet/README.gtetris new file mode 100644 index 00000000..88d6f314 --- /dev/null +++ b/libksirtet/README.gtetris @@ -0,0 +1,21 @@ + Oslo, May 14 1995 + +This code was written about 1 and a half year ago to test the very +first Qt skeleton. It was in fact the first Qt application and was +written at a time when Qt only had pushbutton widgets and the drawing +engine was limited to drawing text in a single font and drawing lines +(no kidding, those were the only two drawing operations). In fact the +tetris project doubled the number of widgets in Qt by introducing +the QLCDNumber widget. The whole application was written in 5 evenings +and 1 weekend and is not very well documented. The tetris engine is +implemented in the GenericTetris class and was first made on my +good old 10 MHZ AT where I made a DOS text-mode Tetris (using about +50 lines) to test it out, it was later moved to a SUN Sparc 10, where it +met Qt. GenericTetris is totally independent of Qt, feel free to use +it in any way you like. (see the file gtetris.h for a brief description +of how to use it) + +Enjoy! + + Eirik Eng + ----- diff --git a/libksirtet/TODO b/libksirtet/TODO new file mode 100644 index 00000000..abe0c12d --- /dev/null +++ b/libksirtet/TODO @@ -0,0 +1,15 @@ +GLOBAL: + * options : different types of tiles ? + * background image + more animations + * key profiles (?) + * autopause if focus is lost (cf kmines) + * set the keyboard repeat rate (possible ?) or better do not use the + repeat... + * use KGame + + +KNOWN BUGS: + * occasional crash in multiplayer wizard / with keybinding changes. + * infinite loop with out-of-bound call to GArray::at + sometimes when creating several boards (human vs AI/human). + * does not update immediately second board when changing background. diff --git a/libksirtet/base/Makefile.am b/libksirtet/base/Makefile.am new file mode 100644 index 00000000..bb8b2365 --- /dev/null +++ b/libksirtet/base/Makefile.am @@ -0,0 +1,16 @@ +INCLUDES = -I$(top_srcdir)/libkdegames -I$(top_srcdir)/libkdegames/highscore $(all_includes) + +# Don't compile with hidden symbols since we are a library. +if disable_VISIBILITY +KDE_CXXFLAGS = -fvisibility=default +endif + +noinst_LTLIBRARIES = libksirtetbase.la +libksirtetbase_la_LDFLAGS = $(all_libraries) + +noinst_HEADERS = kzoommainwindow.h piece.h gtetris.h factory.h highscores.h \ + board.h settings.h field.h inter.h main.h +libksirtetbase_la_SOURCES = kzoommainwindow.cpp main.cpp field.cpp piece.cpp highscores.cpp \ + factory.cpp gtetris.cpp board.cpp settings.cpp \ + inter.cpp baseprefs.kcfgc +METASOURCES = AUTO diff --git a/libksirtet/base/README b/libksirtet/base/README new file mode 100644 index 00000000..a16060b1 --- /dev/null +++ b/libksirtet/base/README @@ -0,0 +1 @@ +This directory contains code shared between ksirtet, kfouleggs and klickety. diff --git a/libksirtet/base/baseprefs.kcfgc b/libksirtet/base/baseprefs.kcfgc new file mode 100644 index 00000000..41c852af --- /dev/null +++ b/libksirtet/base/baseprefs.kcfgc @@ -0,0 +1,7 @@ +# Code generation options for kconfig_compiler +File=libksirtet1.kcfg +#IncludeFiles=defines.h +ClassName=BasePrefs +Singleton=true +#CustomAdditions=true +Mutators=MenubarVisible,BlockSize diff --git a/libksirtet/base/board.cpp b/libksirtet/base/board.cpp new file mode 100644 index 00000000..257e72c3 --- /dev/null +++ b/libksirtet/base/board.cpp @@ -0,0 +1,429 @@ +#include "board.h" +#include "board.moc" + +#include <knotifyclient.h> +#include <klocale.h> +#include <kzoommainwindow.h> + +#include "piece.h" +#include "factory.h" +#include "baseprefs.h" + +using namespace KGrid2D; + +//----------------------------------------------------------------------------- +FixedCanvasView::FixedCanvasView(QWidget *parent, const char *name) + : QCanvasView(parent, name, WNoAutoErase) +{} + +QSize FixedCanvasView::sizeHint() const +{ + if ( canvas()==0 ) return QSize(); + return canvas()->size() + 2 * QSize(frameWidth(), frameWidth()); +} + +void FixedCanvasView::adjustSize() +{ + setFixedSize(sizeHint()); +} + +//----------------------------------------------------------------------------- +const BaseBoard::DirectionData BaseBoard::DIRECTION_DATA[Nb_Direction] = { + { SquareBase::Left, Left }, + { SquareBase::Right, Right }, + { SquareBase::Down, Up }, + { SquareBase::Up, Down } +}; + +BaseBoard::BaseBoard(bool graphic, QWidget *parent) +: FixedCanvasView(parent, "board"), + GenericTetris(bfactory->bbi.width, bfactory->bbi.height, + bfactory->bbi.withPieces, graphic), + state(GameOver), timer(this), sequences(0), main(0), _next(0), + _arcade(false) +{ + if (graphic) { + setVScrollBarMode(AlwaysOff); + setHScrollBarMode(AlwaysOff); + setFrameStyle( QFrame::Panel | QFrame::Sunken ); + + sequences = new SequenceArray; + main = new BlockInfo(*sequences); + setCanvas(main); + if (bfactory->bbi.withPieces) + _next = new BlockInfo(*sequences); + setBlockInfo(main, _next); + + connect(&timer, SIGNAL(timeout()), SLOT(timeout())); + + Piece::info().loadColors(); + KZoomMainWindow::addWidget(this); + } +} + +void BaseBoard::copy(const GenericTetris &g) +{ + GenericTetris::copy(g); + state = static_cast<const BaseBoard &>(g).state; +} + +void BaseBoard::settingsChanged() +{ + Q_ASSERT( graphic() ); + Piece::info().loadColors(); +} + +void BaseBoard::adjustSize() +{ + int size = BasePrefs::blockSize(); + + sequences->setBlockSize(size); + main->resize(matrix().width() * size, matrix().height() * size); + for (uint i=0; i<matrix().width(); i++) + for (uint j=0; j<firstClearLine(); j++) { + Coord c(i, j); + if ( matrix()[c]==0 ) continue; + partialMoveBlock(c, QPoint(0, 0)); + } + + if (_next) { + Coord c = Piece::info().maxSize() + Coord(2, 2); + _next->resize(c.first * size, c.second * size); + _nextPiece->moveCenter(); + } + + FixedCanvasView::adjustSize(); +} + +BaseBoard::~BaseBoard() +{ + if ( graphic() ) { + setBlockInfo(0, 0); // destruct all sprites before deleting canvas + delete _next; + delete main; + delete sequences; + } +} + +void BaseBoard::init(bool arcade) +{ + _arcade = arcade; + _arcadeStageDone = false; +} + +void BaseBoard::start(const GTInitData &data) +{ + Q_ASSERT( graphic() ); + if ( !_arcadeStageDone || _arcadeStage==bfactory->bbi.nbArcadeStages ) + _arcadeStage = 0; + _arcadeStageDone = false; + state = Normal; + GenericTetris::start(data); // NB: the timer is started by updateLevel ! + if (_arcade) arcadePrepare(); +} + +void BaseBoard::stop() +{ + timer.stop(); + state = GameOver; +} + +void BaseBoard::pause() +{ + Q_ASSERT( graphic() ); + timer.stop(); + _oldState = state; + state = Paused; + showBoard(false); +} + +void BaseBoard::gameOver() +{ + stop(); + emit gameOverSignal(); +} + +void BaseBoard::showCanvas(QCanvas *c, bool show) +{ + QCanvasItemList l = c->allItems(); + QCanvasItemList::Iterator it; + for (it=l.begin(); it!=l.end(); ++it) { + if (show) (*it)->show(); + else (*it)->hide(); + } + c->update(); +} + +void BaseBoard::showBoard(bool show) +{ + showCanvas(main, show); +} + +void BaseBoard::unpause() +{ + Q_ASSERT( graphic() ); + showBoard(true); + state = _oldState; + startTimer(); +} + +void BaseBoard::updateRemoved(uint newRemoved) +{ + GenericTetris::updateRemoved(newRemoved); + emit removedUpdated(); +} + +void BaseBoard::updateScore(uint newScore) +{ + GenericTetris::updateScore(newScore); + emit scoreUpdated(); +} + +int BaseBoard::firstColumnBlock(uint col) const +{ + for (int j=firstClearLine()-1; j>=0; j--) { + Coord c(col, j); + if ( matrix()[c]!=0 ) return j; + } + return -1; +} + +//----------------------------------------------------------------------------- +void BaseBoard::_beforeRemove(bool first) +{ + if ( graphic() ) { + state = ( beforeRemove(first) ? BeforeRemove : Normal ); + if ( state==BeforeRemove ) { + startTimer(); + return; + } + } + remove(); + _afterRemove(true); +} + +void BaseBoard::remove() +{ + for (uint j=0; j<firstClearLine(); j++) + for (uint i=0; i<matrix().width(); i++) { + Coord c(i, j); + if ( matrix()[c]==0 || !toBeRemoved(c) ) continue; + removeBlock(c); + } + computeInfos(); + if ( graphic() ) { + main->update(); + KNotifyClient::event(winId(), "removed", i18n("Blocks removed")); + } +} + +bool BaseBoard::doFall(bool doAll, bool first, bool lineByLine) +{ + Q_ASSERT( !lineByLine || !doAll ); + + if ( !doAll ) { + if (first) loop = 0; + else loop++; + } + bool final = (doAll || lineByLine + || loop==bfactory->bbi.nbFallStages); + + for (uint i=0; i<matrix().width(); i++) { + // compute heights + // we must separate this computation since toFall() can depend + // directly on the disposition of blocks under the current one + // (for e.g. in kfouleggs) + // we do not rely on firstClearLine() here since this method is + // used in kfouleggs to make gift blocks fall down ... + uint h = 0; + QMemArray<uint> heights(matrix().height()); + for (uint j=1; j<matrix().height(); j++) { // first line cannot fall + Coord src(i, j); + if ( toFall(src) ) h++; + heights[j] = h; + } + + // do move + for (uint j=1; j<matrix().height(); j++) { + Coord src(i, j); + if( heights[j]==0 || matrix()[src]==0 ) continue; + if (lineByLine) final = false; + uint k = j - (lineByLine ? 1 : heights[j]); + Coord dest(i, k); + if ( final || lineByLine ) moveBlock(src, dest); + else partialBlockFall(src, dest); + } + } + + if (final) computeInfos(); + return final; +} + +void BaseBoard::_afterRemove(bool first) +{ + AfterRemoveResult r = afterRemove(!graphic(), first); + switch (r) { + case Done: + state = Normal; + _afterAfterRemove(); + return; + case NeedAfterRemove: + state = AfterRemove; + startTimer(); + return; + case NeedRemoving: + _beforeRemove(true); + return; + } +} + +BaseBoard::AfterRemoveResult BaseBoard::afterRemove(bool doAll, bool first) +{ + return (doFall(doAll, first, false) ? Done : NeedAfterRemove); +} + +void BaseBoard::_afterAfterRemove() +{ + if ( isArcade() && arcadeDone()>=arcadeTodo() ) { + _arcadeStage++; + _arcadeStageDone = true; + gameOver(); + return; + } + if ( !afterAfterRemove() ) gameOver(); + else if ( graphic() ) startTimer(); +} + +bool BaseBoard::timeout() +{ + Q_ASSERT( graphic() ); + if ( state==GameOver ) return true; + switch (state) { + case BeforeRemove: _beforeRemove(FALSE); break; + case AfterRemove: _afterRemove(FALSE); break; + default: return false; + } + main->update(); + return true; +} + +bool BaseBoard::startTimer() +{ + Q_ASSERT( graphic() ); + if ( state==GameOver ) return true; + switch (state) { + case BeforeRemove: + timer.start(bfactory->bbi.beforeRemoveTime, true); + break; + case AfterRemove: + timer.start(bfactory->bbi.afterRemoveTime, true); + break; + default: + return false; + } + return true; +} + +bool BaseBoard::beforeRemove(bool first) +{ + if (first) loop = 0; + else loop++; + + for (uint j=0; j<firstClearLine(); j++) + for (uint i=0; i<matrix().width(); i++) { + Coord c(i, j); + if ( toBeRemoved(c) ) matrix()[c]->toggleLight(); + } + + return ( loop!=bfactory->bbi.nbToggles ); +} + + +//----------------------------------------------------------------------------- +void BaseBoard::partialBlockFall(const Coord &src, const Coord &dest) +{ + Q_ASSERT( loop<bfactory->bbi.nbFallStages ); + + float c = float(loop+1) / bfactory->bbi.nbFallStages * BasePrefs::blockSize(); + int xdec = dest.first - src.first; + int ydec = src.second - dest.second; + QPoint p(int(xdec * c), int(ydec * c)); + partialMoveBlock(src, p); +} + +uint BaseBoard::findGroup(Square<int> &field, const Coord &c) const +{ + uint nb = 0; + _findGroup(field, c, nb, false); + return nb; +} + +void BaseBoard::setGroup(Square<int> &field, const Coord &c, uint nb) const +{ + _findGroup(field, c, nb, true); +} + +void BaseBoard::_findGroup(Square<int> &field, const Coord &c, + uint &nb, bool set) const +{ + if (!set) nb++; + field[c] = (set ? (int)nb : -1); + uint value = matrix()[c]->value(); + CoordList n = matrix().neighbours(c, true, true); + for (CoordList::const_iterator i = n.begin(); i!=n.end(); ++i) + blockInGroup(field, *i, value, nb, set); +} + +void BaseBoard::blockInGroup(Square<int> &field, const Coord &c, uint value, + uint &nb, bool set) const +{ + if ( matrix()[c]==0 ) return; + if ( matrix()[c]->value()!=value ) return; + if ( field[c]!=(set ? -1 : 0) ) return; + _findGroup(field, c, nb, set); +} + +QMemArray<uint> BaseBoard::findGroups(Square<int> &field, uint minSize, + bool exitAtFirstFound) const +{ + field.fill(0); + QMemArray<uint> groups; + for (uint j=0; j<firstClearLine(); j++) + for (uint i=0; i<matrix().width(); i++) { + Coord c(i, j); + if ( matrix()[c]==0 || matrix()[c]->isGarbage() ) continue; + if ( field[c]!=0 ) continue; + uint nb = findGroup(field, c); + setGroup(field, c, nb); + if ( nb>=minSize ) { + uint s = groups.size(); + groups.resize(s+1); + groups[s] = nb; + if (exitAtFirstFound) return groups; + } + } + return groups; +} + +uint BaseBoard::drawCode(const Coord &c) const +{ + uint v = matrix()[c]->value(); + uint code = 0; + for (uint i=0; i<Nb_Direction; i++) { + Coord nc = SquareBase::neighbour(c, DIRECTION_DATA[i].neighbour); + if ( !matrix().inside(nc) || matrix()[nc]==0 + || matrix()[nc]->value()!=v ) continue; + code |= DIRECTION_DATA[i].direction; + } + return code; +} + +void BaseBoard::computeNeighbours() +{ + for (uint j=0; j<firstClearLine(); j++) + for (uint i=0; i<matrix().width(); i++) { + Coord c(i, j); + if ( matrix()[c]==0 || matrix()[c]->isGarbage() ) continue; + matrix()[c]->sprite()->setFrame( drawCode(c) ); + } +} diff --git a/libksirtet/base/board.h b/libksirtet/base/board.h new file mode 100644 index 00000000..443c6532 --- /dev/null +++ b/libksirtet/base/board.h @@ -0,0 +1,134 @@ +#ifndef BASE_BOARD_H +#define BASE_BOARD_H + +#include <qtimer.h> +#include <qcanvas.h> + +#include "gtetris.h" + +#include <kdemacros.h> + +class SequenceArray; +class BlockInfo; + +//----------------------------------------------------------------------------- +class KDE_EXPORT FixedCanvasView : public QCanvasView +{ + Q_OBJECT +public: + FixedCanvasView(QWidget *parent = 0, const char *name = 0); + + virtual QSize sizeHint() const; + +public slots: + virtual void adjustSize(); +}; + +//----------------------------------------------------------------------------- +class KDE_EXPORT BaseBoard : public FixedCanvasView, public GenericTetris +{ + Q_OBJECT + public: + enum Direction { Left = 1, Right = 2, Up = 4, Down = 8, Nb_Direction = 4 }; + private: + struct DirectionData { + KGrid2D::SquareBase::Neighbour neighbour; + Direction direction; + }; + static const DirectionData DIRECTION_DATA[Nb_Direction]; + + public: + BaseBoard(bool graphic, QWidget *parent); + virtual ~BaseBoard(); + void copy(const GenericTetris &); + + void init(bool arcade); + virtual void start(const GTInitData &); + virtual void pause(); + virtual void unpause(); + virtual void stop(); + bool isGameOver() const { return state==GameOver; } + bool isPaused() const { return state==Paused; } + + bool isArcade() const { return _arcade; } + uint arcadeStage() const { return _arcadeStage; } + bool arcadeStageDone() const { return _arcadeStageDone; } + virtual uint arcadeTodo() const { return 0; } + virtual uint arcadeDone() const { return 0; } + + virtual void settingsChanged(); + BlockInfo *next() const { return _next; } + + int firstColumnBlock(uint column) const; + + public slots: + virtual void adjustSize(); + + protected slots: + virtual bool timeout(); // return true if treated + + signals: + void updatePieceConfigSignal(); + void removedUpdated(); + void scoreUpdated(); + void gameOverSignal(); + + protected: + virtual bool beforeRemove(bool first); + void _beforeRemove(bool first); + enum AfterRemoveResult { Done, NeedAfterRemove, NeedRemoving }; + virtual AfterRemoveResult afterRemove(bool doAll, bool first); + void _afterAfterRemove(); + virtual bool afterAfterRemove() = 0; + virtual bool startTimer(); // return true if treated + virtual bool toBeRemoved(const KGrid2D::Coord &) const = 0; + virtual void remove(); + virtual bool toFall(const KGrid2D::Coord &) const = 0;//height>0 when called + virtual bool doFall(bool doAll, bool first, bool lineByLine); + virtual void gameOver(); + virtual void arcadePrepare() {} + + uint drawCode(const KGrid2D::Coord &) const; + void computeNeighbours(); + void partialBlockFall(const KGrid2D::Coord &src, const KGrid2D::Coord &dest); + + // return the sizes of the groups (>=minSize) + QMemArray<uint> findGroups(KGrid2D::Square<int> &field, uint minSize, + bool exitAtFirstFound = false) const; + // find group size and put -1 in the corresponding blocks (these blocks + // should be 0 at start) + uint findGroup(KGrid2D::Square<int> &field, const KGrid2D::Coord &) const; + // set the size of the group in the blocks (these blocks should be -1 + // at start ie you should have called findGroup() before) + void setGroup(KGrid2D::Square<int> &field, const KGrid2D::Coord &c, + uint nb) const; + + void updateRemoved(uint newRemoved); + void updateScore(uint newScore); + + virtual void showBoard(bool show); + void showCanvas(QCanvas *c, bool show); + + enum BoardState { GameOver, Normal, Paused, + DropDown, BeforeGlue, AfterGlue, BeforeRemove, + AfterRemove, AfterGift }; + BoardState state, _oldState; + QTimer timer; + SequenceArray *sequences; + BlockInfo *main, *_next; + uint loop; + + private: + bool _arcade, _arcadeStageDone; + uint _arcadeStage; + + void _afterRemove(bool first); + void updatePieceConfig() { emit updatePieceConfigSignal(); } + + void _findGroup(KGrid2D::Square<int> &field, const KGrid2D::Coord &c, + uint &nb, bool set) const; + void blockInGroup(KGrid2D::Square<int> &field, const KGrid2D::Coord &c, + uint value, uint &nb, bool ser) const; +}; + +#endif diff --git a/libksirtet/base/factory.cpp b/libksirtet/base/factory.cpp new file mode 100644 index 00000000..55850f0b --- /dev/null +++ b/libksirtet/base/factory.cpp @@ -0,0 +1,52 @@ +#include "factory.h" + +#include <kaboutdata.h> +#include <kapplication.h> +#include <kcmdlineargs.h> +#include <kglobal.h> +#include <klocale.h> + +#include "settings.h" + + +BaseFactory *BaseFactory::_self = 0; + +BaseFactory::BaseFactory(const MainData &md, const BaseBoardInfo &bi) + : mainData(md), bbi(bi) +{ + Q_ASSERT( _self==0 ); + _self = this; + _aboutData = + new KAboutData(md.appName, md.trName, md.longVersion, md.description, + KAboutData::License_GPL, + "(c) 1995, Eirik Eng\n(c) 1996-2004, Nicolas Hadacek", + 0, md.homepage); + _aboutData->addAuthor("Nicolas Hadacek", 0, "hadacek@kde.org"); + _aboutData->addCredit("Eirik Eng", I18N_NOOP("Core engine")); +} + +void BaseFactory::init(int argc, char **argv) +{ + KCmdLineArgs::init(argc, argv, _aboutData); + (void)new KApplication; + KGlobal::locale()->insertCatalogue("libkdegames"); + KGlobal::locale()->insertCatalogue("libksirtet"); +} + +BaseFactory::~BaseFactory() +{ + delete kapp; + delete _aboutData; + Q_ASSERT(_self); + _self = 0; +} + +QWidget *BaseFactory::createAppearanceConfig() +{ + return new BaseAppearanceConfig; +} + +QWidget *BaseFactory::createColorConfig() +{ + return new ColorConfig; +} diff --git a/libksirtet/base/factory.h b/libksirtet/base/factory.h new file mode 100644 index 00000000..b542205e --- /dev/null +++ b/libksirtet/base/factory.h @@ -0,0 +1,60 @@ +#ifndef BASE_FACTORY_H +#define BASE_FACTORY_H + +#include <qglobal.h> + +#include <kdemacros.h> + +struct MainData { + const char *appName, *trName, *description, *homepage, *removedLabel, + *version, *longVersion; +}; + +struct BaseBoardInfo { + uint width, height; + bool withPieces; + + uint beforeRemoveTime, afterRemoveTime; + uint nbToggles, nbFallStages; + + uint nbArcadeStages; + + const uint *histogram; + uint histogramSize; + bool scoreBound; +}; + +class BaseBoard; +class BaseInterface; +class QWidget; +class KAboutData; + +#define bfactory BaseFactory::self() + +class KDE_EXPORT BaseFactory +{ + public: + BaseFactory(const MainData &, const BaseBoardInfo &); + virtual ~BaseFactory(); + void init(int argc, char **argv); + + static BaseFactory *self() { return _self; } + + const MainData &mainData; + const BaseBoardInfo &bbi; + + virtual BaseBoard *createBoard(bool graphic, QWidget *parent) = 0; + virtual BaseInterface *createInterface(QWidget *parent) = 0; + + virtual QWidget *createAppearanceConfig(); + virtual QWidget *createColorConfig(); + virtual QWidget *createGameConfig() { return 0; } + + protected: + KAboutData *_aboutData; + + private: + static BaseFactory *_self; +}; + +#endif diff --git a/libksirtet/base/field.cpp b/libksirtet/base/field.cpp new file mode 100644 index 00000000..53f6220a --- /dev/null +++ b/libksirtet/base/field.cpp @@ -0,0 +1,162 @@ +#include "field.h" + +#include <qwhatsthis.h> +#include <qlayout.h> +#include <qlabel.h> +#include <qpushbutton.h> + +#include <klocale.h> +#include <kcanvasrootpixmap.h> +#include <knotifyclient.h> +#include <kgamelcd.h> + +#include "factory.h" +#include "board.h" +#include "baseprefs.h" + + +const char *BaseField::BUTTON_TEXTS[NB_BUTTON_TYPE] = { + I18N_NOOP("Start"), I18N_NOOP("Resume"), I18N_NOOP("Proceed") +}; + +BaseField::BaseField(QWidget *w) + : _widget(w), _boardLayout(0), _label(0), _button(0) +{ + top = new QGridLayout(w, 3, 5, 10); + + lcds = new QGridLayout(7, 1, 5); + top->addLayout(lcds, 1, 0); + lcds->setRowStretch(1, 0); + + board = bfactory->createBoard(true, w); + _boardRootPixmap = new KCanvasRootPixmap(board); + _boardRootPixmap->start(); + top->addWidget(board, 1, 2); +} + +void BaseField::init(bool AI, bool multiplayer, bool server, bool first, + const QString &name) +{ + _flags.AI = AI; + _flags.multiplayer = multiplayer; + _flags.server = server; + _flags.first = first; + QString text = (AI ? i18n("%1\n(AI player)").arg(name) + : (multiplayer ? i18n("%1\n(Human player)").arg(name) + : QString::null)); + if ( first && !server ) text += i18n("\nWaiting for server"); + setMessage(text, (first && server ? StartButton : NoButton)); + showScore->resetColor(); + board->init(false); +} + +void BaseField::setArcade() +{ + board->init(true); + setMessage(i18n("Stage #1"), StartButton); +} + +bool BaseField::isArcade() const +{ + return board->isArcade(); +} + +void BaseField::setMessage(const QString &label, ButtonType type) +{ + delete _label; + _label = 0; + delete _button; + _button = 0; + delete _boardLayout; + _boardLayout = 0; + + if ( label.isEmpty() && type==NoButton ) { + _widget->setFocus(); + return; + } + + _boardLayout = new QVBoxLayout(board); + _boardLayout->addStretch(3); + if ( !label.isEmpty() ) { + QString str = (isArcade() ? i18n("Arcade game") + '\n' + : QString::null) + label; + _label = new QLabel(str, board); + _label->setAlignment(Qt::AlignCenter); + _label->setFrameStyle( QFrame::Panel | QFrame::Sunken ); + _boardLayout->addWidget(_label, 0, Qt::AlignCenter); + _label->show(); + } + _boardLayout->addStretch(1); + if ( type!=NoButton ) { + _button = new QPushButton(i18n(BUTTON_TEXTS[type]), board); + _button->setFocus(); + const char *slot = (type==ResumeButton ? SLOT(pause()) + : SLOT(start())); + _button->connect(_button, SIGNAL(clicked()), + _widget->parent(), slot); + _boardLayout->addWidget(_button, 0, Qt::AlignCenter); + _button->show(); + } + _boardLayout->addStretch(3); +} + +void BaseField::start(const GTInitData &data) +{ + _firstScore = KExtHighscore::firstScore(); + _lastScore = KExtHighscore::lastScore(); + hideMessage(); + board->start(data); +} + +void BaseField::pause(bool pause) +{ + if (pause) { + board->pause(); + setMessage(i18n("Game paused"), ResumeButton); + } else { + board->unpause(); + hideMessage(); + } +} + +void BaseField::stop(bool gameover) +{ + board->stop(); + ButtonType button = StartButton; + QString msg = (gameover ? i18n("Game over") : QString::null); + if ( board->isArcade() && board->arcadeStageDone() ) { + if ( board->arcadeStage()==bfactory->bbi.nbArcadeStages ) + msg = i18n("The End"); + else { + msg = i18n("Stage #%1 done").arg(board->arcadeStage()); + button = ProceedButton; + } + } + setMessage(msg, button); +} + +void BaseField::gameOver(const KExtHighscore::Score &score, QWidget *parent) +{ + KNotifyClient::event(parent->winId(), "game over", i18n("Game Over")); + KExtHighscore::submitScore(score, parent); +} + +void BaseField::scoreUpdated() +{ + showScore->display( (int)board->score() ); + if (_flags.multiplayer) return; + + QColor color; + if ( _firstScore<currentScore() ) color = Qt::red; + else if ( _lastScore<currentScore() ) color = Qt::blue; + showScore->setColor(color); +} + +void BaseField::settingsChanged() +{ + QColor color = BasePrefs::fadeColor(); + double s = BasePrefs::fadeIntensity(); + _boardRootPixmap->setFadeEffect(s, color); + board->canvas()->setBackgroundColor(color); + board->settingsChanged(); +} diff --git a/libksirtet/base/field.h b/libksirtet/base/field.h new file mode 100644 index 00000000..d006a052 --- /dev/null +++ b/libksirtet/base/field.h @@ -0,0 +1,66 @@ +#ifndef BASE_FIELD_H +#define BASE_FIELD_H + +#include <kexthighscore.h> + +#include <kdemacros.h> + +class QVBoxLayout; +class QGridLayout; +class KGameLCD; +class KGameLCDList; +class BaseBoard; +class QLabel; +class QButton; +class GTInitData; +class KCanvasRootPixmap; + +class KDE_EXPORT BaseField +{ + public: + BaseField(QWidget *widget); + virtual ~BaseField() {} + + virtual KExtHighscore::Score currentScore() const = 0; + static void gameOver(const KExtHighscore::Score &, QWidget *parent); + + virtual void setArcade(); + bool isArcade() const; + + protected: + QGridLayout *top, *lcds; + KGameLCD *showScore; + KGameLCDList *removedList, *scoreList; + BaseBoard *board; + + virtual void scoreUpdated(); + virtual void init(bool AI, bool multiplayer, bool server, bool first, + const QString &name); + virtual void start(const GTInitData &); + virtual void pause(bool pause); + virtual void stop(bool gameover); + virtual void settingsChanged(); + + private: + QWidget *_widget; + struct Flags { + bool AI, multiplayer, server, first; + }; + Flags _flags; + uint _arcadeStage; + QVBoxLayout *_boardLayout; + QLabel *_label; + QButton *_button; + KCanvasRootPixmap *_boardRootPixmap; + KExtHighscore::Score _firstScore, _lastScore; + + enum ButtonType { StartButton = 0, ResumeButton, ProceedButton, + NB_BUTTON_TYPE, NoButton = NB_BUTTON_TYPE }; + static const char *BUTTON_TEXTS[NB_BUTTON_TYPE]; + + bool hasButton() const { return _flags.server && _flags.first; } + void setMessage(const QString &label, ButtonType); + void hideMessage() { setMessage(QString::null, NB_BUTTON_TYPE); } +}; + +#endif diff --git a/libksirtet/base/gtetris.cpp b/libksirtet/base/gtetris.cpp new file mode 100644 index 00000000..2141f9ef --- /dev/null +++ b/libksirtet/base/gtetris.cpp @@ -0,0 +1,241 @@ +#include "gtetris.h" + +#include "piece.h" + + +using namespace KGrid2D; + +GenericTetris::GenericTetris(uint width, uint height, bool withPieces, + bool graphic) + : _nextPiece(0), _currentPiece(0), _score(0), _nbRemoved(0), + _nbClearLines(height), _main(0), + _graphic(graphic), _matrix(width, height) +{ + if (withPieces) { + _nextPiece = new Piece; + _currentPiece = new Piece; + } + _matrix.fill(0); +} + +void GenericTetris::copy(const GenericTetris &g) +{ + Q_ASSERT(_currentPiece); + // copy to non graphic + _score = g._score; + _level = g._level; + _nbRemoved = g._nbRemoved; + _nbClearLines = g._nbClearLines; + _currentPos = g._currentPos; + _nextPiece->copy(g._nextPiece); + _currentPiece->copy(g._currentPiece); + for (uint i=0; i<_matrix.size(); i++) { + Coord c = _matrix.coord(i); + delete _matrix[c]; + if ( g._matrix[c] ) _matrix[c] = new Block(g._matrix[c]->value()); + else _matrix[c] = 0; + } +} + +void GenericTetris::clear() +{ + _currentPos = Coord(0, -1); + for (uint i=0; i<_matrix.size(); i++) removeBlock(_matrix.coord(i)); + computeInfos(); +} + +GenericTetris::~GenericTetris() +{ + // everything should already be done by setBlockInfo(0, 0); +} + +void GenericTetris::setBlockInfo(BlockInfo *main, BlockInfo *next) +{ + Q_ASSERT( _graphic ); + if (main) { + _main = main; + if (_currentPiece) { + Q_ASSERT(next); + _nextPiece->setBlockInfo(next); + _currentPiece->setBlockInfo(main); + } + } else { // before destruction + clear(); + delete _currentPiece; + delete _nextPiece; + } +} + +void GenericTetris::start(const GTInitData &data) +{ + Q_ASSERT( _graphic ); + _random.setSeed(data.seed); + _initLevel = data.initLevel; + updateScore(0); + updateLevel(_initLevel); + updateRemoved(0); + clear(); + if (_nextPiece) { + _nextPiece->setRandomSequence(&_random); + _nextPiece->generateNext(); + newPiece(); + } +} + +void GenericTetris::dropDown() +{ + uint dropHeight = moveTo(Coord(0, -_currentPos.second)); + pieceDropped(dropHeight); +} + +void GenericTetris::oneLineDown() +{ + if ( moveTo(Coord(0, -1))==0 ) pieceDropped(0); +} + +bool GenericTetris::newPiece() +{ + Q_ASSERT(_currentPiece); + Coord min = _nextPiece->min(); + _currentPos.second = _matrix.height() - 1 + min.second; + _currentPos.first = (_matrix.width() - _nextPiece->size().first)/2 + - min.first; + if ( !canPosition(_currentPos, _nextPiece)) { + _currentPos.second = -1; + return false; + } + _currentPiece->copy(_nextPiece); + if (_graphic) { + _currentPiece->move(toPoint(_currentPos)); + _currentPiece->show(true); + updatePieceConfig(); + } + _nextPiece->generateNext(); + if (_graphic) { + _nextPiece->moveCenter(); + _nextPiece->show(true); + updateNextPiece(); + } + return true; +} + +bool GenericTetris::canPosition(const Coord &pos, const Piece *piece) const +{ + for(uint k=0; k<piece->nbBlocks(); k++) { + Coord c(piece->pos(k, pos)); + if ( !_matrix.inside(c) || _matrix[c]!=0 ) + return false; // outside or something in the way + } + return true; +} + +uint GenericTetris::moveTo(const Coord &dec) +{ + Q_ASSERT(_currentPiece); + Q_ASSERT(dec.first==0 || dec.second==0); + + Coord newPos = _currentPos; + Coord d(0, 0); + uint n, i; + + if (dec.first) { + d.first = (dec.first<0 ? -1 : 1); + n = kAbs(dec.first); + } else { + d.second = (dec.second<0 ? -1 : 1); + n = kAbs(dec.second); + } + + for (i=0; i<n; i++) { + if ( !canPosition(newPos + d, _currentPiece) ) break; + newPos = newPos + d; + } + if ( i!=0 ) { // piece can be moved + _currentPos = newPos; + if (_graphic) { + _currentPiece->move(toPoint(newPos)); + updatePieceConfig(); + } + } + return i; +} + +bool GenericTetris::rotate(bool left) +{ + Q_ASSERT(_currentPiece); + + Piece tmp; + tmp.copy(_currentPiece); + QPoint p(0, 0); + tmp.rotate(left, p); + if ( canPosition(_currentPos, &tmp) ) { + if (_graphic) p = toPoint(_currentPos); + _currentPiece->rotate(left, p); + if (_graphic) updatePieceConfig(); + return true; + } + return false; +} + +void GenericTetris::computeInfos() +{ + _nbClearLines = 0; + for (uint j=_matrix.height(); j>0; j--) { + for (uint i=0; i<_matrix.width(); i++) + if ( _matrix[Coord(i, j-1)]!=0 ) return; + _nbClearLines++; + } +} + +void GenericTetris::setBlock(const Coord &c, Block *b) +{ + Q_ASSERT( b && _matrix[c]==0 ); + _matrix[c] = b; + if (_graphic) { + QPoint p = toPoint(c); + b->sprite()->move(p.x(), p.y()); + } +} + +void GenericTetris::removeBlock(const Coord &c) +{ + delete _matrix[c]; + _matrix[c] = 0; +} + +void GenericTetris::moveBlock(const Coord &src, const Coord &dest) +{ + Q_ASSERT( _matrix[dest]==0 ); + if ( _matrix[src] ) { + setBlock(dest, _matrix[src]); + _matrix[src] = 0; + } +} + +QPoint GenericTetris::toPoint(const Coord &c) const +{ + return _main->toPoint(Coord(c.first, _matrix.height() - 1 - c.second)); +} + +void GenericTetris::gluePiece() +{ + Q_ASSERT(_currentPiece); + + for(uint k=0; k<_currentPiece->nbBlocks(); k++) + setBlock(_currentPiece->pos(k, _currentPos), + _currentPiece->takeBlock(k)); + computeInfos(); +} + +void GenericTetris::bumpCurrentPiece(int dec) +{ + Q_ASSERT( _graphic && _currentPiece ); + _currentPiece->move(toPoint(_currentPos) + QPoint(0, dec)); +} + +void GenericTetris::partialMoveBlock(const Coord &c, const QPoint &dec) +{ + Q_ASSERT( _graphic && _matrix[c]!=0 ); + QPoint p = toPoint(c) + dec; + _matrix[c]->sprite()->move(p.x(), p.y()); +} diff --git a/libksirtet/base/gtetris.h b/libksirtet/base/gtetris.h new file mode 100644 index 00000000..48aefb9d --- /dev/null +++ b/libksirtet/base/gtetris.h @@ -0,0 +1,178 @@ +/**************************************************************************** +** +** Definition of GenericTetris, a generic class for implementing Tetris. +** +** Author : Eirik Eng +** Created : 940126 +** Modified by Nicolas Hadacek +** +** Copyright (C) 1994 by Eirik Eng. All rights reserved. +** +**--------------------------------------------------------------------------- +** +** The GenericTetris class is an abstract class that can be used to implement +** the well known game of Tetris. It is totally independent of any hardware +** platform or user interface mechanism. It has no notion of time, so you +** have to supply the timer ticks (or heartbeat) of the game. Apart from +** that this is a complete Tetris implementation. +** +** In the following it is assumed that the reader is familiar with the game +** of Tetris. +** +** The class operates on a grid of squares (referred to as the "board" below), +** where each of the different types of pieces in Tetris covers 4 squares. The +** width and height of the board can be specified in the constructor (default +** is 10x22). The coordinate (0,0) is at the TOP LEFT corner. The class +** assumes that it has total control over the board and uses this to optimize +** drawing of the board. If you need to update parts of the board +** (e.g. if you are using a window system), use the updateBoard() function. +** +** An implementation of the game must subclass from TetrisBoard and must +** implement these abstract functions: +** +** virtual void drawSquare(int x,int y,int value) +** +** This function is called when a square needs to be drawn +** on the Tetris board. A value of 0 means that the square +** should be erased. Values of 1 to 7 indicate the different +** types of pieces. (Thus a minimal implementation can +** draw 0 in one way and 1-7 in a second way). The x and y +** values are coordinates on the Tetris board (see above). +** +** virtual void gameOver() +** +** This function is called when it is impossible to put a new +** piece at the top of the board. +** +** To get a working minimal implementation of Tetris the following functions +** must be called from the user interface: +** +** void startGame() +** +** Clears the board and starts a new game. +** +** void moveLeft() +** void moveRight() +** void rotateLeft() +** void rotateRight() +** +** The standard Tetris controls for moving and rotating the +** falling pieces. +** +** void dropDown(); +** +** Another Tetris control, drops the falling piece and calls the +** virtual function pieceDropped(), whose default implementation is +** to create a new block which appears at the top of the board. +** +** void oneLineDown(); +** +** This is where you supply the timer ticks, or heartbeat, of the +** game. This function moves the current falling piece one line down +** on the board, or, if that cannot be done, calls the virtual +** function pieceDropped() (see dropDown() above). The time between +** each call to this function directly affects the difficulty of the +** game. If you want to pause the game, simply block calls to the +** user control functions above and stop calling this function (you +** might want to call hideBoard() also). +** +** And that's it! There are several other public functions you can call +** and virtual functions you can overload to modify or extend the game. +** +** Do whatever you want with this code (i.e. the files gtetris.h, +** gtetris.cpp, tpiece.h and tpiece.cpp). It is basically a weekend hack +** and it would bring joy to my heart if anyone in any way would find +** it useful. +** +** Nostalgia, comments and/or praise can be sent to: Eirik.Eng@troll.no +** +****************************************************************************/ + +#ifndef GTETRIS_H +#define GTETRIS_H + +#include <qpoint.h> + +#include <krandomsequence.h> +#include <kgrid2d.h> + +#include <kdemacros.h> + +class Piece; +class BlockInfo; +class Block; + +struct GTInitData { + int seed; + uint initLevel; +}; + +class KDE_EXPORT GenericTetris +{ + public: + GenericTetris(uint width, uint height, bool withPieces, bool graphic); + virtual ~GenericTetris(); + virtual void copy(const GenericTetris &); + + void setBlockInfo(BlockInfo *main, BlockInfo *next); + virtual void start(const GTInitData &); + + uint moveLeft(int steps = 1) { return moveTo(KGrid2D::Coord(-steps, 0)); } + uint moveRight(int steps = 1) { return moveTo(KGrid2D::Coord(steps, 0)); } + bool rotateLeft() { return rotate(true); } + bool rotateRight() { return rotate(false); } + virtual void oneLineDown(); + virtual void dropDown(); + + KRandomSequence &randomSequence() { return _random; } + uint score() const { return _score; } + uint level() const { return _level; } + uint nbClearLines() const { return _nbClearLines; } + uint nbRemoved() const { return _nbRemoved; } + bool graphic() const { return _graphic; } + uint firstClearLine() const { return _matrix.height() - _nbClearLines; } + const KGrid2D::Coord ¤tPos() const { return _currentPos; } + const Piece *nextPiece() const { return _nextPiece; } + const Piece *currentPiece() const { return _currentPiece; } + const KGrid2D::Square<Block *> &matrix() const { return _matrix; } + + protected: + Piece *_nextPiece, *_currentPiece; + + virtual void pieceDropped(uint /*dropHeight*/) {} + virtual bool newPiece(); // return false if cannot place new piece + virtual void gluePiece(); + virtual void computeInfos(); + + virtual void updateRemoved(uint newNbRemoved) { _nbRemoved = newNbRemoved;} + virtual void updateScore(uint newScore) { _score = newScore; } + virtual void updateLevel(uint newLevel) { _level = newLevel; } + + void setBlock(const KGrid2D::Coord &, Block *); + virtual void removeBlock(const KGrid2D::Coord &); + void moveBlock(const KGrid2D::Coord &src, const KGrid2D::Coord &dest); + + virtual void updateNextPiece() {} + virtual void updatePieceConfig() {} + void bumpCurrentPiece(int dec); + void partialMoveBlock(const KGrid2D::Coord &, const QPoint &dec); + + private: + QPoint toPoint(const KGrid2D::Coord &) const; + uint moveTo(const KGrid2D::Coord &dec); + bool rotate(bool left); + void clear(); + bool canPosition(const KGrid2D::Coord &newPos, const Piece *newPiece) const; + + GenericTetris(const GenericTetris &); // disabled + + uint _score, _level, _nbRemoved; + uint _nbClearLines, _initLevel; + KGrid2D::Coord _currentPos; + BlockInfo *_main; + bool _graphic; + KGrid2D::Square<Block *> _matrix; + KRandomSequence _random; +}; + +#endif diff --git a/libksirtet/base/highscores.cpp b/libksirtet/base/highscores.cpp new file mode 100644 index 00000000..2b3596d7 --- /dev/null +++ b/libksirtet/base/highscores.cpp @@ -0,0 +1,19 @@ +#include "highscores.h" + +#include <kurl.h> + +#include "factory.h" + + +using namespace KExtHighscore; + +BaseHighscores::BaseHighscores() +{ + setWWHighscores(KURL( bfactory->mainData.homepage ), bfactory->mainData.version); + const BaseBoardInfo &bi = bfactory->bbi; + if ( bi.histogramSize!=0 ) { + QMemArray<uint> a; + a.duplicate(bi.histogram, bi.histogramSize); + setScoreHistogram(a, bi.scoreBound ? ScoreBound : ScoreNotBound); + } +} diff --git a/libksirtet/base/highscores.h b/libksirtet/base/highscores.h new file mode 100644 index 00000000..b3e7b99e --- /dev/null +++ b/libksirtet/base/highscores.h @@ -0,0 +1,13 @@ +#ifndef BASE_HIGHSCORES_H +#define BASE_HIGHSCORES_H + +#include <kexthighscore.h> +#include <kdemacros.h> + +class KDE_EXPORT BaseHighscores : public KExtHighscore::Manager +{ + public: + BaseHighscores(); +}; + +#endif diff --git a/libksirtet/base/inter.cpp b/libksirtet/base/inter.cpp new file mode 100644 index 00000000..4f40b63f --- /dev/null +++ b/libksirtet/base/inter.cpp @@ -0,0 +1,15 @@ +#include "inter.h" + +#include <kexthighscore.h> + + +void BaseInterface::showHighscores(QWidget *parent) +{ + if ( !_isPaused() ) _pause(); + _showHighscores(parent); +} + +void BaseInterface::_showHighscores(QWidget *parent) +{ + KExtHighscore::show(parent); +} diff --git a/libksirtet/base/inter.h b/libksirtet/base/inter.h new file mode 100644 index 00000000..446a77d3 --- /dev/null +++ b/libksirtet/base/inter.h @@ -0,0 +1,23 @@ +#ifndef BASE_INTER_H +#define BASE_INTER_H + +class QWidget; + + +class BaseInterface +{ +public: + BaseInterface() {} + virtual ~BaseInterface() {} + + virtual void _start() = 0; + virtual void _pause() = 0; + virtual bool _isPaused() const = 0; + + void showHighscores(QWidget *parent); + +protected: + virtual void _showHighscores(QWidget *parent); +}; + +#endif diff --git a/libksirtet/base/kzoommainwindow.cpp b/libksirtet/base/kzoommainwindow.cpp new file mode 100644 index 00000000..115d5175 --- /dev/null +++ b/libksirtet/base/kzoommainwindow.cpp @@ -0,0 +1,115 @@ +/* + This file is part of the KDE games library + Copyright (C) 2004 Nicolas Hadacek (hadacek@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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 Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "kzoommainwindow.h" +#include "kzoommainwindow.moc" + +#include <kaction.h> +#include <kstdaction.h> +#include <kmenubar.h> +#include <kcmenumngr.h> + +KZoomMainWindow::KZoomMainWindow(uint min, uint max, uint step, const char *name) + : KMainWindow(0, name), _zoomStep(step), _minZoom(min), _maxZoom(max) +{ + installEventFilter(this); + + _zoomInAction = KStdAction::zoomIn(this, SLOT(zoomIn()), actionCollection()); + _zoomOutAction = + KStdAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); + _menu = + KStdAction::showMenubar(this, SLOT(toggleMenubar()), actionCollection()); +} + +void KZoomMainWindow::init(const char *popupName) +{ + // zoom + setZoom(readZoomSetting()); + + // menubar + _menu->setChecked( menubarVisibleSetting() ); + toggleMenubar(); + + // context popup + if (popupName) { + QPopupMenu *popup = + static_cast<QPopupMenu *>(factory()->container(popupName, this)); + Q_ASSERT(popup); + if (popup) KContextMenuManager::insert(this, popup); + } +} + +void KZoomMainWindow::addWidget(QWidget *widget) +{ + widget->adjustSize(); + QWidget *tlw = widget->topLevelWidget(); + KZoomMainWindow *zm = + static_cast<KZoomMainWindow *>(tlw->qt_cast("KZoomMainWindow")); + Q_ASSERT(zm); + zm->_widgets.append(widget); + connect(widget, SIGNAL(destroyed()), zm, SLOT(widgetDestroyed())); +} + +void KZoomMainWindow::widgetDestroyed() +{ + _widgets.remove(static_cast<const QWidget *>(sender())); +} + +bool KZoomMainWindow::eventFilter(QObject *o, QEvent *e) +{ + if ( e->type()==QEvent::LayoutHint ) + setFixedSize(minimumSize()); // because K/QMainWindow + // does not manage fixed central widget + // with hidden menubar... + return KMainWindow::eventFilter(o, e); +} + +void KZoomMainWindow::setZoom(uint zoom) +{ + _zoom = zoom; + writeZoomSetting(_zoom); + QPtrListIterator<QWidget> it(_widgets); + for (; it.current(); ++it) + (*it)->adjustSize();; + _zoomOutAction->setEnabled( _zoom>_minZoom ); + _zoomInAction->setEnabled( _zoom<_maxZoom ); +} + +void KZoomMainWindow::zoomIn() +{ + setZoom(_zoom + _zoomStep); +} + +void KZoomMainWindow::zoomOut() +{ + Q_ASSERT( _zoom>=_zoomStep ); + setZoom(_zoom - _zoomStep); +} + +void KZoomMainWindow::toggleMenubar() +{ + if ( _menu->isChecked() ) menuBar()->show(); + else menuBar()->hide(); +} + +bool KZoomMainWindow::queryExit() +{ + writeMenubarVisibleSetting(_menu->isChecked()); + return KMainWindow::queryExit(); +} diff --git a/libksirtet/base/kzoommainwindow.h b/libksirtet/base/kzoommainwindow.h new file mode 100644 index 00000000..14f780fb --- /dev/null +++ b/libksirtet/base/kzoommainwindow.h @@ -0,0 +1,128 @@ +/* + This file is part of the KDE games library + Copyright (C) 2004 Nicolas Hadacek (hadacek@kde.org) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + 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 Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef KZOOMMAINWINDOW_H +#define KZOOMMAINWINDOW_H + +#include <kmainwindow.h> +#include <kdemacros.h> + + +class KToggleAction; + +/** + * KZoomMainWindow is a main window of fixed size. Its size can be + * modified with the "zoom in"/"zoom out" actions. + * + * It manages one or several widgets: their adjustSize() method is + * called whenever the zoom level is changed. + * The usual implementation for those widget is to redefine adjustSize() + * with code like: + * /code + * setFixedSize(newsize); + * /endcode + * + * This class also has a "show/hide menubar" action and allows the use + * of a context popup menu (useful to restore the menubar when hidden). + */ +class KDE_EXPORT KZoomMainWindow : public KMainWindow +{ + Q_OBJECT +public: + /** Constructor. */ + KZoomMainWindow(uint minZoom, uint maxZoom, uint zoomStep, + const char *name = 0); + + /** Add a widget to be managed i.e. the adjustSize() method of the + * widget is called whenever the zoom is changed. + * This function assumes that the topLevelWidget() is the KZoomMainWindow. + */ + static void addWidget(QWidget *widget); + + uint zoom() const { return _zoom; } + +public slots: + void zoomIn(); + void zoomOut(); + void toggleMenubar(); + +protected: + /** You need to call this after the createGUI or setupGUI method + * is called. + * @param popupName is the name of the context popup menu as defined in + * the ui.rc file. + */ + void init(const char *popupName = 0); + + virtual void setZoom(uint zoom); + virtual bool eventFilter(QObject *o, QEvent *e); + virtual bool queryExit(); + + /** You need to implement this method since different application + * use different setting class names and keys. + * Use something like: + * /code + * Settings::setZoom(zoom); + * Settings::writeConfig(); + * /endcode + */ + virtual void writeZoomSetting(uint zoom) = 0; + + /** Youneed to implement this method since different application + * use different setting class names and keys. + * Use something like: + * /code + * return Settings::zoom(); + * /endcode + */ + virtual uint readZoomSetting() const = 0; + + /** You need to implement this method since different application + * use different setting class names and keys. + * Use something like: + * /code + * Settings::setMenubarVisible(visible); + * Settings::writeConfig(); + * /endcode + */ + virtual void writeMenubarVisibleSetting(bool visible) = 0; + + /** You need to implement this method since different application + * use different setting class names and keys. + * Use something like: + * /code + * Settings::menubarVisible(); + * /endcode + */ + virtual bool menubarVisibleSetting() const = 0; + +private slots: + void widgetDestroyed(); + +private: + uint _zoom, _zoomStep, _minZoom, _maxZoom; + QPtrList<QWidget> _widgets; + KAction *_zoomInAction, *_zoomOutAction; + KToggleAction *_menu; + + class KZoomMainWindowPrivate; + KZoomMainWindowPrivate *d; +}; + +#endif diff --git a/libksirtet/base/libksirtet1.kcfg b/libksirtet/base/libksirtet1.kcfg new file mode 100644 index 00000000..684de57f --- /dev/null +++ b/libksirtet/base/libksirtet1.kcfg @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <include>factory.h</include> + <include>piece.h</include> + <group name="Options"> + <entry name="BlockSize" type="Int" key="block size"> + <label>Size of the blocks.</label> + <min>4</min> + <max>100</max> + <default>15</default> + </entry> + <entry name="FadeIntensity" type="Double" key="fade intensity"> + <label>Fade intensity.</label> + <default>1.0</default> + </entry> + <entry name="FadeColor" type="Color" key="fade color"> + <label>Fade color.</label> + <default>black</default> + </entry> + <entry name="AnimationsEnabled" type="Bool" key="enable animations"> + <label>Animations enabled.</label> + <default>true</default> + </entry> + <entry name="MenubarVisible" type="Bool" key="menubar visible"> + <label>Menubar visible.</label> + <default>true</default> + </entry> + <entry name="Color$(Number)" type="Color" key="color #$(Number)"> + <parameter name="Number" type="Int" max="10"/> + <label>Block colors.</label> +<!-- TODO: max, default is dynamic --> + <default code="true">Piece::info().defaultColor($(Number))</default> + </entry> + </group> +</kcfg> diff --git a/libksirtet/base/main.cpp b/libksirtet/base/main.cpp new file mode 100644 index 00000000..4b5a4160 --- /dev/null +++ b/libksirtet/base/main.cpp @@ -0,0 +1,131 @@ +#include "main.h" +#include "main.moc" + +#include <kaction.h> +#include <kstdaction.h> +#include <kmenubar.h> +#include <kstdgameaction.h> +#include <kcmenumngr.h> +#include <kkeydialog.h> +#include <klocale.h> +#include <knotifyclient.h> +#include <knotifydialog.h> +#include <kexthighscore.h> +#include <kconfigdialog.h> + +#include "inter.h" +#include "factory.h" +#include "settings.h" +#include "baseprefs.h" + +BaseMainWindow::BaseMainWindow() + : KZoomMainWindow(4, 100, 1, "main_window") +{ + KNotifyClient::startDaemon(); + + // File & Popup + KStdGameAction::gameNew(this, SLOT(start()), actionCollection()); + _pause = KStdGameAction::pause(this, SLOT(pause()), actionCollection()); + _pause->setEnabled(false); + KStdGameAction::highscores(this, SLOT(showHighscores()), + actionCollection()); + KStdGameAction::quit(qApp, SLOT(quit()), actionCollection()); + + // Settings + KStdAction::preferences(this, SLOT(configureSettings()), + actionCollection()); + KStdAction::keyBindings(this, SLOT(configureKeys()), actionCollection()); + KStdAction::configureNotifications(this, SLOT(configureNotifications()), + actionCollection()); + KStdGameAction::configureHighscores(this, SLOT(configureHighscores()), + actionCollection()); + + _inter = bfactory->createInterface(this); +} + +void BaseMainWindow::buildGUI(QWidget *widget) +{ + createGUI(); + setCentralWidget(widget); + init("popup"); +} + +BaseMainWindow::~BaseMainWindow() +{ + delete _inter; +} + +void BaseMainWindow::showHighscores() +{ + _inter->showHighscores(this); +} + +void BaseMainWindow::start() +{ + _inter->_start(); +} + +void BaseMainWindow::pause() +{ + _inter->_pause(); +} + +void BaseMainWindow::configureHighscores() +{ + KExtHighscore::configure(this); +} + +void BaseMainWindow::configureSettings() +{ + if ( !_inter->_isPaused() ) _inter->_pause(); + if ( KConfigDialog::showDialog("settings") ) return; + + KConfigDialog *dialog = new KConfigDialog(this, "settings", BasePrefs::self() ); + QWidget *w = bfactory->createGameConfig(); + if (w) dialog->addPage(w, i18n("Game"), "package_system"); + w = bfactory->createAppearanceConfig(); + if (w) dialog->addPage(w, i18n("Appearance"), "style"); + w = bfactory->createColorConfig(); + if (w) dialog->addPage(w, i18n("Colors"), "colorize"); +// dialog->addPage(new BackgroundConfigWidget, i18n("Background"), "background"); + addConfig(dialog); + connect(dialog, SIGNAL(settingsChanged()), SIGNAL(settingsChanged())); + dialog->show(); +} + +void BaseMainWindow::configureKeys() +{ + KKeyDialog d(true, this); + addKeys(d); + d.insert(actionCollection()); + d.configure(false); + actionCollection()->writeShortcutSettings(); + saveKeys(); +} + +void BaseMainWindow::configureNotifications() +{ + KNotifyDialog::configure(this); +} + +void BaseMainWindow::writeZoomSetting(uint zoom) +{ + BasePrefs::setBlockSize(zoom); + BasePrefs::writeConfig(); +} + +uint BaseMainWindow::readZoomSetting() const +{ + return BasePrefs::blockSize(); +} + +void BaseMainWindow::writeMenubarVisibleSetting(bool visible) +{ + BasePrefs::setMenubarVisible(visible); + BasePrefs::writeConfig(); +} + +bool BaseMainWindow::menubarVisibleSetting() const +{ + return BasePrefs::menubarVisible(); +} diff --git a/libksirtet/base/main.h b/libksirtet/base/main.h new file mode 100644 index 00000000..d2e108aa --- /dev/null +++ b/libksirtet/base/main.h @@ -0,0 +1,49 @@ +#ifndef BASE_MAIN_H +#define BASE_MAIN_H + +#include "kzoommainwindow.h" + +#include <kdemacros.h> + +class BaseInterface; +class KToggleAction; +class KKeyDialog; +class KConfigDialog; + +class KDE_EXPORT BaseMainWindow : public KZoomMainWindow +{ + Q_OBJECT +public: + BaseMainWindow(); + virtual ~BaseMainWindow(); + +signals: + void settingsChanged(); + +private slots: + void start(); + void pause(); + void showHighscores(); + void configureSettings(); + void configureKeys(); + void configureHighscores(); + void configureNotifications(); + +protected: + BaseInterface *_inter; + + void buildGUI(QWidget *widget); + virtual void addConfig(KConfigDialog *) {} + virtual void addKeys(KKeyDialog &) {} + virtual void saveKeys() {} + + virtual void writeZoomSetting(uint zoom); + virtual uint readZoomSetting() const; + virtual void writeMenubarVisibleSetting(bool visible); + virtual bool menubarVisibleSetting() const; + +private: + KToggleAction *_pause; +}; + +#endif diff --git a/libksirtet/base/piece.cpp b/libksirtet/base/piece.cpp new file mode 100644 index 00000000..25aed934 --- /dev/null +++ b/libksirtet/base/piece.cpp @@ -0,0 +1,274 @@ +#include "piece.h" + +#include <kglobal.h> +#include <krandomsequence.h> + +#include "baseprefs.h" + +using namespace KGrid2D; + +QPoint operator *(const Coord &c, int i) +{ + return QPoint(c.first * i, c.second * i); +} + +//----------------------------------------------------------------------------- +GPieceInfo::GPieceInfo() +{ + Piece::setPieceInfo(this); +} + +QPixmap *GPieceInfo::pixmap(uint blockSize, uint blockType, uint blockMode, + bool lighted) const +{ + QPixmap *pixmap = new QPixmap(blockSize, blockSize); + draw(pixmap, blockType, blockMode, lighted); + setMask(pixmap, blockMode); + return pixmap; +} + +Coord GPieceInfo::maxSize() const +{ + Coord min, max; + Coord size(0, 0); + for (uint n=0; n<nbForms(); n++) { + min = max = Coord(i(n, 0)[0], j(n, 0)[0]); + for (uint k=0; k<nbBlocks(); k++) { + Coord tmp = Coord(i(n, 0)[k], j(n, 0)[k]); + max = maximum(max, tmp); + min = minimum(min, tmp); + } + size = maximum(size, max - min); + } + return size; +} + +uint GPieceInfo::generateType(KRandomSequence *random) const +{ + return random->getLong( nbTypes() ); +} + +uint GPieceInfo::generateGarbageBlockType(KRandomSequence *random) const +{ + return nbNormalBlockTypes() + random->getLong( nbGarbageBlockTypes() ); +} + +void GPieceInfo::loadColors() +{ + _colors.resize(nbColors()); + for (uint i=0; i<_colors.size(); i++) + _colors[i] = BasePrefs::color(i); +} + + +//----------------------------------------------------------------------------- +SequenceArray::SequenceArray() +: _size(0) +{ + const GPieceInfo &pinfo = Piece::info(); + fill(0, pinfo.nbNormalBlockTypes() + pinfo.nbGarbageBlockTypes()); +} + +void SequenceArray::setBlockSize(uint bsize) +{ + _size = bsize; + const GPieceInfo &pinfo = Piece::info(); + QPtrList<QPixmap> pixmaps; + pixmaps.setAutoDelete(TRUE); + QPtrList<QPoint> points; + points.setAutoDelete(TRUE); + uint nm = pinfo.nbBlockModes(); + for (uint i=0; i<size(); i++) { + for (uint k=0; k<2; k++) + for (uint j=0; j<nm; j++) { + QPoint *po = new QPoint(0, 0); + QPixmap *pi = pinfo.pixmap(bsize, i, j, k==1); + if ( at(i) ) { + at(i)->setImage(k*nm + j, new QCanvasPixmap(*pi, *po)); + delete po; + delete pi; + } else { + points.append(po); + pixmaps.append(pi); + } + } + if ( at(i)==0 ) { + at(i) = new QCanvasPixmapArray(pixmaps, points); + pixmaps.clear(); + points.clear(); + } + } +} + +SequenceArray::~SequenceArray() +{ + for (uint i=0; i<size(); i++) delete at(i); +} + +//----------------------------------------------------------------------------- +BlockInfo::BlockInfo(const SequenceArray &s) +: _sequences(s) +{} + +QPoint BlockInfo::toPoint(const Coord &pos) const +{ + return pos * _sequences.blockSize(); +} + +//----------------------------------------------------------------------------- +Block::Block(uint value) +: _value(value), _sprite(0) +{} + +Block::~Block() +{ + delete _sprite; +} + +void Block::setValue(uint value, BlockInfo *binfo) +{ + _value = value; + if (binfo) { + QCanvasPixmapArray *seq = binfo->sequences()[value]; + if (_sprite) _sprite->setSequence(seq); + else { + _sprite = new QCanvasSprite(seq, binfo); + _sprite->setZ(0); + } + } +} + +void Block::toggleLight() +{ + const GPieceInfo &pinfo = Piece::info(); + uint f = _sprite->frame() + pinfo.nbBlockModes() + * (_sprite->frame()>=(int)pinfo.nbBlockModes() ? -1 : 1); + _sprite->setFrame(f); +} + +bool Block::isGarbage() const +{ + return Piece::info().isGarbage(_value); +} + + +//----------------------------------------------------------------------------- +GPieceInfo *Piece::_info = 0; + +Piece::Piece() + : _binfo(0), _i(0), _j(0) +{ + _blocks.setAutoDelete(true); +} + +void Piece::rotate(bool left, const QPoint &p) +{ + if (left) { + if ( _rotation==0 ) _rotation = 3; + else _rotation--; + } else { + if ( _rotation==3 ) _rotation = 0; + else _rotation++; + } + + uint form = _info->form(_type); + _i = _info->i(form, _rotation); + _j = _info->j(form, _rotation); + if (_binfo) move(p); +} + +Coord Piece::min() const +{ + if ( _i==0 || _j==0 ) return Coord(0, 0); + Coord min = coord(0); + for(uint k=1; k<_info->nbBlocks(); k++) + min = minimum(min, coord(k)); + return min; +} + +Coord Piece::max() const +{ + if ( _i==0 || _j==0 ) return Coord(0, 0); + Coord max = coord(0); + for(uint k=1; k<_info->nbBlocks(); k++) + max = maximum(max, coord(k)); + return max; +} + +void Piece::copy(const Piece *p) +{ + if ( p->_blocks.size()!=0 ) { + _blocks.resize(p->_blocks.size()); + for (uint k=0; k<_blocks.size(); k++) { + if ( _blocks[k]==0 ) _blocks.insert(k, new Block); + _blocks[k]->setValue(p->_blocks[k]->value(), _binfo); + } + } + _type = p->_type; + _random = p->_random; + _rotation = p->_rotation; + _i = p->_i; + _j = p->_j; +} + +void Piece::generateNext(int type) +{ + if ( _blocks.size()==0 ) { + _blocks.resize(_info->nbBlocks()); + for (uint k=0; k<_blocks.size(); k++) _blocks.insert(k, new Block); + } + _type = (type==-1 ? _info->generateType(_random) : (uint)type ); + _rotation = 0; + + uint form = _info->form(_type); + _i = _info->i(form, _rotation); + _j = _info->j(form, _rotation); + + for (uint k=0; k<_blocks.size(); k++) + _blocks[k]->setValue(_info->value(_type, k), _binfo); +} + +void Piece::moveCenter() +{ + uint s = _binfo->sequences().blockSize(); + QPoint p = QPoint(_binfo->width(), _binfo->height()) - size() * s; + move(p/2 - min() * s); +} + +Coord Piece::pos(uint k, const Coord &pos) const +{ + return Coord(pos.first + coord(k).first, pos.second - coord(k).second); +} + +void Piece::move(const QPoint &p) +{ + for (uint k=0; k<_blocks.size(); k++) moveBlock(k, p); +} + +void Piece::moveBlock(uint k, const QPoint &p) +{ + QPoint po = p + _binfo->toPoint(coord(k)); + _blocks[k]->sprite()->move(po.x(), po.y()); +} + +Block *Piece::garbageBlock() const +{ + Block *b = new Block; + b->setValue(_info->generateGarbageBlockType(_random), _binfo); + return b; +} + +Block *Piece::takeBlock(uint k) +{ + Block *b = _blocks.take(k); + _blocks.insert(k, new Block); + return b; +} + +void Piece::show(bool show) +{ + for (uint k=0; k<_blocks.size(); k++) { + if (show) _blocks[k]->sprite()->show(); + else _blocks[k]->sprite()->hide(); + } +} diff --git a/libksirtet/base/piece.h b/libksirtet/base/piece.h new file mode 100644 index 00000000..4c0486a8 --- /dev/null +++ b/libksirtet/base/piece.h @@ -0,0 +1,155 @@ +#ifndef BASE_PIECE_H +#define BASE_PIECE_H + +#include <qcanvas.h> +#include <qptrvector.h> + +#include <kgrid2d.h> + + +class KRandomSequence; + +//----------------------------------------------------------------------------- +class GPieceInfo +{ + public: + GPieceInfo(); + virtual ~GPieceInfo() {} + + virtual uint nbBlocks() const = 0; // nb of blocks in a piece + virtual uint nbTypes() const = 0; // nb of combin. of types in piece + virtual uint nbForms() const = 0; // nb of geometrical form of piece + + virtual const int *i(uint form, uint rotation) const = 0; + virtual const int *j(uint form, uint rotation) const = 0; + virtual uint value(uint type, uint n) const = 0; + virtual uint form(uint type) const = 0; + virtual uint nbConfigurations(uint type) const = 0; + uint generateType(KRandomSequence *) const; + + KGrid2D::Coord maxSize() const; + + QPixmap *pixmap(uint blockSize, uint blockType, uint blockMode, + bool lighted) const; + + virtual uint nbNormalBlockTypes() const = 0; + virtual uint nbGarbageBlockTypes() const = 0; + virtual uint nbBlockModes() const = 0; // nb of modes per block + bool isGarbage(uint type) const { return type>=nbNormalBlockTypes(); } + uint generateGarbageBlockType(KRandomSequence *) const; + + virtual uint nbColors() const = 0; + virtual QString colorLabel(uint i) const = 0; + QCString colorKey(uint i) const; + virtual QColor defaultColor(uint i) const = 0; + void loadColors(); + + protected: + QColor color(uint i) const { return _colors[i]; } + + virtual void draw(QPixmap *, uint blockType, uint blockMode, + bool lighted) const = 0; + virtual void setMask(QPixmap *, uint /*blockMode*/) const {} + + private: + QValueVector<QColor> _colors; +}; + +class SequenceArray : public QMemArray<QCanvasPixmapArray *> +{ + public: + SequenceArray(); + ~SequenceArray(); + + void setBlockSize(uint size); + uint blockSize() const { return _size; } + + private: + uint _size; +}; + +//----------------------------------------------------------------------------- +class BlockInfo : public QCanvas +{ + public: + BlockInfo(const SequenceArray &); + const SequenceArray &sequences() const { return _sequences; } + + QPoint toPoint(const KGrid2D::Coord &) const; + + private: + const SequenceArray &_sequences; +}; + +//----------------------------------------------------------------------------- +class Block +{ + public: + Block(uint value = 0); + ~Block(); + + void setValue(uint, BlockInfo *); + uint value() const { return _value; } + bool isGarbage() const; + void toggleLight(); + QCanvasSprite *sprite() const { return _sprite; } + + private: + uint _value; + QCanvasSprite *_sprite; + + Block(const Block &); // disabled + Block &operator =(const Block &); // disabled +}; + +//----------------------------------------------------------------------------- +class Piece +{ + public: + Piece(); + + void copy(const Piece *); + void setBlockInfo(BlockInfo *bi) { _binfo = bi; } + static void setPieceInfo(GPieceInfo *pi) { _info = pi; } + static GPieceInfo &info() { return *_info; } + + uint type() const { return _type; } + uint nbBlocks() const { return _blocks.size(); } + uint nbConfigurations() const { return _info->nbConfigurations(_type); } + + int value(uint k) const { return _blocks[k]->value(); } + KGrid2D::Coord pos(uint k, const KGrid2D::Coord &) const; + + KGrid2D::Coord min() const; + KGrid2D::Coord max() const; + KGrid2D::Coord size() const { return max() - min() + KGrid2D::Coord(1, 1); } + + void generateNext(int type = -1); + void rotate(bool left, const QPoint &); + void move(const QPoint &); + void moveCenter(); + void show(bool show); + + void setRandomSequence(KRandomSequence *random) { _random = random; } + + Block *garbageBlock() const; + Block *takeBlock(uint k); + + private: + QPtrVector<Block> _blocks; + uint _type; + KRandomSequence *_random; + static GPieceInfo *_info; + BlockInfo *_binfo; + uint _rotation; + int const *_i; + int const *_j; + + Piece(const Piece &); // disabled + Piece &operator =(const Piece &); // disabled + + KGrid2D::Coord coord(uint k) const { return KGrid2D::Coord(_i[k], _j[k]); } + void moveBlock(uint k, const QPoint &); +}; + +#endif diff --git a/libksirtet/base/settings.cpp b/libksirtet/base/settings.cpp new file mode 100644 index 00000000..1ea3b16a --- /dev/null +++ b/libksirtet/base/settings.cpp @@ -0,0 +1,84 @@ +#include "settings.h" +#include "settings.moc" + +#include <qlabel.h> +#include <qhbox.h> +#include <qlayout.h> +#include <qcheckbox.h> +#include <qhgroupbox.h> + +#include <klocale.h> +#include <knuminput.h> +#include <kcolorbutton.h> +#include <kapplication.h> +#include <kdialogbase.h> + +#include "piece.h" +#include "factory.h" + + +//----------------------------------------------------------------------------- +BaseAppearanceConfig::BaseAppearanceConfig() + : QWidget(0, "appearance_config") +{ + QVBoxLayout *top = new QVBoxLayout(this); + + // upper part + _main = new QWidget(this); + top->addWidget(_main); + _grid = new QGridLayout(_main, 3, 2, 0, KDialog::spacingHint()); + _grid->setColStretch(1, 1); + + QCheckBox *chb = + new QCheckBox(i18n("Enable animations"), _main, "kcfg_AnimationsEnabled"); + _grid->addMultiCellWidget(chb, 2, 2, 0, 1); + + top->addSpacing(KDialog::spacingHint()); + + // lower part + QHGroupBox *gbox = new QHGroupBox(i18n("Background"), this); + top->addWidget(gbox); + QWidget *widget = new QWidget(gbox); + QGridLayout *grid = + new QGridLayout(widget, 2, 3, 0, KDialog::spacingHint()); + grid->setColStretch(2, 1); + QLabel *label = new QLabel(i18n("Color:"), widget); + grid->addWidget(label, 0, 0); + KColorButton *cob = new KColorButton(widget, "kcfg_FadeColor"); + cob->setFixedWidth(100); + grid->addWidget(cob, 0, 1); + label = new QLabel(i18n("Opacity:"), widget); + grid->addWidget(label, 1, 0); + KDoubleNumInput *dn = new KDoubleNumInput(widget, "kcfg_FadeIntensity"); + dn->setRange(0.0, 1.0, 0.01); + grid->addMultiCellWidget(dn, 1, 1, 1, 2); + + top->addStretch(1); +} + +//----------------------------------------------------------------------------- +ColorConfig::ColorConfig() + : QWidget(0, "color_config") +{ + const GPieceInfo &info = Piece::info(); + QVBoxLayout *top = new QVBoxLayout(this); + uint nb = info.nbColors(); + QGridLayout *grid = new QGridLayout(top, nb+1, 3, KDialog::spacingHint()); + grid->setColStretch(2, 1); + for (uint i=0; i<nb; i++) { + QLabel *label = new QLabel(info.colorLabel(i), this); + grid->addWidget(label, i, 0); + KColorButton *cob = new KColorButton(this, colorKey(i)); + cob->setFixedWidth(100); + grid->addWidget(cob, i, 1); + } + grid->setRowStretch(nb, 1); +} + +QCString ColorConfig::colorKey(uint i) +{ + QCString s; + s.setNum(i); + return "kcfg_Color" + s; +} + diff --git a/libksirtet/base/settings.h b/libksirtet/base/settings.h new file mode 100644 index 00000000..c64bfd5b --- /dev/null +++ b/libksirtet/base/settings.h @@ -0,0 +1,33 @@ +#ifndef BASE_SETTINGS_H +#define BASE_SETTINGS_H + +#include <qwidget.h> +#include <kconfig.h> + +class QGridLayout; + + +//----------------------------------------------------------------------------- +class BaseAppearanceConfig : public QWidget +{ + Q_OBJECT +public: + BaseAppearanceConfig(); + +protected: + QWidget *_main; + QGridLayout *_grid; +}; + +//----------------------------------------------------------------------------- +class ColorConfig : public QWidget +{ + Q_OBJECT +public: + ColorConfig(); + +private: + static QCString colorKey(uint i); +}; + +#endif diff --git a/libksirtet/common/Makefile.am b/libksirtet/common/Makefile.am new file mode 100644 index 00000000..1c11265d --- /dev/null +++ b/libksirtet/common/Makefile.am @@ -0,0 +1,28 @@ +INCLUDES = -I$(top_builddir)/libksirtet -I$(top_srcdir)/libksirtet -I$(srcdir)/../base -I$(top_srcdir)/libkdegames/highscore -I$(top_srcdir)/libkdegames $(all_includes) + +# Don't compile with hidden symbols since we are a library. +if disable_VISIBILITY +KDE_CXXFLAGS = -fvisibility=default +endif + +noinst_LTLIBRARIES = libksirtetcommon.la +libksirtetcommon_la_LDFLAGS = $(all_libraries) -no-undefined +libksirtetcommon_la_DEPENDENCIES = $(LIB_KDEGAMES_DEP) $(top_builddir)/libksirtet/lib/libksirtetmultiplayers.la $(top_builddir)/libksirtet/base/libksirtetbase.la +libksirtetcommon_la_LIBADD = $(LIB_KDEGAMES) $(top_builddir)/libksirtet/lib/libksirtetmultiplayers.la $(top_builddir)/libksirtet/base/libksirtetbase.la + +noinst_HEADERS = types.h factory.h misc_ui.h highscores.h \ + board.h ai.h field.h settings.h inter.h main.h +libksirtetcommon_la_SOURCES = types.cpp factory.cpp misc_ui.cpp \ + highscores.cpp \ + board.cpp ai.cpp field.cpp settings.cpp \ + inter.cpp main.cpp commonprefs.kcfgc +METASOURCES = misc_ui.moc board.moc ai.moc field.moc \ + settings.moc inter.moc main.moc + +ai.lo: ../base/baseprefs.h +board.lo: ../base/baseprefs.h +commonprefs.lo: ../base/baseprefs.h +field.lo: ../base/baseprefs.h +inter.lo: ../base/baseprefs.h +misc_ui.lo: ../base/baseprefs.h + diff --git a/libksirtet/common/README b/libksirtet/common/README new file mode 100644 index 00000000..03ffa387 --- /dev/null +++ b/libksirtet/common/README @@ -0,0 +1 @@ +This directory contains code shared between ksirtet and kfouleggs. diff --git a/libksirtet/common/ai.cpp b/libksirtet/common/ai.cpp new file mode 100644 index 00000000..bc1c6722 --- /dev/null +++ b/libksirtet/common/ai.cpp @@ -0,0 +1,356 @@ +#include "ai.h" +#include "ai.moc" + +#include <assert.h> + +#include <qlabel.h> +#include <qhbox.h> +#include <qvbox.h> +#include <qlayout.h> +#include <qgrid.h> +#include <qwhatsthis.h> + +#include <klocale.h> +#include <kconfig.h> +#include <kapplication.h> + +#include "commonprefs.h" +#include "board.h" +#include "base/piece.h" +#include "base/factory.h" + + +//----------------------------------------------------------------------------- +AIPiece::AIPiece() + : _current(0) +{} + +AIPiece::~AIPiece() +{ + delete _current; +} + +void AIPiece::init(const Piece *piece, Board *b) +{ + _piece = piece; + _board = b; + if ( _current==0 ) _current = new Piece; + reset(); +} + +void AIPiece::reset() +{ + curPos = 0; + curRot = 0; + if (_piece) _current->copy(_piece); +// else _current->generateNext(0); + nbRot = _current->nbConfigurations() - 1; + nbPos = _board->matrix().width() - _current->size().first + 1; +} + +bool AIPiece::increment() +{ + curPos++; + if ( curPos==nbPos ) { + if ( curRot==nbRot ) { +// if ( _piece || _current->type()==Piece::info().nbTypes() ) { + reset(); + return false; +// } +// _current->generateNext(_current->type()+1); +// nbRot = _current->nbConfigurations() - 1; +// curRot = 0; + } + _current->rotate(true, QPoint(0, 0)); + nbPos = _board->matrix().width() - _current->size().first + 1; + curRot++; + curPos = 0; + } + return true; +} + +bool AIPiece::place() +{ + if ( curRot==3 ) { + if ( !_board->rotateRight() ) return false; + } else for (uint i=0; i<curRot; i++) + if ( !_board->rotateLeft() ) return false; + curDec = curPos - _board->currentPos().first - _current->min().first; + if ( curDec!=0 && _board->moveRight(curDec)!=(uint)kAbs(curDec) ) + return false; + _board->dropDown(); + return !_board->isGameOver(); +} + +//----------------------------------------------------------------------------- +const AI::Data AI::LastData = { 0, 0, 0, false, 0 }; + +AI::AI(uint tTime, uint oTime, const Data *DATA) + : timer(this), thinkTime(tTime), orderTime(oTime), stopped(false), + board(0) +{ + connect(&timer, SIGNAL(timeout()), SLOT(timeout())); + + for (uint i=0; DATA[i].name; i++) { + Element element; + element.coefficient = 0.; + element.trigger = 0; + element.data = &DATA[i]; + _elements.append(element); + } + settingsChanged(); +} + +void AI::resizePieces(uint size) +{ + uint oldSize = pieces.size(); + for (uint i=size; i<oldSize; i++) delete pieces[i]; + pieces.resize(size); + for (uint i=oldSize; i<size; i++) pieces[i] = new AIPiece; +} + +AI::~AI() +{ + delete board; + resizePieces(0); +} + +void AI::initThink() +{ + board->copy(*main); +} + +void AI::launch(Board *m) +{ + main = m; + if ( board==0 ) + board = static_cast<Board *>(bfactory->createBoard(false, 0)); + + pieces[0]->init(main->currentPiece(), board); // current + if ( pieces.size()>=2 ) pieces[1]->init(main->nextPiece(), board); // next + + state = Thinking; + hasBestPoints = false; + startTimer(); +} + +void AI::stop() +{ + timer.stop(); + stopped = true; +} + +void AI::start() +{ + if (stopped) { + startTimer(); + stopped = false; + } +} + +void AI::startTimer() +{ + switch (state) { + case Thinking: timer.start(thinkTime, true); break; + case GivingOrders: timer.start(orderTime, true); break; + } +} + +void AI::timeout() +{ + switch (state) { + case Thinking: + if ( think() ) state = GivingOrders; + break; + case GivingOrders: + if ( emitOrder() ) return; + break; + } + + startTimer(); +} + +bool AI::emitOrder() +{ + if ( bestRot==3 ) { + bestRot = 0; + main->pRotateRight(); + } else if (bestRot) { + bestRot--; + main->pRotateLeft(); + } else if ( bestDec>0 ) { + bestDec--; + main->pMoveRight(); + } else if ( bestDec<0 ) { + bestDec++; + main->pMoveLeft(); + } else { + main->pDropDownStart(); + return true; + } + return false; +} + +bool AI::think() +{ + initThink(); + bool moveOk = true; + for (uint i=0; i<pieces.size(); i++) + if ( !pieces[i]->place() ) { + moveOk = false; + break; + } + if (moveOk) { + double p = points(); + if ( !hasBestPoints || p>bestPoints + || (p==hasBestPoints && random.getBool()) ) { + hasBestPoints = true; + bestPoints = p; + bestDec = pieces[0]->dec(); + bestRot = pieces[0]->rot(); + } + } + + for (uint i=pieces.size(); i>0; i--) + if ( pieces[i-1]->increment() ) return false; + return true; +} + +double AI::points() const +{ + double pts = 0; + for (uint i=0; i<_elements.size(); i++) { + if ( _elements[i].coefficient==0.0 ) continue; + double v = _elements[i].data->function(*main, *board); + if ( _elements[i].data->triggered && qRound(v)<_elements[i].trigger ) + continue; + pts += _elements[i].coefficient * v; + } + return pts; +} + +void AI::settingsChanged() +{ + int d = CommonPrefs::thinkingDepth(); + resizePieces(d); + for (uint i=0; i<_elements.size(); i++) { + const Data &data = *_elements[i].data; + _elements[i].coefficient = AIConfig::coefficient(data); + if (data.triggered) _elements[i].trigger = AIConfig::trigger(data); + } + if ( timer.isActive() ) launch(main); +} + +double AI::nbOccupiedLines(const Board &, const Board ¤t) +{ + return current.matrix().height() - current.nbClearLines(); +} + +double AI::nbHoles(const Board &, const Board ¤t) +{ + uint nb = 0; + for (uint i=0; i<current.matrix().width(); i++) { + for (int j=current.firstColumnBlock(i)-1; j>=0; j--) { + KGrid2D::Coord c(i, j); + if ( current.matrix()[c]==0 ) nb++; + } + } + return nb; +} + +double AI::peakToPeak(const Board &, const Board ¤t) +{ + int min = current.matrix().height()-1; + for (uint i=0; i<current.matrix().width(); i++) + min = kMin(min, current.firstColumnBlock(i)); + return (int)current.firstClearLine()-1 - min; +} + +double AI::mean(const Board &, const Board ¤t) +{ + double mean = 0; + for (uint i=0; i<current.matrix().width(); i++) + mean += current.firstColumnBlock(i); + return mean / current.matrix().width(); +} + +double AI::nbSpaces(const Board &main, const Board ¤t) +{ + double nb = 0; + double m = mean(main, current); + for (uint i=0; i<current.matrix().width(); i++) { + int j = current.firstColumnBlock(i); + if ( j<m ) nb += m - j; + } + return nb; +} + +double AI::nbRemoved(const Board &main, const Board ¤t) +{ + return current.nbRemoved() - main.nbRemoved(); +} + + +//----------------------------------------------------------------------------- +const uint AIConfig::minThinkingDepth = 1; +const uint AIConfig::maxThinkingDepth = 2; + +AIConfig::AIConfig(const QValueVector<AI::Element> &elements) + : QWidget(0, "ai config") +{ + QGridLayout *top = new QGridLayout(this, 3, 2, KDialog::marginHint(), + KDialog::spacingHint()); + + QLabel *label = new QLabel(i18n("Thinking depth:"), this); + top->addWidget(label, 0, 0); + KIntNumInput *in = new KIntNumInput(this, "kcfg_ThinkingDepth"); + in->setRange(minThinkingDepth, maxThinkingDepth); + top->addWidget(in, 0, 1); + + top->addRowSpacing(1, KDialog::spacingHint()); + + QGrid *grid = new QGrid(2, this); + top->addMultiCellWidget(grid, 2, 2, 0, 1); + for (uint i=0; i<elements.size(); i++) { + const AI::Data &data = *elements.at(i).data; + QLabel *label = new QLabel(i18n(data.label), grid); + if (data.whatsthis) QWhatsThis::add(label, i18n(data.whatsthis)); + label->setFrameStyle(QFrame::Panel | QFrame::Plain); + + QVBox *vb = new QVBox(grid); + if (data.whatsthis) QWhatsThis::add(vb, i18n(data.whatsthis)); + vb->setMargin(KDialog::spacingHint()); + vb->setSpacing(KDialog::spacingHint()); + vb->setFrameStyle(QFrame::Panel | QFrame::Plain); + if (data.triggered) { + KIntNumInput *trig = new KIntNumInput(vb, triggerKey(data.name)); + trig->setRange(0, 10, 1, true); + } + KDoubleNumInput *coeff = new KDoubleNumInput(vb, coefficientKey(data.name)); + coeff->setRange(0.0, 1.0, 1.0, true); + } +} + +QCString AIConfig::triggerKey(const char *name) +{ + return "kcfg_Trigger_" + QCString(name); +} + +QCString AIConfig::coefficientKey(const char *name) +{ + return "kcfg_Coefficient_" + QCString(name); +} + +double AIConfig::coefficient(const AI::Data &data) +{ + KConfigSkeletonItem *item = CommonPrefs::self()->findItem( QString("Coefficient_%1").arg(data.name) ); + assert(item); + return item->property().toDouble(); +} + +int AIConfig::trigger(const AI::Data &data) +{ + KConfigSkeletonItem *item = CommonPrefs::self()->findItem( QString("Trigger_%1").arg(data.name) ); + assert(item); + return item->property().toInt(); +} diff --git a/libksirtet/common/ai.h b/libksirtet/common/ai.h new file mode 100644 index 00000000..da298abc --- /dev/null +++ b/libksirtet/common/ai.h @@ -0,0 +1,122 @@ +#ifndef COMMON_AI_H +#define COMMON_AI_H + +#include <qtimer.h> +#include <qvaluevector.h> + +#include <kdialogbase.h> +#include <knuminput.h> +#include <krandomsequence.h> +#include "lib/libksirtet_export.h" + +class Board; +class Piece; + + +//----------------------------------------------------------------------------- +class LIBKSIRTET_EXPORT AIPiece +{ + public: + AIPiece(); + ~AIPiece(); + + void init(const Piece *p, Board *b); + bool place(); + bool increment(); + + int dec() const { return curDec; } + uint rot() const { return curRot; } + + private: + uint nbPos, nbRot, curPos, curRot; + int curDec; + const Piece *_piece; + Piece *_current; + Board *_board; + + void reset(); +}; + +//----------------------------------------------------------------------------- +class LIBKSIRTET_EXPORT AI : public QObject +{ + Q_OBJECT + public: + struct Data { + const char *name, *label, *whatsthis; + bool triggered; + double (*function)(const Board &, const Board &); + }; + static const Data LastData; + + AI(uint thinkTime, uint orderTime, const Data *DATA); + virtual ~AI(); + + void launch(Board *main); + void stop(); + void start(); + + class Element { + public: + const Data *data; + double coefficient; + int trigger; + }; + const QValueVector<Element> &elements() const { return _elements; } + + void settingsChanged(); + + private slots: + void timeout(); + + protected: + virtual void initThink(); + + static double nbOccupiedLines(const Board &, const Board &); + static double nbHoles(const Board &, const Board &); + static double nbSpaces(const Board &, const Board &); + static double peakToPeak(const Board &, const Board &); + static double mean(const Board &, const Board &); + static double nbRemoved(const Board &, const Board &); + + private: + bool think(); + void startTimer(); + bool emitOrder(); + double points() const; + void resizePieces(uint size); + + QTimer timer; + enum ThinkState { Thinking, GivingOrders }; + ThinkState state; + uint thinkTime, orderTime; + bool stopped; + QMemArray<AIPiece *> pieces; + QValueVector<Element> _elements; + Board *main, *board; + KRandomSequence random; + + bool hasBestPoints; + double bestPoints; + int bestDec; + uint bestRot; +}; + +//----------------------------------------------------------------------------- +class LIBKSIRTET_EXPORT AIConfig : public QWidget +{ + Q_OBJECT + public: + AIConfig(const QValueVector<AI::Element> &elements); + + static double coefficient(const AI::Data &data); + static int trigger(const AI::Data &data); + + private: + static QCString triggerKey(const char *name); + static QCString coefficientKey(const char *name); + + static const uint minThinkingDepth, maxThinkingDepth; +}; + +#endif diff --git a/libksirtet/common/board.cpp b/libksirtet/common/board.cpp new file mode 100644 index 00000000..f5f011a6 --- /dev/null +++ b/libksirtet/common/board.cpp @@ -0,0 +1,286 @@ +#include "board.h" +#include "board.moc" + +#include <knotifyclient.h> +#include <klocale.h> + +#include "factory.h" +#include "base/piece.h" +#include "misc_ui.h" +#include "ai.h" +#include "commonprefs.h" + + +Board::Board(bool graphic, GiftPool *gp, QWidget *parent) + : BaseBoard(graphic, parent), + _giftPool(gp), aiEngine(0) +{} + +Board::~Board() +{ + delete aiEngine; +} + +void Board::setType(bool _ai) +{ + Q_ASSERT( graphic() ); + if (_ai) { + if ( aiEngine==0 ) aiEngine = cfactory->createAI(); + } else { + delete aiEngine; + aiEngine = 0; + } +} + +void Board::start(const GTInitData &data) +{ + randomGarbage.setSeed(data.seed); + _giftPool->reset(); + BaseBoard::start(data); +} + +void Board::stop() +{ + BaseBoard::stop(); + if (aiEngine) aiEngine->stop(); +} + +void Board::showBoard(bool show) +{ + BaseBoard::showBoard(show); + showCanvas(_next, show); +} + +void Board::unpause() +{ + BaseBoard::unpause(); + if (aiEngine) aiEngine->start(); // eventually restart thinking +} + +void Board::updateLevel() +{ + uint nb = cfactory->cbi.nbRemovedToLevel; + if ( nbRemoved()>=level()*nb ) updateLevel(level()+1); +} + +void Board::updateLevel(uint newLevel) +{ + BaseBoard::updateLevel(newLevel); + emit levelUpdated(); + if ( graphic() ) startTimer(); +} + +void Board::settingsChanged() +{ + BaseBoard::settingsChanged(); + if (aiEngine) aiEngine->settingsChanged(); +} + +/*****************************************************************************/ +void Board::pMoveLeft() +{ + if ( state!=Normal ) return; + moveLeft(); + main->update(); +} + +void Board::pMoveRight() +{ + if ( state!=Normal ) return; + moveRight(); + main->update(); +} + +void Board::pMoveLeftTotal() +{ + if ( state!=Normal ) return; + moveLeft(bfactory->bbi.width); + main->update(); +} + +void Board::pMoveRightTotal() +{ + if ( state!=Normal ) return; + moveRight(bfactory->bbi.width); + main->update(); +} + +void Board::pOneLineDown() +{ + if ( state!=Normal ) return; + oneLineDown(); + main->update(); +} + +void Board::pDropDownStart() +{ + if ( state!=Normal ) return; + _dropHeight = 0; + oneLineDown(); + if ( state==Normal ) { + state = DropDown; + startTimer(); + } + main->update(); +} + +void Board::pDropDownStop() +{ + if ( state!=DropDown || CommonPrefs::directDropDownEnabled() ) return; + state = Normal; + startTimer(); + main->update(); +} + +void Board::pRotateLeft() +{ + if ( state!=Normal ) return; + rotateLeft(); + main->update(); +} + +void Board::pRotateRight() +{ + if ( state!=Normal ) return; + rotateRight(); + main->update(); +} + +void Board::pieceDropped(uint dropHeight) +{ + if ( state==DropDown ) state = Normal; + else _dropHeight = dropHeight; + _beforeGlue(true); +} + +void Board::_beforeGlue(bool first) +{ + if ( graphic() ) { + state = (beforeGlue(_dropHeight>=1, first) ? BeforeGlue : Normal); + if ( state==BeforeGlue ) { + startTimer(); + return; + } + } + gluePiece(); +} + +void Board::gluePiece() +{ + BaseBoard::gluePiece(); + _afterGlue(true); + if ( graphic() ) KNotifyClient::event(winId(), "glued", i18n("Piece glued")); +} + +void Board::_afterGlue(bool first) +{ + bool b = afterGlue(!graphic(), first); + if ( graphic() ) { + state = (b ? AfterGlue : Normal); + if ( state==AfterGlue ) { + startTimer(); + return; + } + } + + updateScore(score() + _dropHeight); + if ( needRemoving() ) _beforeRemove(true); + else _afterAfterRemove(); +} + +bool Board::afterAfterRemove() +{ + // checkGift + if ( graphic() && _giftPool->pending() ) { + if ( putGift(_giftPool->take()) ) { + computeInfos(); + _afterGift(true); + return true; + } else return false; + } + return newPiece(); +} + +void Board::_afterGift(bool first) +{ + Q_ASSERT( graphic() ); + state = (afterGift(first) ? AfterGift : Normal); + if ( state==AfterGift ) startTimer(); + else afterAfterRemove(); +} + +bool Board::newPiece() +{ + Q_ASSERT( !graphic() || state==Normal ); + if ( !BaseBoard::newPiece() ) return false; + if ( graphic() ) { + main->update(); + _next->update(); + if (aiEngine) aiEngine->launch(this); + // else : a human player can think by himself ... + } + return true; +} + +bool Board::timeout() +{ + if ( BaseBoard::timeout() ) return true; + + switch (state) { + case DropDown: _dropHeight++; + case Normal: oneLineDown(); break; + case BeforeGlue: _beforeGlue(false); break; + case AfterGlue: _afterGlue(false); break; + case AfterGift: _afterGift(false); break; + default: return false; + } + main->update(); + return true; +} + +uint Board::normalTime() const +{ + return cfactory->cbi.baseTime / (1 + level()); +} + +bool Board::startTimer() +{ + if ( BaseBoard::startTimer() ) return true; + + switch (state) { + case Normal: + timer.start(normalTime()); + break; + case DropDown: + timer.start(cfactory->cbi.dropDownTime); + break; + case BeforeGlue: + timer.start(cfactory->cbi.beforeGlueTime, true); + break; + case AfterGlue: + timer.start(cfactory->cbi.afterGlueTime, true); + break; + case AfterGift: + timer.start(cfactory->cbi.afterGiftTime, true); + break; + default: + return false; + } + return true; +} + + +//----------------------------------------------------------------------------- +bool Board::beforeGlue(bool bump, bool first) +{ + if ( !bump ) return false; + if (first) { + loop = 0; + return true; + } else loop++; + + float dec = BasePrefs::blockSize() * (loop+1) * -0.1; + if ( BasePrefs::animationsEnabled() ) bumpCurrentPiece((int)dec); + + return ( loop!=cfactory->cbi.nbBumpStages ); +} diff --git a/libksirtet/common/board.h b/libksirtet/common/board.h new file mode 100644 index 00000000..97c37c17 --- /dev/null +++ b/libksirtet/common/board.h @@ -0,0 +1,75 @@ +#ifndef COMMON_BOARD_H +#define COMMON_BOARD_H + +#include "base/board.h" + +#include "lib/libksirtet_export.h" + +class GiftPool; +class AI; + +class LIBKSIRTET_EXPORT Board : public BaseBoard +{ + Q_OBJECT + public: + Board(bool graphic, GiftPool *, QWidget *parent); + virtual ~Board(); + + void setType(bool computer); + virtual void start(const GTInitData &); + void unpause(); + void stop(); + + virtual uint gift() = 0; + virtual bool needRemoving() = 0; + + GiftPool *giftPool() const { return _giftPool; } + + public slots: + void pMoveLeft(); + void pMoveRight(); + void pDropDownStart(); + void pDropDownStop(); + void pOneLineDown(); + void pRotateLeft(); + void pRotateRight(); + void pMoveLeftTotal(); + void pMoveRightTotal(); + + private slots: + bool timeout(); + + signals: + void levelUpdated(); + + protected: + KRandomSequence randomGarbage; + + virtual bool beforeGlue(bool bump, bool first); + virtual void gluePiece(); + virtual bool afterGlue(bool /*doAll*/, bool /*first*/) { return false; } + virtual bool afterGift(bool /*first*/) { return false; } + virtual bool putGift(uint) = 0; + + virtual uint normalTime() const; + void updateLevel(); + + void settingsChanged(); + + private: + uint _dropHeight; + GiftPool *_giftPool; + AI *aiEngine; + + bool newPiece(); + void pieceDropped(uint dropHeight); + void _afterGift(bool first); + void _beforeGlue(bool first); + void _afterGlue(bool first); + bool afterAfterRemove(); + bool startTimer(); + void showBoard(bool show); + void updateLevel(uint newLevel); +}; + +#endif diff --git a/libksirtet/common/commonprefs.kcfgc b/libksirtet/common/commonprefs.kcfgc new file mode 100644 index 00000000..8e41b16f --- /dev/null +++ b/libksirtet/common/commonprefs.kcfgc @@ -0,0 +1,9 @@ +# Code generation options for kconfig_compiler +File=libksirtet2.kcfg +IncludeFiles=base/baseprefs.h +ClassName=CommonPrefs +Inherits=BasePrefs +Singleton=true +#Mutators=true +#CustomAdditions=true +Mutators=false diff --git a/libksirtet/common/factory.cpp b/libksirtet/common/factory.cpp new file mode 100644 index 00000000..1b239a82 --- /dev/null +++ b/libksirtet/common/factory.cpp @@ -0,0 +1,28 @@ +#include "factory.h" + +#include "ai.h" +#include "settings.h" + + +CommonFactory::CommonFactory(const MainData &md, const BaseBoardInfo &bbi, + const CommonBoardInfo &ci) + : BaseFactory(md, bbi), cbi(ci) +{} + +QWidget *CommonFactory::createAppearanceConfig() +{ + return new AppearanceConfig; +} + +QWidget *CommonFactory::createGameConfig() +{ + return new GameConfig; +} + +QWidget *CommonFactory::createAIConfig() +{ + AI *ai = createAI(); + QWidget *cw = new AIConfig(ai->elements()); + delete ai; + return cw; +} diff --git a/libksirtet/common/factory.h b/libksirtet/common/factory.h new file mode 100644 index 00000000..c0b40d66 --- /dev/null +++ b/libksirtet/common/factory.h @@ -0,0 +1,36 @@ +#ifndef COMMON_FACTORY_H +#define COMMON_FACTORY_H + +#include "base/factory.h" + +#include "lib/libksirtet_export.h" + +struct CommonBoardInfo { + uint baseTime, dropDownTime, beforeGlueTime, afterGlueTime; + uint afterGiftTime, nbBumpStages; + uint nbRemovedToLevel; + uint nbGiftLeds, maxGiftsToSend, giftShowerTimeout, giftPoolTimeout; +}; + +class BaseField; +class AI; + +#define cfactory static_cast<CommonFactory *>(BaseFactory::self()) + +class LIBKSIRTET_EXPORT CommonFactory : public BaseFactory +{ + public: + CommonFactory(const MainData &, const BaseBoardInfo &, + const CommonBoardInfo &); + + const CommonBoardInfo &cbi; + + virtual BaseField *createField(QWidget *parent) = 0; + virtual AI *createAI() = 0; + + QWidget *createAIConfig(); + virtual QWidget *createAppearanceConfig(); + virtual QWidget *createGameConfig(); +}; + +#endif diff --git a/libksirtet/common/field.cpp b/libksirtet/common/field.cpp new file mode 100644 index 00000000..2d67062e --- /dev/null +++ b/libksirtet/common/field.cpp @@ -0,0 +1,243 @@ +#include "field.h" +#include "field.moc" + +#include <qwhatsthis.h> +#include <qlabel.h> +#include <qlayout.h> + +#include <klocale.h> +#include <kprogress.h> +#include <kgameprogress.h> +#include <kcanvasrootpixmap.h> +#include <kgamelcd.h> + +#include "base/baseprefs.h" +#include "factory.h" +#include "highscores.h" +#include "misc_ui.h" +#include "board.h" +#include "commonprefs.h" + + +Field::Field(QWidget *parent) +: MPSimpleBoard(parent), BaseField(this) +{ +// column 1 + // score LCD + scoreList = new KGameLCDList(this); + showScore = new KGameLCD(6, scoreList); + scoreList->append(showScore); + showTime = new KGameLCDClock(scoreList); + scoreList->append(showTime); + lcds->addWidget(scoreList, 1, 0); + lcds->setRowStretch(2, 1); + + // removed LCD + removedList = new KGameLCDList(i18n(bfactory->mainData.removedLabel), this); + lcds->addWidget(removedList, 3, 0); + lcds->setRowStretch(4, 1); + + // level progress + levelLabel = new QLabel(this); + levelLabel->setAlignment(AlignCenter); + lcds->addWidget(levelLabel, 5, 0); + toLevel = new KProgress(this); + toLevel->setTextEnabled(true); + toLevel->setFormat("1"); + QWhatsThis::add(toLevel, i18n("Display the progress to complete the current level or stage.")); + lcds->addWidget(toLevel, 6, 0); + lcds->setRowStretch(7, 1); + +// column 2 + // previous player height + prevHeight = new PlayerProgress(board, this, "prev_progress"); + QWhatsThis::add(prevHeight, i18n("Previous player's height")); + top->addWidget(prevHeight, 1, 1, AlignHCenter); + +// column 3 + // pending gift shower + Board *b = static_cast<Board *>(board); + top->addWidget(b->giftPool(), 0, 2, AlignCenter); + + // shadow piece + shadow = new Shadow(board, this); + QWhatsThis::add(shadow, i18n("Shadow of the current piece")); + top->addWidget(shadow, 2, 2); + +// column 4 + // next player height + nextHeight = new PlayerProgress(board, this, "next_progress"); + QWhatsThis::add(nextHeight, i18n("Next player's height")); + top->addWidget(nextHeight, 1, 3, AlignHCenter); + +// column 5 + // next piece shower + QVBoxLayout *vbl = new QVBoxLayout(10); + top->addLayout(vbl, 1, 4); + vbl->addStretch(1); + + labShowNext = new QLabel(i18n("Next Tile"), this); + labShowNext->setAlignment(AlignCenter); + vbl->addWidget(labShowNext, 0); + showNext = new ShowNextPiece(board, this); + _snRootPixmap = new KCanvasRootPixmap(showNext); + _snRootPixmap->start(); + vbl->addWidget(showNext, 0); + vbl->addStretch(4); + + connect(board, SIGNAL(scoreUpdated()), SLOT(scoreUpdatedSlot())); + connect(board, SIGNAL(levelUpdated()), SLOT(levelUpdated())); + connect(board, SIGNAL(removedUpdated()), SLOT(removedUpdated())); + + initVariableGUI(); +} + +void Field::levelUpdated() +{ + toLevel->setFormat(QString::number(board->level())); + // necessary to update string ... + int p = toLevel->progress(); + toLevel->setProgress(p+1); + toLevel->setProgress(p); +} + +void Field::removedUpdated() +{ + uint nb = cfactory->cbi.nbRemovedToLevel; + toLevel->setProgress(isArcade() ? board->arcadeDone() + : board->nbRemoved() % nb); +} + +void Field::showOpponents(bool show) +{ + Board *b = static_cast<Board *>(board); + if (show) { + prevHeight->show(); + nextHeight->show(); + b->giftPool()->show(); + } else { + prevHeight->hide(); + nextHeight->hide(); + b->giftPool()->hide(); + } +} + +void Field::settingsChanged() +{ + BaseField::settingsChanged(); + QColor color = BasePrefs::fadeColor(); + double s = BasePrefs::fadeIntensity(); + _snRootPixmap->setFadeEffect(s, color); + showNext->canvas()->setBackgroundColor(color); + bool b = CommonPrefs::showNextPiece(); + if (b) { + showNext->show(); + labShowNext->show(); + } else { + showNext->hide(); + labShowNext->hide(); + } + b = CommonPrefs::showPieceShadow(); + if (b) shadow->show(); + else shadow->hide(); +} + +void Field::_init(bool AI, bool multiplayer, bool server, bool first, + const QString &name) +{ + BaseField::init(AI, multiplayer, server, first, name); + showOpponents(multiplayer); + static_cast<Board *>(board)->setType(AI); +} + +void Field::_initFlag(QDataStream &s) +{ + ServerInitData sid; + s >> sid; + GTInitData data; + data.seed = sid.seed; + data.initLevel = sid.initLevel; + + shadow->setDisplay(true); + toLevel->setValue(0); + showTime->reset(); + showTime->start(); + + BaseField::start(data); + initVariableGUI(); +} + +void Field::initVariableGUI() +{ + if ( board->isArcade() ) { + scoreList->title()->setText(i18n("Elapsed time")); + showScore->hide(); + showTime->show(); + QWhatsThis::add(scoreList, i18n("Display the elapsed time.")); + levelLabel->setText(i18n("Stage")); + toLevel->setTotalSteps( board->arcadeTodo() ); + } else { + scoreList->title()->setText(i18n("Score")); + showScore->show(); + showTime->hide(); + QWhatsThis::add(scoreList, i18n("<qt>Display the current score.<br/>It turns <font color=\"blue\">blue</font> if it is a highscore and <font color=\"red\">red</font> if it is the best local score.</qt>")); + levelLabel->setText(i18n("Level")); + toLevel->setTotalSteps(cfactory->cbi.nbRemovedToLevel); + } +} + +void Field::_playFlag(QDataStream &s) +{ + ServerPlayData spd; + s >> spd; + prevHeight->setValue(spd.prevHeight); + nextHeight->setValue(spd.nextHeight); + if (spd.gift) + static_cast<Board *>(board)->giftPool()->put(spd.gift); +} + +void Field::_pauseFlag(bool p) +{ + pause(p); + shadow->setDisplay(!p); + if (p) showTime->stop(); + else showTime->start(); +} + +void Field::_stopFlag(bool gameover) +{ + BaseField::stop(gameover); + showTime->stop(); +} + +void Field::_dataOut(QDataStream &s) +{ + _cpd.height = board->firstClearLine(); + _cpd.end = static_cast<Board *>(board)->isGameOver(); + _cpd.gift = static_cast<Board *>(board)->gift(); + s << _cpd; +} + +KExtHighscore::Score Field::currentScore() const +{ + KExtHighscore::Score score(_cpd.end ? KExtHighscore::Lost : KExtHighscore::Won); + score.setScore(board->score()); + score.setData("level", board->level()); + score.setData("removed", board->nbRemoved()); + return score; +} + +void Field::_gameOverDataOut(QDataStream &s) +{ + s << currentScore(); +} + +void Field::moveLeft() { static_cast<Board *>(board)->pMoveLeft(); } +void Field::moveRight() { static_cast<Board *>(board)->pMoveRight(); } +void Field::dropDownStart() { static_cast<Board *>(board)->pDropDownStart(); } +void Field::dropDownStop() { static_cast<Board *>(board)->pDropDownStop(); } +void Field::oneLineDown() { static_cast<Board *>(board)->pOneLineDown(); } +void Field::rotateLeft() { static_cast<Board *>(board)->pRotateLeft(); } +void Field::rotateRight() { static_cast<Board *>(board)->pRotateRight(); } +void Field::moveLeftTotal() { static_cast<Board *>(board)->pMoveLeftTotal(); } +void Field::moveRightTotal() { static_cast<Board *>(board)->pMoveRightTotal();} diff --git a/libksirtet/common/field.h b/libksirtet/common/field.h new file mode 100644 index 00000000..8179539b --- /dev/null +++ b/libksirtet/common/field.h @@ -0,0 +1,68 @@ +#ifndef COMMON_FIELD_H +#define COMMON_FIELD_H + +#include "lib/mp_simple_board.h" +#include "base/field.h" +#include "types.h" + +#include "lib/libksirtet_export.h" + + +class ShowNextPiece; +class GiftShower; +class Shadow; +class KProgress; +class KGameProgress; +class KGameLCDClock; + +class LIBKSIRTET_EXPORT Field : public MPSimpleBoard, public BaseField +{ + Q_OBJECT + public: + Field(QWidget *parent); + + public slots: + void moveLeft(); + void moveRight(); + void dropDownStart(); + void dropDownStop(); + void oneLineDown(); + void rotateLeft(); + void rotateRight(); + void moveLeftTotal(); + void moveRightTotal(); + + virtual void settingsChanged(); + + protected slots: + void scoreUpdatedSlot() { scoreUpdated(); } + virtual void levelUpdated(); + virtual void removedUpdated(); + + private: + KGameLCDClock *showTime; + ShowNextPiece *showNext; + KProgress *toLevel; + QLabel *labShowNext, *levelLabel; + KGameProgress *prevHeight, *nextHeight; + Shadow *shadow; + KCanvasRootPixmap *_snRootPixmap; + ClientPlayData _cpd; + + void _init(bool AI, bool multiplayer, bool server, bool first, + const QString &name); + void showOpponents(bool show); + void initVariableGUI(); + + void _initFlag(QDataStream &); + void _playFlag(QDataStream &); + void _pauseFlag(bool pause); + void _stopFlag(bool gameover); + void _dataOut(QDataStream &); + void _gameOverDataOut(QDataStream &); + void _initDataOut(QDataStream &) {} + + KExtHighscore::Score currentScore() const; +}; + +#endif diff --git a/libksirtet/common/highscores.cpp b/libksirtet/common/highscores.cpp new file mode 100644 index 00000000..799a31b9 --- /dev/null +++ b/libksirtet/common/highscores.cpp @@ -0,0 +1,60 @@ +#include "highscores.h" + +#include <klocale.h> +#include <kapplication.h> +#include <kconfig.h> + +#include "base/factory.h" + + +using namespace KExtHighscore; + +CommonHighscores::CommonHighscores() +{ + Item *item = new Item((uint)1, i18n("Level"), Qt::AlignRight); + addScoreItem("level", item); + item = new Item((uint)0, i18n(bfactory->mainData.removedLabel), + Qt::AlignRight); + addScoreItem("removed", item); +} + +void CommonHighscores::convertLegacy(uint) +{ + KConfigGroupSaver cg(kapp->config(), "High Scores"); + for (uint i=0; i<10; i++) { + QString name + = cg.config()->readEntry(QString("name%1").arg(i), QString::null); + if ( name.isNull() ) break; + if ( name.isEmpty() ) name = i18n("anonymous"); + uint score + = cg.config()->readUnsignedNumEntry(QString("score%1").arg(i), 0); + uint level + = cg.config()->readUnsignedNumEntry(QString("level%1").arg(i), 1); + Score s(Won); + s.setScore(score); + s.setData("name", name); + s.setData("level", level); + submitLegacyScore(s); + } +} + +bool CommonHighscores::isStrictlyLess(const Score &s1, const Score &s2) const +{ + uint l1 = s1.data("level").toUInt(); + uint r1 = s1.data("removed").toUInt(); + uint l2 = s2.data("level").toUInt(); + uint r2 = s2.data("removed").toUInt(); + + if ( s1.score()==s2.score() ) { + if ( l1==l2 ) return r1<r2; + else return l1<l2; + } else return BaseHighscores::isStrictlyLess(s1, s2); +} + +void CommonHighscores::additionalQueryItems(KURL &url, const Score &s) const +{ + uint l = s.data("level").toUInt(); + addToQueryURL(url, "scoreLevel", QString::number(l)); + uint r = s.data("removed").toUInt(); + addToQueryURL(url, "scoreRemoved", QString::number(r)); +} diff --git a/libksirtet/common/highscores.h b/libksirtet/common/highscores.h new file mode 100644 index 00000000..f04c0d2e --- /dev/null +++ b/libksirtet/common/highscores.h @@ -0,0 +1,20 @@ +#ifndef COMMON_HIGHSCORES_H +#define COMMON_HIGHSCORES_H + +#include "base/highscores.h" + +#include "lib/libksirtet_export.h" + +class LIBKSIRTET_EXPORT CommonHighscores : public BaseHighscores +{ + public: + CommonHighscores(); + + private: + void convertLegacy(uint level); + bool isStrictlyLess(const KExtHighscore::Score &, + const KExtHighscore::Score &) const; + void additionalQueryItems(KURL &, const KExtHighscore::Score &) const; +}; + +#endif diff --git a/libksirtet/common/inter.cpp b/libksirtet/common/inter.cpp new file mode 100644 index 00000000..e9f1688b --- /dev/null +++ b/libksirtet/common/inter.cpp @@ -0,0 +1,150 @@ +#include "inter.h" +#include "inter.moc" + +#include <klocale.h> +#include <kaction.h> +#include <kapplication.h> + +#include "factory.h" +#include "field.h" +#include "commonprefs.h" +#include "main.h" + + +const ActionData Interface::ACTION_DATA[Nb_Actions] = { + { I18N_NOOP("Move Left"), "move left", SLOT(moveLeft()), 0 }, + { I18N_NOOP("Move Right"), "move right", SLOT(moveRight()), 0 }, + { I18N_NOOP("Drop Down"), "drop down", SLOT(dropDownStart()), + SLOT(dropDownStop()) }, + { I18N_NOOP("One Line Down"), "one line down", SLOT(oneLineDown()), 0 }, + { I18N_NOOP("Rotate Left"), "rotate left", SLOT(rotateLeft()), 0 }, + { I18N_NOOP("Rotate Right"), "rotate right", SLOT(rotateRight()), 0 }, + { I18N_NOOP("Move to Left Column"), "move left total", + SLOT(moveLeftTotal()), 0 }, + { I18N_NOOP("Move to Right Column"), "move right total", + SLOT(moveRightTotal()), 0 } +}; + +const int Interface::KEYCODE_ONE[Nb_Actions] = { + Key_Left, Key_Right, Key_Down, Key_Shift, Key_Up, Key_Return, + CTRL+Key_Left, CTRL+Key_Right +}; +const int Interface::KEYCODE_TWO[Nb_Actions] = { + Key_F, Key_G, Key_D, Key_Space, Key_E, Key_C, SHIFT+Key_F, SHIFT+Key_G +}; + +Interface::Interface(const MPGameInfo &gi, QWidget *parent) + : MPSimpleInterface(gi, Nb_Actions, ACTION_DATA, parent) +{ + setDefaultKeycodes(1, 0, KEYCODE_ONE); + setDefaultKeycodes(2, 0, KEYCODE_TWO); + setDefaultKeycodes(2, 1, KEYCODE_ONE); +} + +MPBoard *Interface::newBoard(uint i) +{ + Field *f = static_cast<Field *>(cfactory->createField(this)); + f->settingsChanged(); + connect(this, SIGNAL(settingsChanged()), f, SLOT(settingsChanged())); + if ( i==0 ) _firstField = f; + return f; +} + +void Interface::normalGame() +{ + singleHuman(); +} + +void Interface::arcadeGame() +{ + singleHuman(); + _firstField->setArcade(); +} + +void Interface::_init() +{ + if ( !server() ) return; + _data.resize(nbPlayers()); + _scores.setPlayerCount( nbPlayers() ); + for (uint i=0; i<nbPlayers(); i++) + _scores.setName(i, playerName(i)); +} + +bool Interface::_readPlayData() +{ + bool end = false; + for (uint i=0; i<nbPlayers(); i++) { + readingStream(i) >> _data[i]; + if (_data[i].end) end = true; + } + return end; +} + +void Interface::_sendPlayData() +{ + ServerPlayData sd; + for(uint i=0; i<nbPlayers(); i++) { + sd.prevHeight = _data[prev(i)].height; + sd.nextHeight = _data[next(i)].height; + sd.gift = _data[prev(i)].gift; + writingStream(i) << sd; + } +} + +void Interface::_showHighscores(QWidget *parent) +{ + if ( !server() || nbPlayers()!=1 ) _scores.show(parent); + else BaseInterface::_showHighscores(parent); +} + +void Interface::_showGameOverData() +{ + if ( !server() || nbPlayers()!=1 ) _scores.show(this); + else if ( !_firstField->isArcade() ) { + _score.setType(KExtHighscore::Won); + BaseField::gameOver(_score, this); + } +} + +uint Interface::prev(uint i) const +{ + if ( i==0 ) return nbPlayers()-1; + else return i-1; +} + +uint Interface::next(uint i) const +{ + if ( i==(nbPlayers()-1) ) return 0; + else return i+1; +} + +// server only +void Interface::_treatInit() +{ + ServerInitData sid; + sid.seed = kapp->random(); + sid.initLevel = CommonPrefs::initialGameLevel(); + for (uint i=0; i<nbPlayers(); i++) { + sid.prevName = playerName(prev(i)); + sid.nextName = playerName(next(i)); + sid.name = playerName(i); + writingStream(i) << sid; + } +} + +void Interface::_sendGameOverData(QDataStream &s) +{ + bool multiplayers = ( nbPlayers()>1 ); + for (uint i=0; i<nbPlayers(); i++) { + readingStream(i) >> _score; + if (multiplayers) _scores.addScore(i, _score); + } + if (multiplayers) s << _scores; + // else no need to send anything +} + +// client only +void Interface::_readGameOverData(QDataStream &s) +{ + s >> _scores; +} diff --git a/libksirtet/common/inter.h b/libksirtet/common/inter.h new file mode 100644 index 00000000..e38c2ed5 --- /dev/null +++ b/libksirtet/common/inter.h @@ -0,0 +1,62 @@ +#ifndef COMMON_INTER_H +#define COMMON_INTER_H + +#include <kexthighscore.h> +#include "lib/libksirtet_export.h" + +#include "lib/mp_simple_interface.h" +#include "base/inter.h" +#include "types.h" +#include "board.h" + + +class Field; + +class LIBKSIRTET_EXPORT Interface : public MPSimpleInterface, public BaseInterface +{ + Q_OBJECT +public: + Interface(const MPGameInfo &, QWidget *parent); + +signals: + void settingsChanged(); + +public slots: + void normalGame(); + void arcadeGame(); + void settingsChangedSlot() { emit settingsChanged(); } + +protected: + void _showHighscores(QWidget *parent); + +private: + QMemArray<ClientPlayData> _data; + KExtHighscore::Score _score; + KExtHighscore::MultiplayerScores _scores; + Field *_firstField; + + enum Action { Nb_Actions = 8 }; + static const ActionData ACTION_DATA[Nb_Actions]; + static const int KEYCODE_ONE[Nb_Actions]; + static const int KEYCODE_TWO[Nb_Actions]; + + MPBoard *newBoard(uint); + void setInitData(uint player, ServerInitData &); + uint prev(uint i) const; + uint next(uint i) const; + + void _readGameOverData(QDataStream &s); + void _sendGameOverData(QDataStream &s); + void _firstInit() {} + void _treatInit(); + void _init(); + void _showGameOverData(); + bool _readPlayData(); + void _sendPlayData(); + + void _start() { MPSimpleInterface::start(); } + void _pause() { MPSimpleInterface::pause(); } + bool _isPaused() const { return MPSimpleInterface::isPaused(); } +}; + +#endif diff --git a/libksirtet/common/libksirtet2.kcfg b/libksirtet/common/libksirtet2.kcfg new file mode 100644 index 00000000..27ee72e3 --- /dev/null +++ b/libksirtet/common/libksirtet2.kcfg @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="UTF-8"?> +<kcfg xmlns="http://www.kde.org/standards/kcfg/1.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.kde.org/standards/kcfg/1.0 + http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" > + <group name="Options"> + <entry name="ShowNextPiece" type="Bool" key="show next piece"> + <label>Show next piece.</label> + <default>true</default> + </entry> + <entry name="ShowPieceShadow" type="Bool" key="show piece shadow"> + <label>Show the shadow of a piece.</label> + <default>true</default> + </entry> + <entry name="ShowDetailedRemoved" type="Bool" key="show detailed removed"> + <label>Show detailed 'removed lines'.</label> + <default>false</default> + </entry> + <entry name="InitialGameLevel" type="Int" key="init level" min="1" max="20"> + <label>The inital level of new games.</label> + <default>1</default> + <min>1</min> + <max>20</max> + </entry> + <entry name="DirectDropDownEnabled" type="Bool" key="direct drop down"> + <label>Enable direct dropping of pieces.</label> + <default>false</default> + </entry> + </group> + <group name="AI"> + <entry name="ThinkingDepth" type="Int" key="thinking depth" min="1" max="2"> + <label>The thinking depth</label> + <default>2</default> + </entry> + </group> +</kcfg> diff --git a/libksirtet/common/main.cpp b/libksirtet/common/main.cpp new file mode 100644 index 00000000..004b86ca --- /dev/null +++ b/libksirtet/common/main.cpp @@ -0,0 +1,60 @@ +#include "main.h" +#include "main.moc" + +#include <klocale.h> +#include <kaction.h> +#include <kstdaction.h> +#include <kkeydialog.h> +#include <kconfigdialog.h> + +#include "inter.h" +#include "factory.h" + +void MainWindow::addConfig(KConfigDialog *dialog) +{ + QWidget *w = cfactory->createAIConfig(); + if (w) dialog->addPage(w, i18n("A.I."), "personal"); +} + +void MainWindow::init() +{ + Interface *inter = static_cast<Interface *>(_inter); + inter->normalGame(); + setFocusPolicy(StrongFocus); + + // Modes + bool ama = ( bfactory->bbi.nbArcadeStages!=0 ); + QString s = (ama ? i18n("&Single Human (Normal)") : i18n("&Single Human")); + (void)new KAction(s, 0, inter, SLOT(normalGame()), + actionCollection(), "mp_single_human"); + if (ama) (void)new KAction(i18n("&Single Human (Arcade)"), 0, + inter, SLOT(arcadeGame()), + actionCollection(), "mp_arcade"); + (void)new KAction(i18n("Human vs &Human"), 0, inter, SLOT(humanVsHuman()), + actionCollection(), "mp_human_vs_human"); + (void)new KAction(i18n("Human vs &Computer"), 0, + inter, SLOT(humanVsComputer()), + actionCollection(), "mp_human_vs_computer"); + (void)new KAction(i18n("&More..."), 0, inter, SLOT(dialog()), + actionCollection(), "mp_more"); + + buildGUI(inter); + connect(this, SIGNAL(settingsChanged()), + inter, SLOT(settingsChangedSlot())); +} + +void MainWindow::addKeys(KKeyDialog &d) +{ + static_cast<Interface *>(_inter)->addKeys(d); +} + +void MainWindow::saveKeys() +{ + static_cast<Interface *>(_inter)->saveKeys(); +} + +void MainWindow::focusInEvent(QFocusEvent *e) +{ + static_cast<Interface *>(_inter)->setFocus(); + BaseMainWindow::focusInEvent(e); +} diff --git a/libksirtet/common/main.h b/libksirtet/common/main.h new file mode 100644 index 00000000..d22b7273 --- /dev/null +++ b/libksirtet/common/main.h @@ -0,0 +1,22 @@ +#ifndef COMMON_MAIN_H +#define COMMON_MAIN_H + +#include "base/main.h" + +#include "lib/libksirtet_export.h" + +class LIBKSIRTET_EXPORT MainWindow : public BaseMainWindow +{ + Q_OBJECT +public: + MainWindow() {} + +protected: + void init(); + void addConfig(KConfigDialog *); + void addKeys(KKeyDialog &); + void saveKeys(); + virtual void focusInEvent(QFocusEvent *e); +}; + +#endif diff --git a/libksirtet/common/misc_ui.cpp b/libksirtet/common/misc_ui.cpp new file mode 100644 index 00000000..45cfb440 --- /dev/null +++ b/libksirtet/common/misc_ui.cpp @@ -0,0 +1,194 @@ +#include "misc_ui.h" +#include "misc_ui.moc" + +#include <qpainter.h> + +#include "base/piece.h" +#include "base/board.h" +#include "base/baseprefs.h" +#include "base/kzoommainwindow.h" +#include "factory.h" + +const uint GIFT_SHOWER_TIMEOUT = 800; +const uint GIFT_POOL_TIMEOUT = 2000; + +const uint SHADOW_HEIGHT = 10; + +const uint GI_WIDTH = 15; +const uint GI_HEIGHT = 11; +const uint ARROW_HEIGHT = 3; +const uint ARROW_WIDTH = 7; + +const uint LED_WIDTH = 15; +const uint LED_HEIGHT = 15; +const uint LED_SPACING = 5; + +/*****************************************************************************/ +ShowNextPiece::ShowNextPiece(BaseBoard *board, QWidget *parent) + : FixedCanvasView(parent, "show_next_piece") +{ + setCanvas(board->next()); + setFrameStyle(QFrame::Panel | QFrame::Sunken); + KZoomMainWindow::addWidget(this); +} + +/*****************************************************************************/ +Shadow::Shadow(BaseBoard *board, QWidget *parent) + : QWidget(parent, "shadow"), _xOffset(board->frameWidth()), + _board(board), _show(false) +{ + KZoomMainWindow::addWidget(this); + connect(board, SIGNAL(updatePieceConfigSignal()), SLOT(update())); +} + +QSize Shadow::sizeHint() const +{ + return QSize(_xOffset + _board->matrix().width() * BasePrefs::blockSize(), + SHADOW_HEIGHT); +} + +QSizePolicy Shadow::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +void Shadow::setDisplay(bool show) +{ + _show = show; + update(); +} + +void Shadow::paintEvent(QPaintEvent *) +{ + if ( !_show ) return; + + const Piece *piece = _board->currentPiece(); + uint pf = piece->min().first + _board->currentPos().first; + uint pl = pf + piece->size().first - 1; + + QPainter p(this); + p.setBrush(black); + p.setPen(black); + for (uint i=pf; i<=pl; i++) { + QRect r(_xOffset + i * BasePrefs::blockSize() + 1 , 0, + BasePrefs::blockSize() - 2, SHADOW_HEIGHT); + p.drawRect(r); + } +} + + +/*****************************************************************************/ +class Led : public QWidget +{ + public: + Led(const QColor &c, QWidget *parent) + : QWidget(parent), col(c), _on(FALSE) {} + + QSizePolicy sizePolicy() const + { return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); } + QSize sizeHint() const { return QSize(LED_WIDTH, LED_HEIGHT); } + + void on() { if (!_on) { _on = TRUE; repaint(); } } + void off() { if (_on) {_on = FALSE; repaint(); } } + void setColor(const QColor &c) { if (c!=col) { col = c; repaint(); } } + + protected: + void paintEvent(QPaintEvent *) { + QPainter p(this); + p.setBrush((_on ? col.light() : col.dark())); + p.setPen(black); + p.drawEllipse(0, 0, width(), height()); + } + + private: + QColor col; + bool _on; +}; + +GiftPool::GiftPool(QWidget *parent) + : QHBox(parent, "gift_pool"), nb(0), ready(false) +{ + setSpacing(LED_SPACING); + leds.resize(cfactory->cbi.nbGiftLeds); + for (uint i=0; i<leds.size(); i++) + leds.insert(i, new Led(yellow, this)); +} + +QSize GiftPool::sizeHint() const +{ + QSize s = (leds.size() ? leds[0]->sizeHint() : QSize()); + return QSize((s.width()+LED_SPACING)*leds.size()-LED_SPACING, s.height()); +} + +QSizePolicy GiftPool::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} + +void GiftPool::put(uint n) +{ + if ( n==0 ) return; + if ( nb==0 && !ready ) + QTimer::singleShot(cfactory->cbi.giftPoolTimeout, + this, SLOT(timeout())); + uint e = QMIN(nb+n, leds.size()); + for (uint i=nb; i<e; i++) leds[i]->on(); + uint f = QMIN(nb+n-e, leds.size()); + for (uint i=0; i<f; i++) leds[i]->setColor(red); + nb += n; +} + +uint GiftPool::take() +{ + Q_ASSERT(ready); + for (uint i=0; i<leds.size(); i++) { + leds[i]->setColor(yellow); + leds[i]->off(); + } + uint max = cfactory->cbi.maxGiftsToSend; + if ( nb>max ) { + uint p = nb - max; + nb = 0; + put(p); + return max; + } else { + ready = false; + uint t = nb; + nb = 0; + return t; + } +} + +void GiftPool::reset() +{ + killTimers(); + ready = false; + nb = 0; + for (uint i=0; i<leds.size(); i++) { + leds[i]->setColor(yellow); + leds[i]->off(); + } +} + +//----------------------------------------------------------------------------- +PlayerProgress::PlayerProgress(BaseBoard *board, QWidget *parent, + const char *name) + : KGameProgress(0, board->matrix().height(), 0, KGameProgress::Vertical, + parent, name), _board(board) +{ + setBackgroundColor(lightGray); + setTextEnabled(false); + setBarColor(blue); + KZoomMainWindow::addWidget(this); +} + +QSize PlayerProgress::sizeHint() const +{ + return QSize(10, _board->matrix().height() * BasePrefs::blockSize()) + + 2 * QSize(frameWidth(), frameWidth()); +} + +QSizePolicy PlayerProgress::sizePolicy() const +{ + return QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); +} diff --git a/libksirtet/common/misc_ui.h b/libksirtet/common/misc_ui.h new file mode 100644 index 00000000..3a89acaa --- /dev/null +++ b/libksirtet/common/misc_ui.h @@ -0,0 +1,81 @@ +#ifndef COMMON_MISC_UI_H +#define COMMON_MISC_UI_H + +#include <qcanvas.h> +#include <qptrvector.h> +#include <qhbox.h> + +#include <kgameprogress.h> +#include "lib/libksirtet_export.h" + +#include "base/board.h" + +/*****************************************************************************/ +class LIBKSIRTET_EXPORT ShowNextPiece : public FixedCanvasView +{ + Q_OBJECT + public: + ShowNextPiece(BaseBoard *, QWidget *parent); +}; + +/*****************************************************************************/ +class LIBKSIRTET_EXPORT Shadow : public QWidget +{ + Q_OBJECT + public: + Shadow(BaseBoard *, QWidget *parent); + + virtual QSize sizeHint() const; + virtual QSizePolicy sizePolicy() const; + void setDisplay(bool show); + + private: + int _xOffset; + const BaseBoard *_board; + bool _show; + + void paintEvent(QPaintEvent *); +}; + +/*****************************************************************************/ +class Led; + +class LIBKSIRTET_EXPORT GiftPool : public QHBox +{ + Q_OBJECT + public: + GiftPool(QWidget *parent); + + virtual QSize sizeHint() const; + virtual QSizePolicy sizePolicy() const; + + void reset(); + void put(uint); + uint take(); + bool pending() const { return ready; } + + private slots: + void timeout() { ready = true; } + + private: + QPtrVector<Led> leds; + uint _timeout, nb; + bool ready; +}; + + +/*****************************************************************************/ +class LIBKSIRTET_EXPORT PlayerProgress : public KGameProgress +{ + Q_OBJECT +public: + PlayerProgress(BaseBoard *board, QWidget *parent = 0, const char *name = 0); + + virtual QSize sizeHint() const; + virtual QSizePolicy sizePolicy() const; + +private: + BaseBoard *_board; +}; + +#endif diff --git a/libksirtet/common/settings.cpp b/libksirtet/common/settings.cpp new file mode 100644 index 00000000..902d1e56 --- /dev/null +++ b/libksirtet/common/settings.cpp @@ -0,0 +1,54 @@ +#include "settings.h" +#include "settings.moc" + +#include <qlabel.h> +#include <qlayout.h> +#include <qcheckbox.h> +#include <qwhatsthis.h> + +#include <klocale.h> +#include <knuminput.h> +#include <kdialog.h> +#include <kglobal.h> + + +//----------------------------------------------------------------------------- +AppearanceConfig::AppearanceConfig() +{ + int row = _grid->numRows(); + int col = _grid->numCols(); + + QCheckBox *cb = new QCheckBox(i18n("Show piece's shadow"), _main, "kcfg_ShowPieceShadow"); + _grid->addMultiCellWidget(cb, row, row, 0, col-1); + + cb = new QCheckBox(i18n("Show next piece"), _main, "kcfg_ShowNextPiece"); + _grid->addMultiCellWidget(cb, row+1, row+1, 0, col-1); + + cb = new QCheckBox(i18n("Show detailed \"removed lines\" field"), _main, "kcfg_ShowDetailedRemoved"); + _grid->addMultiCellWidget(cb, row+2, row+2, 0, col-1); +} + +//----------------------------------------------------------------------------- +GameConfig::GameConfig() + : QWidget(0, "game config") +{ + QVBoxLayout *top = new QVBoxLayout(this, KDialog::marginHint(), KDialog::spacingHint()); + + _grid = new QGridLayout(top, 3, 2); + _grid->setColStretch(1, 1); + + QLabel *label = new QLabel(i18n("Initial level:"), this); + _grid->addWidget(label, 0, 0); + KIntNumInput *in = new KIntNumInput(this, "kcfg_InitialGameLevel"); + in->setRange(1, 20, 1, true); + _grid->addWidget(in, 0, 1); + + _grid->addRowSpacing(1, KDialog::spacingHint()); + + QCheckBox *cb = new QCheckBox(i18n("Direct drop down"), this, "kcfg_DirectDropDownEnabled"); + QWhatsThis::add(cb, i18n("Drop down is not stopped when drop down key is released.")); + _grid->addMultiCellWidget(cb, 2, 2, 0, 1); + + top->addStretch(1); +} + diff --git a/libksirtet/common/settings.h b/libksirtet/common/settings.h new file mode 100644 index 00000000..ceda13a1 --- /dev/null +++ b/libksirtet/common/settings.h @@ -0,0 +1,28 @@ +#ifndef COMMON_SETTINGS_H +#define COMMON_SETTINGS_H + +#include "base/settings.h" + +#include "lib/libksirtet_export.h" + + +//----------------------------------------------------------------------------- +class LIBKSIRTET_EXPORT AppearanceConfig : public BaseAppearanceConfig +{ + Q_OBJECT +public: + AppearanceConfig(); +}; + +//----------------------------------------------------------------------------- +class LIBKSIRTET_EXPORT GameConfig : public QWidget +{ + Q_OBJECT +public: + GameConfig(); + +protected: + QGridLayout *_grid; +}; + +#endif diff --git a/libksirtet/common/types.cpp b/libksirtet/common/types.cpp new file mode 100644 index 00000000..05d91db3 --- /dev/null +++ b/libksirtet/common/types.cpp @@ -0,0 +1,16 @@ +#include "types.h" + +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ClientPlayData &d) + { s << d.height << d.gift << d.end; return s; } +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ClientPlayData &d) + { s >> d.height >> d.gift >> d.end; return s; } + +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ServerPlayData &d) + { s << d.prevHeight << d.nextHeight << d.gift; return s; } +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ServerPlayData &d) + { s >> d.prevHeight >> d.nextHeight >> d.gift; return s; } + +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ServerInitData &d) +{ s << d.initLevel << d.seed << d.nextName << d.prevName << d.name; return s; } +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ServerInitData &d) +{ s >> d.initLevel >> d.seed >> d.nextName >> d.prevName >> d.name; return s; } diff --git a/libksirtet/common/types.h b/libksirtet/common/types.h new file mode 100644 index 00000000..8a30d276 --- /dev/null +++ b/libksirtet/common/types.h @@ -0,0 +1,26 @@ +#ifndef COMMON_TYPES_H +#define COMMON_TYPES_H + +#include <qdatastream.h> + +#include "lib/libksirtet_export.h" + + +struct ClientPlayData { Q_UINT8 height, gift, end; }; +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ClientPlayData &d); +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ClientPlayData &d); + +struct ServerPlayData { Q_UINT8 prevHeight, nextHeight, gift; }; +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ServerPlayData &d); +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ServerPlayData &d); + +class ServerInitData +{ + public: + QString prevName, nextName, name; + Q_UINT32 initLevel, seed; +}; +LIBKSIRTET_EXPORT QDataStream &operator <<(QDataStream &s, const ServerInitData &d); +LIBKSIRTET_EXPORT QDataStream &operator >>(QDataStream &s, ServerInitData &d); + +#endif diff --git a/libksirtet/configure.in.in b/libksirtet/configure.in.in new file mode 100644 index 00000000..fef48401 --- /dev/null +++ b/libksirtet/configure.in.in @@ -0,0 +1,5 @@ +AC_CHECK_HEADERS(fcntl.h sys/time.h unistd.h sys/select.h sys/filio.h) + +dnl libksirtet is completely not prepared for visibility support, check for it being enabled +dnl so we can disable it in the Makefile.am. +AM_CONDITIONAL(disable_VISIBILITY, test "$HAVE_GCC_VISIBILITY" = "1") 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 (hadacek@kde.org) + + +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 (hadacek@kde.org) +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 <montel@kde.org> + + 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 |