You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/libtdegames/kgame/DESIGN

408 lines
18 KiB

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).