|
|
|
This document tries to describe the design of KGame - the KDE multiplayer
|
|
|
|
library.
|
|
|
|
This document has been written by:
|
|
|
|
Andreas Beckermann <b_mann@gmx.de>
|
|
|
|
M. Heni <martin@heni-online.de>
|
|
|
|
Burkhard Lehner <Burkhard.Lehner@gmx.de>
|
|
|
|
|
|
|
|
This document is published under the terms of the GNU FDL
|
|
|
|
|
|
|
|
!!!
|
|
|
|
Note that this is the initial version of this document and has not yet been
|
|
|
|
aproved by all core developers (and is far from being complete)
|
|
|
|
AB: please remove this comments as soon as all KGame hackers have read the
|
|
|
|
document
|
|
|
|
!!!
|
|
|
|
|
|
|
|
Please refer the API documentation of every KGame class if you want up tp date
|
|
|
|
information.
|
|
|
|
|
|
|
|
|
|
|
|
0. Contents
|
|
|
|
-----------
|
|
|
|
|
|
|
|
1. DEFINITIONS
|
|
|
|
1.1 Message Server
|
|
|
|
1.2 Client or Message Client
|
|
|
|
1.3 Master
|
|
|
|
1.4 Admin
|
|
|
|
1.5 Server
|
|
|
|
1.6 Player
|
|
|
|
|
|
|
|
2. Game Negotiation (M.Heni 20.05.2001)
|
|
|
|
|
|
|
|
AB: 3.x is obsolete!
|
|
|
|
3. Game Properties (Andreas Beckermann 28.07.2001) ( not yet completed )
|
|
|
|
3.1 Using KGameProperty
|
|
|
|
3.2 Custom Classes
|
|
|
|
3.3 Concepts
|
|
|
|
|
|
|
|
4. KGameIO (Andreas Beckermann 10.08.2001)
|
|
|
|
|
|
|
|
5. Debugging (Andreas Beckermann 06.10.2001) TODO!
|
|
|
|
5.1 KGameDebugDialog
|
|
|
|
5.1.1 Debug KGame
|
|
|
|
5.1.3 Debug Messages
|
|
|
|
|
|
|
|
---------------------------------------------------------------------
|
|
|
|
1. DEFINITIONS
|
|
|
|
--------------
|
|
|
|
|
|
|
|
First we have to clear some words. The main expressions used in KGame which
|
|
|
|
need a definition are
|
|
|
|
|
|
|
|
1.1 Message Server
|
|
|
|
1.2 Client or Message Client
|
|
|
|
1.3 Master
|
|
|
|
1.4 Admin
|
|
|
|
1.5 Server
|
|
|
|
1.6 Player
|
|
|
|
|
|
|
|
The most important and confusing ones are Master, Admin and Server. We make
|
|
|
|
quite big differerences between those inside KGame.
|
|
|
|
|
|
|
|
1.1 Message Server:
|
|
|
|
-------------------
|
|
|
|
A game has always exactly one object of this class, for local games as well as
|
|
|
|
for network games. For network games, this object can be on one of the users
|
|
|
|
processes (usually inside KGame), or it can also be on an independant computer,
|
|
|
|
that has no idea about what game is played on it.
|
|
|
|
|
|
|
|
A KMessageClient object can connect to it. It's main purpose is transmitting
|
|
|
|
messages between KMessageClient objects.
|
|
|
|
|
|
|
|
The Message Server is the main communication object. It is represented by the
|
|
|
|
class KMessageServer. Note that there is also a "Master" and a "Server" which
|
|
|
|
both differ heavily from the Message Server!
|
|
|
|
|
|
|
|
1.2 Client, Message Client:
|
|
|
|
---------------------------
|
|
|
|
Each process that wants to take part in the game must have a
|
|
|
|
KMessageClient object, that is connected to the Message Server. KGame creates
|
|
|
|
this object and connects it to the Messager Server, so that you usually don't
|
|
|
|
need to create these of your own. Even in a local game (no network) there
|
|
|
|
must be a message server and one message client connected to it. This is usually
|
|
|
|
done by the KGame object itself.
|
|
|
|
|
|
|
|
Each message client has a unique ID number (a positive integer value, not zero).
|
|
|
|
The KMessageClient object, which does the communication with the Message Server
|
|
|
|
is called "Message Client" and to simplify the use we call any KGame object (or
|
|
|
|
even the game process) that is connected to a game (i.e. even the Master) just
|
|
|
|
"Client".
|
|
|
|
|
|
|
|
The main purpose of a Client is to connect to a Master (i.e. to a game) and to
|
|
|
|
communicate with it. A client has always a KGame object.
|
|
|
|
|
|
|
|
1.3 Master:
|
|
|
|
-----------
|
|
|
|
The process that contains the Message Server is called "Master". In any local
|
|
|
|
game this is the game process. The Message Server is started by KGame using
|
|
|
|
KGame::setMaster(true) which is automatically done on startup. The Message
|
|
|
|
Server is deleted automatically as soon as you connect to another Master.
|
|
|
|
So in most cases there is exactly one KGame object / Client which is Master. But
|
|
|
|
in one case there can be no KGame object / Client that is Master - if the
|
|
|
|
Message Server is started as an own process. This "Message-Server-only" process
|
|
|
|
is called "Master" then, although there is no KGame object which is Master. See
|
|
|
|
also the definition of Admin!
|
|
|
|
|
|
|
|
1.4 Admin:
|
|
|
|
----------
|
|
|
|
One (and only one) of the Clients is the Admin. He can configure the Message
|
|
|
|
Server and the game in general in several ways. He can limit the maximum number
|
|
|
|
of connected message clients and can drop the connection to some other clients,
|
|
|
|
as well as he can configure game specific ssettings (like max/min players, start
|
|
|
|
money, ...). The Admin also initializes newly connected Clients. If the Admin
|
|
|
|
himself disconnects, another Client becomes Admin (The Admin can himself elect
|
|
|
|
some other Client to become Admin. He himself loses that Admin status then).
|
|
|
|
An Admin is *alway* a KGame object. The Admin is usually the same as the Master,
|
|
|
|
but if the Master is an own process (i.e. the Message Server has been started
|
|
|
|
outside KGame) then Master and Admin differ. An Admin *must* be a KGame object
|
|
|
|
while the Master doesn't have to be.
|
|
|
|
|
|
|
|
1.5 Server:
|
|
|
|
-----------
|
|
|
|
The definition of Server differs quite much from the definition of Master.
|
|
|
|
A Master just accepts connections and forwards messages. The Server on the other
|
|
|
|
side checks these messages, calculates results and sends the results to the
|
|
|
|
Clients. That means the Server does all game calculations and doesn't directly
|
|
|
|
forward the messages from one Clients to all other Clients.
|
|
|
|
KGamer makes it possible to write multiplayer games even without a Server. All
|
|
|
|
Clients just send their moves to the Master which forwards them to all Clients.
|
|
|
|
Now all Clients calculate the result.
|
|
|
|
E.g. in a poker game a player selects two of five cards to be exchanges and
|
|
|
|
clicks on "draw" then the client sends the message "Exchange Card-1 and Card-2"
|
|
|
|
to the Master. A no-Server solution forwards this to all Clients, and these
|
|
|
|
Clients exchange the cards of the player. Note that in a no-Server solution
|
|
|
|
(you can also see it as a "every-Client-is-a-Server solution") all Clients must
|
|
|
|
have the same random seed and must be of the same version, i.e. the result must
|
|
|
|
be the same on all Clients.
|
|
|
|
In a Server-Solution on the other hand the Master forwards the Message
|
|
|
|
("Exchange Card-1 and Card-2") to the Server only. This Server now calculates
|
|
|
|
the result, and sends the new cards back to the Client.
|
|
|
|
Both concepts have advantages and disadvantages. It is on you - the game
|
|
|
|
developer - to decide which way is better for you.
|
|
|
|
E.g. the Server-Solution makes it easier for you to write games. The version
|
|
|
|
must not necessarily be the same, you have one central computer which does the
|
|
|
|
calcultations. The No-Server-Solution on the other hand decreases network
|
|
|
|
traffik as the Clients just send their moves and all Clients can calculate the
|
|
|
|
reactions. I'm sure there are a lot of advantages/disadvantages more for both
|
|
|
|
concepts.
|
|
|
|
|
|
|
|
1.6 Player:
|
|
|
|
-----------
|
|
|
|
A KPlayer object is always connected to a KGame object and represents a
|
|
|
|
player that participates the game. In a network game, every KPlayer object is
|
|
|
|
duplicated on every other KGame object connected to the message server with
|
|
|
|
virtual KPlayer objects. So at every time in the game, every KGame object has
|
|
|
|
the same number of KPlayer objects.
|
|
|
|
|
|
|
|
|
|
|
|
2. Game negotiation
|
|
|
|
-------------------
|
|
|
|
Upon connection of a client the admin and the client try to negotiate
|
|
|
|
the game setup. Basically this means the game of the admin is transferred
|
|
|
|
(saved) on the client. However, the client's players are added to the game
|
|
|
|
as far as possible. If the addition of the client's players would add more
|
|
|
|
players than allowed some players are inactivated. Which players are
|
|
|
|
inactivated depends on their networkPriority(). This procedure allows
|
|
|
|
easy replacement of players in a constant number game (e.g. chess). If
|
|
|
|
this feature is of no interest simply keep the priorities equal (all 0)
|
|
|
|
and the client will only add only players if the number of players is
|
|
|
|
less or equal the maximum player number.
|
|
|
|
|
|
|
|
The following is the negotiation procedure as started by the connection
|
|
|
|
of a client. It is initiated in the negotiateNetworkGame() virtual function
|
|
|
|
of KGame:
|
|
|
|
|
|
|
|
admin: client:
|
|
|
|
------------ ------------
|
|
|
|
IdSetupGame
|
|
|
|
QINT16 Library
|
|
|
|
Version
|
|
|
|
QINT32 Application
|
|
|
|
cookie
|
|
|
|
IdSetupGameContinue;
|
|
|
|
QValueList<int> player id's
|
|
|
|
QValueList<int> network priority's
|
|
|
|
|
|
|
|
IdGameLoad
|
|
|
|
all game data
|
|
|
|
|
|
|
|
IdGameReactivate
|
|
|
|
QValueList<int> id's
|
|
|
|
|
|
|
|
IdSyncRandom
|
|
|
|
int randomseed
|
|
|
|
|
|
|
|
|
|
|
|
3. Game Properties
|
|
|
|
------------------
|
|
|
|
A very hard task in a network game is consistency. You have to achieve that all
|
|
|
|
properties of the game and of all players have the same value on all clients
|
|
|
|
every time. This is because
|
|
|
|
a) the user might be confused if he sees "Player has $0" on client A but
|
|
|
|
"Player has $10" on client B and
|
|
|
|
b) Often game handling depends on those values, e.g. if the example above
|
|
|
|
happens the computer might quit the game for the Player on client A because
|
|
|
|
he/she doesn't have enough money. But the game continues on client B.
|
|
|
|
Another not that easy task is the network protocol itself. You have to write
|
|
|
|
several send() and receive() functions which apply changed values of properties
|
|
|
|
to the local property.
|
|
|
|
|
|
|
|
KGameProperty is designed to do all of this for you. KGameProperty is
|
|
|
|
implemented as a template so you can use it theoretically for every type of data
|
|
|
|
- even for your self defined classes.
|
|
|
|
|
|
|
|
|
|
|
|
3.1 Using KGameProperty
|
|
|
|
-----------------------
|
|
|
|
It is basically very easy to use a KGameProperty. You first create your own
|
|
|
|
class containing the property, e.g:
|
|
|
|
class MyGame : public KGame
|
|
|
|
{
|
|
|
|
[...]
|
|
|
|
protected:
|
|
|
|
KGamePropertyInt money;
|
|
|
|
KGamePropertyQString name;
|
|
|
|
KGameProperty<AntotherClass> myProperty;
|
|
|
|
};
|
|
|
|
KGamePropertyInt is just a typedef for KGameProperty<int> - just like
|
|
|
|
KGamePropertyQString. Now you need to register the properties in the constructor
|
|
|
|
of the class to the KGamePropertyHandler:
|
|
|
|
MyGame::MyGame() : KGame(myCookie)
|
|
|
|
{
|
|
|
|
money.registerData(KGamePropertyBase::IdUser+1, dataHandler(), "Money");
|
|
|
|
name.registerData(KGamePropertyBase::IdUser+2, this, "Name");
|
|
|
|
myProperty.registerData(KGamePropertyBase::IdUser+3, dataHandler(), "MyProperty");
|
|
|
|
}
|
|
|
|
-> You need to specify a *unique* ID. This ID must be greater than
|
|
|
|
KGamePropertyBase::IdUser. IDs below this are reserved for KGame. Probably this
|
|
|
|
will be changed so that you cannot use IDs below IdUser in the future. Then you
|
|
|
|
have to specify the dataHandler(). You can also use a KGame or KPlayer pointer.
|
|
|
|
This will automatically use KGame::dataHandler() or KPlayer::dataHandler().
|
|
|
|
Finally you *can* provide a name for the property. This will be used for
|
|
|
|
debugging in KGameDebugDialog. If you want to save some memory you can leave
|
|
|
|
this out.
|
|
|
|
Note that if you use pointers to create the properties dynamically they are
|
|
|
|
*not* deleted automatically! You MUST delete them yourself!
|
|
|
|
Now you can use the KGameProperty like every variable else. See also Section
|
|
|
|
"3.3 Concepts" for restrictions in use.
|
|
|
|
|
|
|
|
3.2 Custom Classes
|
|
|
|
------------------
|
|
|
|
To make custom classes possible you have to implement several operators for your
|
|
|
|
them: you need at least << and >> for QDataStream as well as "==" for your own
|
|
|
|
class. To overload the "<<" you would e.g. do something like this:
|
|
|
|
QDataStream& operator<<(QDataStream& stream, MyData& data)
|
|
|
|
{
|
|
|
|
int type = data.type;
|
|
|
|
QString name = data.name;
|
|
|
|
stream << type << name;
|
|
|
|
return stream;
|
|
|
|
}
|
|
|
|
So you basically just have to split your class to several basic types and stream
|
|
|
|
them.
|
|
|
|
|
|
|
|
3.3 Concepts
|
|
|
|
------------
|
|
|
|
You can use KGameProperty basically in two completely different ways. You can
|
|
|
|
also use a mixture of both but this is not recommended. The default behaviour
|
|
|
|
and therefore also the recommended is the "clean" way:
|
|
|
|
a) Always Consistent. This means that a KGameProperty has always the same value
|
|
|
|
on *every* client. This is achieved by using KGameProperty::send() whenever you
|
|
|
|
want to change the value using "=". You can still use changeValue() or
|
|
|
|
setLocal() but send() will be the default. If you use send() then the value of
|
|
|
|
the property does *NOT* change immediately. It is just sent to the
|
|
|
|
KMessageServer which forwards the value to all clients. As soon as the new value
|
|
|
|
is received from the message server the KGamePropertyHandler (a collection class
|
|
|
|
for KGameProperty) calls KGameProperty::load() and changes the value of the
|
|
|
|
property. So the game first has to go into the event loop, where the message is
|
|
|
|
received. This means to you that you cannot do this:
|
|
|
|
myIntProperty = 10;
|
|
|
|
int value = myIntProperty;
|
|
|
|
As myIntPoperty still has the old value when "value = myIntProperty" is called.
|
|
|
|
This might seem to be quite complex, but
|
|
|
|
KGamePropertyHandler::signalPropertyChanged() is emitted whenever a new value is
|
|
|
|
assigned so you can connect to this and work immediately with the new value.
|
|
|
|
You gain the certainty that the value is the same on every client every time.
|
|
|
|
That will safe you a lot of time debugging!
|
|
|
|
Another way is the "dirty" way:
|
|
|
|
b) Not Always Consistent. Sometimes you really *want* to do something like
|
|
|
|
myIntProperty = 10;
|
|
|
|
int value = myIntProperty;
|
|
|
|
but this is not possible with the default behaviour. If you call
|
|
|
|
KGameProperty::setAlwaysConsistent(false) in the constructor (right after
|
|
|
|
registerData()) you get another behaviour. "=" means changeValue() now.
|
|
|
|
changeValue() also uses send() to change the value but additionally calls
|
|
|
|
setLocal() to create a local copy of the property. This copy now has the value
|
|
|
|
you supplied with "=" and is deleted again as soon as any value from the network
|
|
|
|
is received.
|
|
|
|
|
|
|
|
4. KGameIO
|
|
|
|
----------
|
|
|
|
The class KGameIO is used to let the players communicate with the server. You
|
|
|
|
can plug as many KGameIO objects into a player as you want, e.g. you can plug a
|
|
|
|
KGameMouseIO and a KGameKeyIO into a player so that you can control the player
|
|
|
|
with the mouse and the keyboard - e.g. in a breakout game.
|
|
|
|
You can probably see the advantage: as most of the control stuff is common in a
|
|
|
|
lot of games you can use the same IO class in many different games with very
|
|
|
|
small adjustments.
|
|
|
|
You could also put all the IO stuff directly into your KPlayer object, like
|
|
|
|
sendBet(int money) for a poker game. But there is a major disadvantage and I'm
|
|
|
|
very sure you don't want to use a KPlayer object for your IO stuff as soon as
|
|
|
|
you know which disadvantage:
|
|
|
|
KGameIO is designed to be able to switch between different IOs "on the fly". So
|
|
|
|
you might have a KGamePlayerIO, derived from KGameIO, for your game. But now
|
|
|
|
this player (who "owns"/uses the KGamePlayerIO) leaves the game (e.g. because he
|
|
|
|
was a remote player). So now the game would be over for every player as one
|
|
|
|
player is now out of order. But with KGameIO you can just let any of the
|
|
|
|
remaining clients create a KGameComputerIO and plug this into the player. So the
|
|
|
|
player now is controlled by the computer and the game can continue.
|
|
|
|
|
|
|
|
Think about it! You don't have to care about removing players when a player
|
|
|
|
leaves as you can just replace it! The same works the other way round: imagine a
|
|
|
|
game with 10 player (e.g. 5 human and 5 computer players) that has already
|
|
|
|
started. You cannot add any further players without restarting. So if there are
|
|
|
|
any additional player you can just call KPlayer::removeGameIO() which removes
|
|
|
|
the IO of a computer player and then call KPlayer::addGameIO() for the same
|
|
|
|
player which adds a GameIO for new human player. That's all!
|
|
|
|
|
|
|
|
To achieve this you just have to make sure that you make *all* of your IO
|
|
|
|
operations through a KGameIO! So instead of using MyPlayer::sendBet(int money)
|
|
|
|
you should use something like MyIO::sendBet(). The amount of money would
|
|
|
|
probably be calculated by the game IO itself.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5. Debugging
|
|
|
|
------------
|
|
|
|
The general debugging concept (if there is one at all) or general debugging
|
|
|
|
hints are not yet written. Feel free to do so
|
|
|
|
|
|
|
|
5.1 KGameDebugDialog
|
|
|
|
--------------------
|
|
|
|
A nice way of debugging a KGame based game is the KGameDebugDialog. Basically
|
|
|
|
all you have to do is to add something like "Debug" to your game's menu and add
|
|
|
|
a slot like
|
|
|
|
slotDebug()
|
|
|
|
{
|
|
|
|
KGameDebugDialog* dialog = new KGameDebugDialog(mGame, this);
|
|
|
|
connect(dialog, SIGNAL(finished()), dialog, SLOT(slotDelayedDestruct()));
|
|
|
|
dialog->show();
|
|
|
|
}
|
|
|
|
that's it.
|
|
|
|
You can now click on that menu entry and you get a non-modal dialog where you
|
|
|
|
can start to debug :-)
|
|
|
|
The dialog consist of several pages. You can easily add your own using
|
|
|
|
KDialogBase::addVBoxPage() (for example).
|
|
|
|
|
|
|
|
5.1.1 Debug KGame
|
|
|
|
-----------------
|
|
|
|
The first page, "Debug KGame" shows on the left most or even all status values of
|
|
|
|
KGame. That contains e.g. minPlayers(), isAdmin(), gameStatus(), ...
|
|
|
|
The right side is probably the more important one. It lists *all* KGameProperties
|
|
|
|
which have been inserted to this KGame object (only to this KGame object - not
|
|
|
|
the ones that have been added to the players!). Most of the status variables of
|
|
|
|
the left side are here again as they are implemented as KGameProperty. You can
|
|
|
|
see the name of the property (together with its ID), its value and the policy
|
|
|
|
this property uses. Note that "unknwon" will be displayed as name of the
|
|
|
|
property if you haven't supplied one. See KGamePropertyBase::registerData() for
|
|
|
|
info. You probably always want to supply a name for the property to debug it
|
|
|
|
easily. In the future there will be something like
|
|
|
|
KGamePropertyHandler::setDebug() so that you can switch off debugging and save
|
|
|
|
the memory of the names in a release version.
|
|
|
|
For as long as you use standard types for your properties (int, long, bool,
|
|
|
|
...) you should always be able to see the value of the property. If you just see
|
|
|
|
"unknown" then this type has not been implemented. You can connect to the signal
|
|
|
|
KGamePropertyHandler::signalRequestValue() and supply a QString with the value
|
|
|
|
yourself. If you do so for a standard type please also submit a bug report!
|
|
|
|
|
|
|
|
Currently the dialog does *not* update automatically! So you alway have to click
|
|
|
|
the "update" button when you want a current value. There are several reasons for
|
|
|
|
this (and one of them is that i'm too lazy to implement the update ;)). E.g.
|
|
|
|
often (very often) a property is just in the background - stores e.g. the
|
|
|
|
available money in a game. But you don't want it to update whenever the value
|
|
|
|
changes (a player receives/pays money) but only when the value on the screen
|
|
|
|
changes.
|
|
|
|
|
|
|
|
5.1.2 Debug Players
|
|
|
|
-------------------
|
|
|
|
This page consists of three widgets. On the very left there is a list of all
|
|
|
|
players in the game. Only the IDs are displayed to save space. If you click one
|
|
|
|
the other widgets are filled with content. These widgets are quite much the same
|
|
|
|
as the ones in "Debug KGame" - the left shows the value of the functions and the
|
|
|
|
right one displays all KProperties of a player. Not much to say here - except:
|
|
|
|
See "Debug KGame".
|
|
|
|
|
|
|
|
If you change to another player the value are also updated.
|
|
|
|
|
|
|
|
5.1.3 Debug Messages
|
|
|
|
--------------------
|
|
|
|
This page is probably not as important as the other ones. It displays *every*
|
|
|
|
message that is sent through the KGame object. As a KGameProperry also send
|
|
|
|
messages you probably get a lot of them...
|
|
|
|
You can exclude message IDs from being displayed (e.g. all game properties).
|
|
|
|
You can also change the sorting of the list to see all messages of a certain ID.
|
|
|
|
The default is to sort by time (which is displayed on the left side).
|
|
|
|
|