This document tries to describe the design of KGame - the KDE multiplayer library. This document has been written by: Andreas Beckermann M. Heni Burkhard Lehner 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 player id's QValueList network priority's IdGameLoad all game data IdGameReactivate QValueList 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 myProperty; }; KGamePropertyInt is just a typedef for KGameProperty - 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(), gametqStatus(), ... 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).