Monday, January 25, 2016

Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 8

Game Client Source code overview

In this post i will teach you about the client class that handles the game logic , as you remember
The "Real" game is played on the server BUT we as players need to visualize the game.
The Game logic in the client have many tasks to do but all the tasks are stateless .
That means in simple words there is no data saved on the client each action made 
will first make request to the server and after confirmation it will make its move , 
of course all the actions will be informed in real time to the other players.
also in our game , this Game layer will take the control of the WebSockets callbacks .

and will handle the building the request JSON protocol and the Decoding the returned
JSON from the server And Act upon the events received .

Here is JSON communication example :

User Login to the game with user name send login request to the server:
The JSON format:
{
"event": 2,
"username": "Pla"
}
The Server confirms the new Player and it send back to the current client and other client:
response JSON format :
{
"winnercards": "",
"winner": -1,
"activecardid": "c47",
"endgame": -1,
"players": [],   //Here is the array of the other players in the room
"deck": "",
"id": 0,
"event": 3,
"activeplayerid": 0,
"registertionnum": 0,
"username": "Pla",
"numcardsleft": 25

}



In this end of this tutorial the login should look like this :
2 browsers open , to simulate 2 players doing login
and each one see the other in real time




GameScene.js

Game logic class 



  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
var spriteFrameCache = cc.spriteFrameCache;
var size = null;
var MENU_TAG = 1;
var CENTER_DECK = 2;
var CARD = 3;
var TEXT_INPUT_FONT_SIZE_PLAYER = 20;
 
var GameLayer = cc.Layer.extend({
    cardDeckSprite:null,
    sprite:null,
    listener1:null,
    jsonData:null,
    textFieldUserNameCapton:null,
    currentPlayer:null,
    otherPlayers:[],
    ctor:function (_jsondata) {
        this.jsonData = _jsondata;
        //after succesful login we want to take control on massages coming from server
        //so we attache this new callback function to the websocket onmessage
        ws.onmessage = this.ongamestatus.bind(this);
        ws.onclose = this.onclose.bind(this);
        ws.onerror = this.onerror.bind(this);
        
        this._super();
        size = cc.winSize;        
        return true;
    },
     
    onEnter:function () {       
        this._super();   
            // Make sprite1 touchable
        this.listener1 = cc.EventListener.create({
            event: cc.EventListener.TOUCH_ONE_BY_ONE,
            swallowTouches: true,
            onTouchBegan: function (touch, event) {
                event.getCurrentTarget().invokeTurn(); 
                return true;
            },
            onTouchEnded: function (touch, event) {
            }
        });
        this.setUserObject(this.listener1);
        cc.eventManager.addListener(this.listener1, this);
        spriteFrameCache.addSpriteFrames(res.sprites_plist, res.sprites_png); 
           
        var userName = this.jsonData.username;
        this.textFieldUserNameCapton = new cc.TextFieldTTF("Hello "+userName,
            TEXT_INPUT_FONT_NAME,
            TEXT_INPUT_FONT_SIZE_PLAYER);
        this.textFieldUserNameCapton.setTextColor(cc.color.RED);
        this.textFieldUserNameCapton.x = size.width / 2;
        this.textFieldUserNameCapton.y = size.height-(TEXT_INPUT_FONT_SIZE_PLAYER+100);
        this.addChild(this.textFieldUserNameCapton,10);  
        this.eventHandler(this.jsonData.event);
     },
    onExit:function () {
         
        this._super();
        spriteFrameCache.removeSpriteFramesFromFile(res.sprites_plist);
        spriteFrameCache.removeSpriteFramesFromFile(res.sprites_png);
        cc.eventManager.removeListener(this.listener1);
    }, 
    eventHandler:function(event)
    {
        switch (event) {
            case Events.LOGIN_DONE:
            {
              this.setupCurrentPlayer();
              break;
            }
            case Events.NEW_USER_LOGIN_DONE:
            {
              this.setupOtherPlayerS();
              break;
             
            }
            case Events.PLAY_DONE:
            {
              this.setPlayState();
              break;
             
            }
        }
        this.setTurnMassage();
    },
    setTurnMassage:function()
    {
       var userName = this.jsonData.username;
       var activePlayerId = this.jsonData.activeplayerid;
       if(activePlayerId === this.currentPlayer.id)
       {
           this.textFieldUserNameCapton.setString("Hello "+userName+" Its your turn"); 
       }
       else
       {
           this.textFieldUserNameCapton.setString("Hello "+userName); 
       }
    },
    onCallbackMoveTo:function (nodeExecutingAction,player) {
            this.currentPlayer.updatePlayerNumberOfCardsCaption(this.jsonData.numcardsleft);
            this.otherPlayers[0].updatePlayerNumberOfCardsCaption(this.jsonData.players[0].numcardsleft);       
    },
    setPlayState:function()
    {
        this.currentPlayer.setNewCardById(this.jsonData.activecardid);
        this.updatePlayer(this.currentPlayer,this.jsonData); 
        if(this.jsonData.players.length>0)
        {
            for(var i=0;i<this.jsonData.players.length;i++)
            {
                if(this.jsonData.players[i].event === Events.PLAY_DONE)
                {
                    this.otherPlayers[i].setNewCardById(this.jsonData.players[i].activecardid);
                    this.updatePlayer(this.otherPlayers[i],this.jsonData.players[i]);
                }
            } 
        }
        //handle animation
        var pos = null;
        var activePlayerId = this.jsonData.activeplayerid;
        if(activePlayerId !== this.currentPlayer.id)
        {
            pos = this.currentPlayer.getPosition();
        }
        else
        {
            //TODO this fix this hard coded position getter
            pos = this.otherPlayers[0].getPosition();
        }
        var cardInDeck = this.jsonData.deck;
        this.animateCard(cardInDeck,pos);
    },
    animateCard:function(_cardInDeckId,_pos)
    {
        var cardName =  cards[_cardInDeckId];
        this.cardDeckSprite = new cc.Sprite("#"+cardName);
        this.cardDeckSprite.attr({
                x: _pos.x,//(cc.winSize.width / 2) ,
                y: _pos.y//(cc.winSize.height / 2)
            });
        this.addChild(this.cardDeckSprite,1,CENTER_DECK); //TODO handel removeble when not needed by tag name
        var posMid = cc.p(size.width/2,size.height/2); 
        var action = cc.sequence(
                    cc.moveTo(0.5, posMid),
                    cc.callFunc(this.onCallbackMoveTo,this,this.cardDeckSprite));
        this.cardDeckSprite.runAction(action);          
    },
    invokeTurn:function()
    {
       if(this.currentPlayer.id == this.currentPlayer.activeplayerid)
       {
          var config = {
                        event:Events.PLAY,
                        username:this.currentPlayer.username,
                        id:this.currentPlayer.id,
          };  
          var message = Encode(config);
          ws.send(message);
       }
       else
       {
          console.log("GameScene->invokeTurn() not its turn:"+this.currentPlayer.id); 
       }
    },
    setupCurrentPlayer:function()
    {
        this.currentPlayer = new Player(this.jsonData.id,this.jsonData.username,
                                                        this.jsonData.activecardid);        
        this.updatePlayer(this.currentPlayer,this.jsonData);      
        
        this.addChild(this.currentPlayer,1); 
        this.positionPlayer(this.currentPlayer); 
        if(this.jsonData.players.length>0)
        {
            for(var i=0;i<this.jsonData.players.length;i++)
            {
                if(this.jsonData.players[i].event === Events.NEW_USER_LOGIN_DONE)
                {
                    this.setupOtherPlayer(i);
                }
            } 
        }     
    },
    setupOtherPlayerS:function()
    {
        if(this.jsonData.players.length>0)
        {
            for(var i=0;i<this.jsonData.players.length;i++)
            {
                if(this.jsonData.players[i].event === Events.LOGIN_DONE)
                {
                    this.setupOtherPlayer(i);
                }
            } 
        }
    },
    setupOtherPlayer:function(inx)
    {
        this.otherPlayers[inx] = new Player(this.jsonData.players[inx].id,
                                            this.jsonData.players[inx].username,
                                            this.jsonData.players[inx].activecardid);      
        this.updatePlayer(this.otherPlayers[inx],this.jsonData.players[inx]);
        
        this.addChild(this.otherPlayers[inx],1); 
        this.positionPlayer(this.otherPlayers[inx]); 
        
    },
    updatePlayer:function(_player,jsonObj)
    {
        _player.activeplayerid = jsonObj.activeplayerid;
        _player.activecardid =  jsonObj.activecardid;
        _player.event =  jsonObj.event;
        _player.registertionnum =  jsonObj.registertionnum; 
        _player.winner =  jsonObj.winner; 
        _player.winnercards =  jsonObj.winnercards; 
        _player.numcardsleft =  jsonObj.numcardsleft;          
    },
    positionPlayer:function(_player)
    {
        if(_player.registertionnum === 0)
        {
            _player.attr({
                x: (cc.winSize.width / 2) + 150,
                y: (cc.winSize.height / 2)
            });
            
        }
        else if (_player.registertionnum === 1)
        {
            _player.attr({
                x: (cc.winSize.width / 2) - 150,
                y: (cc.winSize.height / 2)
            });
        }
    },
    ongamestatus:function(e) {
          console.log("GameScene->.ws.onmessage():"+e.data);
          if(e.data!==null || e.data !== 'undefined')
          { 
              this.jsonData = Decode(e.data);
              this.eventHandler(this.jsonData.event);
         }
     }
     ,   
    onclose:function (e) {

    },
    onerror:function (e) {

    }
});  
var EnterWorldScene = cc.Scene.extend({    
    session:null,
    ctor:function (_session) {
        this.session = _session;
        this._super();
        size = cc.winSize;        
        return true;
    },
    onEnter:function () {
        this._super();
        var layer = new GameLayer(this.session);
        this.addChild(layer);
    }    
});


  1. Lines 9 - 15 : members of the class ,
    cardDeckSprite : the sprite that holds the middle deck cards.
    listener1: the cocos2d-x object which listen to touch events from teh user
    jsonData: the JSON data received from the server.
    textFieldUserNameCapton : User name of the current user
    currentPlayer : current user that login
    otherPlayers: array that holds the Other players in the room data  , so wee can see the updates they made 
  2. Lines 16 -26 : class constructor that do several important things ,
    First - it gets the initial JSON data from the server that received when User LOGIN event
    confirmed.
    Second  - 20 to 22 it takes control of the WebSocket build in callbacks . and bind them to
    this class local callbacks .
  3. Lines 32 - 41 : set up Cocos2d-x new EventListener to detect on devices that enable touches
    The touch event and mouse events when it disabled . will invoke the invokeTurn() function .
  4. Lines 47 - 54 : set the user name according to the data received from the server 
  5. Lines 63 -83 : Event Handler function that is triggered first time when user enter the game room in line 54 .
    and then it triggered each time there is massage from the server  line 241.
  6. Line 84 : after each event there is need to update the players with who turn is now 
  7. Lines 99 -  101 : this callback function is called to invoke the players updatePlayerNumberOfCardsCaption function after the animation of the cards is finished. ( see lines 143 - 145 )
  8. Lines 103 -131 : The function is called when the PALY_DONE event is received from the server.
    Lines 105 -117 :  the current player get updated , and also the other players in the room get updated with new states .
    Lines 119 -131 : trigger the animation of the cards.
  9. Lines 133 - 147 : card animation function , animate the moving card into the middle card deck.
  10. Lines 148 - 164 : this function is triggered when player touch the screen or by mouse in desktop or finger on mobile device see paragraph 3 above .
    it will only be invoked if the current player is the active player . that means the one which is his turn in the game .
    then it will prepare the JSON massage to send to the server .
  11. Lines 165 - 183 : setup the current player this is called once when the LOGIN_DONE event
    is received from the server.
  12. Lines 184 - 196 : set up the other players in he game room , this is for us to see what is the status of the other players . this is triggered when the server sends the client NEW_USER_LOGIN_DONE event .
  13. Lines 197 - 207 : create new other player and set it to the otherPlayers client array .
  14. Lines 208 - 217 : update player object with new data.
  15. Lines 218 -235 : position the players in the game room based on order they joined the game
    the data is kept in the JSON registertionnum  member and set to the player registertionnum member .
  16. Lines 236 - 250 : those functions are the new calback functions that are bound to our client
    WebSocket object , see paragraph 2 above .
Thats it!  this is the last part of 8 parts tutorial .
 there are things i still need to fix and refine will do those over time.
I hope it was informative .

Sunday, January 24, 2016

Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 7

Game Client Source code overview


As discussed in the previous Server code review posts , here is reminder :
Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 1
I wrote about basic principle in MMO games in which the game is 
actually played on the server
That means the client side needs to send information about every future move it does ,
AND also to get confirmation from the server that indicate the move is valid .
The communication is done via WebSocket protocol , and the "language" the client
"talks" to the server. this 
"language" can also be called protocol in our game it is:
JSON structure and Events that both the client and the server must know about.

Example  of JSON massage exchange between the client and the server .


GameConfig.js

The configuration of the game client is defined in this file .
It contains the Events enumeration , the WebSocket Cocos2d-x-js definition and the cards hash-map.And 2 JSON helpers functions


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var WebSocket = WebSocket || window.WebSocket || window.MozWebSocket; 
var ws = null;

var cards = {
    c1:"cardClubsA.png",
    c2:"cardClubsJ.png",
    c3:"cardClubsK.png",
    ...
    ...
    ...
    c53:"cardClubs9.png",
};

Events  = {
    HANDSHAKE_COMPLETE_SUCCESS:1,
    LOGIN:2,
    LOGIN_DONE:3,
    NEW_USER_LOGIN_DONE:4,
    PLAY:5,
    PLAY_DONE:6,
};


var Encode = function(obj) {
       return JSON.stringify(obj);
   };
var Decode = function(obj) {
    return JSON.parse(obj);
};


  1. Line 1 : This is how cocos2d-x defines the Cross platform WebSocket object .
  2. Lines 4 - 12 : The cards hash map (the complete list is in the original source code ).
    The server and the client most have the same keys to the right cards names .
    see:Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 3
    Look in the GameManager.java at the bottom of this file for the cards hash in the server side .
    the setCardsHash() fucntion .
  3. Lines 14 - 21 : This is first part of the so called "language" we defined between the Client and the Server.
    Those events are also defined in the server code in Config.java file  :
    Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 4

    Line 15  : When Websocket protocol is confirmed.
    Line 16  : Login request form the client .
    Line 17  ; Login is handled , response to client that login is done.
    Line 18  : New User is joined the room.
    Line 19  : Player is done its turn and played.
    Line 20 : Response to all other player and the current player that the turn is done
  4. Lines 24 - 26 : Helper function which convert JSON object to string.
  5. Lines 27 - 29 : Helper function which convert JSON string to object.


Player.js

This class is representing the Player class , The player class members are the same (almost )
As the server Player class , again as you remember the game is played on the server .
With some additional helper functions.



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
var TEXT_INPUT_FONT_NAME = "Thonburi";
var TEXT_INPUT_FONT_SIZE = 36;
var TEXT_INPUT_FONT_SIZE_PLAYER = 20;
 var Player = cc.Sprite.extend({
    id:null,
    username:null,
    event:null,
    activecardid:null,
    activeplayerid:null,
    registertionnum:null,
    winner:null,
    winnercards:"",
    numcardsleft:null,
    spriteSize:null,
    textFieldUserName:null,
    ctor: function(_id,_username,_activecardid){        
        this.id = _id;
        this.username = _username;
        this.activecardid = _activecardid;
        var cardName = this.getPlayerCardById(this.activecardid);
        this._super("#"+cardName);
       
    },    
    onEnter:function () {        
        this._super();  
        this.spriteSize = this.getContentSize();
        this.setPlayerNameCaption(this.username);
        this.setPlayerNumberOfCardsCaption(this.numcardsleft);
    },
    onExit:function () {        
         this._super();      
    },
    getPlayerCardById:function(_cardId)
    {
        var cardName =  cards[_cardId];
        return cardName;
    },
    setPlayerNameCaption:function(_name)
    {
        this.textFieldUserName = new cc.TextFieldTTF(_name,
            TEXT_INPUT_FONT_NAME,
            TEXT_INPUT_FONT_SIZE_PLAYER);
        this.textFieldUserName.setTextColor(cc.color.RED);
        this.textFieldUserName.x = this.spriteSize.width / 2;
        this.textFieldUserName.y = 0-TEXT_INPUT_FONT_SIZE_PLAYER;
        this.addChild(this.textFieldUserName,2); 
    },
    updatePlayerNumberOfCardsCaption:function(_numberOfCards)
    {
        this.numcardsleft = _numberOfCards
        this.textFieldNumberOfCards.setString("Cards:"+this.numcardsleft); 
    },
    setPlayerNumberOfCardsCaption:function(_numberOfCards)
    {
        this.textFieldNumberOfCards = new cc.TextFieldTTF("Cards:"+_numberOfCards,
            TEXT_INPUT_FONT_NAME,
            TEXT_INPUT_FONT_SIZE_PLAYER);
        this.textFieldNumberOfCards.setTextColor(cc.color.RED);
        this.textFieldNumberOfCards.x = this.spriteSize.width / 2;
        this.textFieldNumberOfCards.y = 0-(TEXT_INPUT_FONT_SIZE_PLAYER+TEXT_INPUT_FONT_SIZE_PLAYER);
        this.addChild(this.textFieldNumberOfCards,2); 
    },
    setNewCardById:function (_cardid)
    {
        //get the right card from the cards hash
        var cardName =  cards[_cardid];
        this.activecardid = cardName;
        //this._super(this.playerSpriteFrameName);
        this.setSpriteFrame(cardName);
    },
});
 

  1. Lines 1 -15 : Variables Definition of the player members . almost the same as the server side
    Player here is reminder:
    Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 5
  2. Lines 16 -21 : the player constructure that init the player current card , so the player visual
    will be his current card to play. 
  3. Lines 24 - 28 : The first function according to the Cocos2d-x reference which be triggered
    when the sprite is render to the screen .
    it will invoke 2 functions:
    setPlayerNameCaption - set the player name.
    setPlayerNumberOfCardsCaption - set the number of cards the player left with.
  4. The other functions as helper functions that are self explained.

    
    
In  the next  part of the tutorial i will explain about the final client source code
Where all the client game logic is happen :
Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 8


Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 6

Game Client Source code overview


The client in this game is developed using cocos2d-x framework using its JavaScript API,
This means by using this framework we are enabling our game to be cross platform .
With the
same code base !  including the WebSocket part 

The game client game logic will be explained using the web version of Cocos2d-x.
And in the end i will show you how to compile the source code in iOS and Windows Desktop

To develop in JavaScript and HTML5 you can choose several tools.
You can use simple text editor like Vi , Notepad++ , and debug the code With
build-in Chrome Debugger . 
Or you can take the full IDE Approach as i shown in this Tutorial :Debug Cocos2d-x HTML5 Project using NetBeans IDE and Chrome browser in 10 easy steps.

The game flow is very simple:
  1. Player enter its User name.
  2. Client Send the user name to the server
  3. Server confirms the user name and send back to the client
  4. Server Updates other client about new user 
  5. Client receives the confirmation from the server 
  6. Client enter to the Game room.
  7. Client plays , send the play move to server for confirmation 
  8. Server get the play move Confirms the move 
  9. Server send back confirmation to client and updates other clients about the player move

    and on and one until the game end  
You can see the game logic diagram for better understanding the game flow



Go to the source code you previously cloned from :
https://github.com/meiry/multiplayer_cocos2dx-js_using_netty_websockets

Go to the /Client/WarCardJS directory this is where all the client side source code are in.
To run it im using simple Apache server installed locally .
then start it by going to where the index.html is.  like normal static web site .


Before we start we need to configure few things in our Cocos2d-x framework
In the project.json file we adding the additional files and modules that is part of this Game 
in our case we added the JS files which we are going to be using in our game 
and additional Cocos2d-x module.

project.json


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
{
    "project_type": "javascript",

    "debugMode" : 1,
    "showFPS" : true,
    "frameRate" : 60,
    "noCache" : false,
    "id" : "gameCanvas",
    "renderMode" : 0,
    "engineDir":"frameworks/cocos2d-html5",

    "modules" : ["cocos2d","extensions"],

    "jsList" : [
        "src/GameConfig.js",
        "src/GameScene.js",       
        "src/resource.js",
        "src/app.js",        
        "src/Player.js"
    ]
}



  1. Line 12 : New module is added called "extensions"    for advance GUI 
  2. Line 15 - 19 : The new js source files the game is using .

main.js

This file is the game bootstrap , that mean here the game starts , the first game scene Initialize
here .



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
cc.game.onStart = function(){
    if(!cc.sys.isNative && document.getElementById("cocosLoading")) //If referenced loading.js, please remove it
        document.body.removeChild(document.getElementById("cocosLoading"));

    // Pass true to enable retina display, on Android disabled by default to improve performance
    cc.view.enableRetina(cc.sys.os === cc.sys.OS_IOS ? true : false);
    // Adjust viewport meta
    cc.view.adjustViewPort(true);
    // Setup the resolution policy and design resolution size
    cc.view.setDesignResolutionSize(450, 800, cc.ResolutionPolicy.SHOW_ALL);
    // Instead of set design resolution, you can also set the real pixel resolution size
    // Uncomment the following line and delete the previous line.
    // cc.view.setRealPixelResolution(960, 640, cc.ResolutionPolicy.SHOW_ALL);
    // The game will be resized when browser size change
    cc.view.resizeWithBrowserSize(true);
    //load resources
    cc.LoaderScene.preload(g_resources, function () {
        cc.director.runScene(new LoginScene());
    }, this);
};


  1. Line 10 : set the window resolution 
  2. Line 18 : The name of our first game scene called LoginScene ,
    the screen of the LoginScene will be shown first 


app.js 

This file is holding the main Scene called 
LoginScene  that Initialize  LOGIN Screen that lives inside the LoginLayer Object
This is the first file that starting the game .


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
var TEXT_INPUT_FONT_NAME = "Thonburi";
var TEXT_INPUT_FONT_SIZE = 36;
var sceneIdx = -1;
var TEXT_FIELD_ERR = 1;
var LoginLayer = cc.Layer.extend({
    sprite:null,
    size:null,
    textFieldUserName:null,
    textErorrConnectedField:null, 
    enterWorldScene:null,
    textField:null,
    ctor:function () {
        //////////////////////////////
        // 1. super init first
        this._super();
        this.size = cc.winSize;        
        return true;
    },
     onEnter:function () {
        //----start2----onEnter
        this._super();         
        var winSize = cc.director.getWinSize();
        // Create the textfield
        this.textField = new ccui.TextField();
        this.textField.setMaxLengthEnabled(true);
        this.textField.setMaxLength(30);
        this.textField.setTouchEnabled(true);
        this.textField.fontName = TEXT_INPUT_FONT_NAME;
        this.textField.fontSize = 30;
        this.textField.placeHolder = "[click here for user name]";
        this.textField.x = winSize.width / 2.0;
        this.textField.y = winSize.height / 2.0;
        this.textField.addEventListener(this.textFieldEvent, this);
        this.addChild(this.textField);
        cc.MenuItemFont.setFontSize(35);
        var menu = new cc.Menu(
            new cc.MenuItemFont("Login Game", this.loginGame, this)
            );
        menu.alignItemsVerticallyWithPadding(4);
        menu.x = this.size.width / 2;
        menu.y = this.size.height / 2 - 50;
        this.addChild(menu,5);
    },
     onExit:function () {
         this._super();    
    },
    createErrorMsg:function () {
            this.textErorrConnectedField = new cc.TextFieldTTF("Error Connecting Server try again",
                                                            TEXT_INPUT_FONT_NAME,
                                                            TEXT_INPUT_FONT_SIZE+20);
            this.textErorrConnectedField.setTag(TEXT_FIELD_ERR);            
            this.textErorrConnectedField.x = cc.winSize.width / 2;
            this.textErorrConnectedField.y = cc.winSize.height / 2 +50;
            this.addChild(this.textErorrConnectedField,2);  
    },
    
    loginGame:function (sender) {
        //remove error msg if any 
        if(this.getChildByTag(TEXT_FIELD_ERR)!==null)
        {
            this.removeChildByTag(TEXT_FIELD_ERR);
        }
        
        //check login in the server 
        var txtUserName = this.textField.getString();
        var config = {
                    event:Events.LOGIN,
                    username:txtUserName
     };
        var message = Encode(config);
        try {
            ws = new WebSocket("ws://localhost:8888/ws"); 
            ws.onopen = function() {
                    ws.send(message);
            };
            ws.onmessage = function (e) {
                console.log("app->srv.ws.onmessage():"+e.data);
                if(e.data!==null || e.data !== 'undefined')
                { 
                      var jsonFromClient = Decode(e.data);
                      if(jsonFromClient.event === Events.LOGIN_DONE)
                      {
                           enterWorldScene = new EnterWorldScene(jsonFromClient);
                           cc.director.runScene(enterWorldScene);
                      }
                }
            };
            ws.onclose = function (e) {
                    
            };
            ws.onerror = function (e) {
                  
            };
        } catch (e) {
            console.error('Sorry, the web socket at "%s" is un-available', url);
        }
    },
    onClickTrackNode:function (clicked) {
        var textField = this._trackNode;
        if (clicked) {
            textField.attachWithIME();
        } else {
            textField.detachWithIME();
        }
    },
});
var LoginScene = cc.Scene.extend({
    onEnter:function () {
        this._super();
        var layer = new LoginLayer();
        this.addChild(layer);
    }
    
});



  1. Lines 19 - 42 : Setup the Textbox and the login button 
  2. Line 37 : add listener to when the player is clicking the login button the loginGame function will be triggered.
  3. Lines 57 - 97 : The loginGame function , 
  4. Line 72 This function will open the WebSocket connection With the server .
    and wait for handshake confirmation from the server 
  5. Lines 76 -87 : the ws.onmessage callback is part of the browser WebSocket API
    and it is triggered when there is massage from the server.
  6. Lines 80 - 85 : when the massage is coming from the server first thing is the client do
    Is decode the JSON massage  , check if the event we receive are the right one in our case
    LOGIN_DONE ( as part of our protocol ) .
    If we got LOGIN_DONE it means the players was confirmed and we are ready to start the game and invoke the game scene called :"EnterWorldScene"  which holds the game logic.


     
In the next post i will teach you how the client game logic is developed:
Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 7





Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 5

Game Server Source code overview



Now we are close to the point which we return with WebSocket response the new game states to the clients , but what are those "Game states" ?
it can be represented in many ways but in our case which is super simple the states will be kepet
in 2 levels .

First level is global game state and all things that are relevant to the general game like:
How many players are in the game
What is the scoring status
Who is the winner
How many cards left in the middle card deck

Second level of states are the ones that relevant to the players
Each player has several states , the states are updated during the game .


Player.java

This is the class that represent the player and the player states
Only the important code is explained all the rest of the code are getters and setter to the members
of The Player Class.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Player {
 private final static Logger LOG = LoggerManager.GetLogger(GameServerMain.class.getName());
 private LinkedList<String> PlayerCards;
 private String userName;
 private int Event;
 //Session channel
 private Channel channel;
 //Player Json massage
 private String playerJson;
 //the player which is active and has the turn
 private int activeplayerid;
 private int id;
 private String activecardid;
 private int registertionNum;
 private int winner;
 private String winnercards;
 private String deckcard;
 //mark the end game the value will be the winner id
 private int endgame;
  public Player()
 {
  this.channel = null;
  Init();
 } 
 public Player(Channel _channel)
 {
  this.channel = _channel;
  Init(); 
 }



  1. Line 3 : the cards that were dealt to the player , which are the half of the total cards in the page
  2. Line 4 : player user name.
  3. Line 5 : player event , the current event that the player is in.
  4. Line 7 : each player will hold the Netty channel in which it used to communicate from the client . this channel will be used to inform back to the client the state of the palyers and the state of the game.
  5. Line 9 : the json string that is constructed during the process of playing the game in the server , this json will be the one that used to send back to the player client .
  6. Line 11 : the id of the player that is currently active , that means the one that is playing its turn
  7. Line 12 : player unique id.
  8. Line 13 : the card that is played now 
  9. Line 14 : the order of which the player registered , this way it will be determined the player turn. 
  10. Line 15 : mark as winner of this game
  11. Line 16 : save the winner cards in this global game turn the format is:
    currentPlayerCardId+"_"+ prevCardId
  12. Line 17 : the card which is now on the middle card deck.
  13. Line 19 : Id of the player that win the game
  14. Lines 25 - 29 : Player Constructure that gets the player channel instance
    This channel instance will be used to communicate with the client .
GameResponseDispatcher.java
This class is responsible to build the JSON object that will be send back to the clients that are associated with this game .
The class have 2 main response functions as you remember we have 2 main events in the game
LOGIN and PLAY.
so the class will handle the response to the LOGIN event  :ResponseDispatcheLoginDone
and to the PLAY event : 
ResponseDispatchePlayDone


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
public class GameResponseDispatcher {
 private final static Logger LOG = LoggerManager.GetLogger(GameEventHandler.class.getName());
 private GameManager gameManager; 
 
 public GameResponseDispatcher(GameManager _gameManager)
 {
  this.gameManager = _gameManager;
   
 }
 public boolean ResponseDispatcheLoginDone(int  _playerId)
 {
  int currentPlayerId = _playerId;
  Player currentPlayer = this.gameManager.getPlayers().get(currentPlayerId);
  
  JSONObject currentPlayerJsonObj = setPlayerToJson(currentPlayer,
               currentPlayer.getEvent(),
               currentPlayer.getId());
  
  JSONObject currentPlayerJsonObj2 = setPlayerToJson(currentPlayer,
              currentPlayer.getEvent(),
              currentPlayer.getId());
   
  //build the other players json
     JSONArray currentPlayerArrayNewPlayers = new JSONArray();
     JSONArray ArrayCurrentPlayers = new JSONArray();
     ArrayCurrentPlayers.put(currentPlayerJsonObj2);
     Iterator<Entry<Integer, Player>> it = this.gameManager.getPlayers().entrySet().iterator();
     while (it.hasNext()) {
      @SuppressWarnings("rawtypes")
   Map.Entry pair = (Map.Entry)it.next();
         Player playerIt = ((Player)pair.getValue());
          
         if(currentPlayerId != playerIt.getId())
         {
          this.gameManager.getPlayers().get(playerIt.getId()).setEvent(Config.NEW_USER_LOGIN_DONE); 
          JSONObject playerJsonObjIt = setPlayerToJson(playerIt,
                     Config.NEW_USER_LOGIN_DONE,
                     playerIt.getId());
          JSONObject newPlayerForCurrent  = setPlayerToJson(playerIt,
                      Config.NEW_USER_LOGIN_DONE,
                      playerIt.getId());
          //to current    
          currentPlayerArrayNewPlayers.put(newPlayerForCurrent);
          //to others 
          playerJsonObjIt.put("players",ArrayCurrentPlayers);
          String jsonStr = playerJsonObjIt.toString();
          this.gameManager.getPlayers().get(playerIt.getId()).setPlayerJson(jsonStr);
         }
     }
     //current user array
     currentPlayerJsonObj.put("players",currentPlayerArrayNewPlayers);
     String jsonStr = currentPlayerJsonObj.toString();
     this.gameManager.getPlayers().get(currentPlayerId).setPlayerJson(jsonStr);
    return true;
 }
 
 public boolean ResponseDispatchePlayDone(int _playerId)
 {
  int currentPlayerId = _playerId;
  this.gameManager.getPlayers().get(currentPlayerId).setEvent(Config.PLAY_DONE);
  int newActivePlayer = this.gameManager.getNextActivePlayer(currentPlayerId);
  this.gameManager.getPlayers().get(currentPlayerId).setActiveplayerid(newActivePlayer);
  
  Player currentPlayer = this.gameManager.getPlayers().get(currentPlayerId);
  
  JSONObject currentPlayerJsonObj = setPlayerToJson(currentPlayer,
               currentPlayer.getEvent(),
               currentPlayer.getId());
  
  JSONObject currentPlayerJsonObj2 = setPlayerToJson(currentPlayer,
              currentPlayer.getEvent(),
              currentPlayer.getId());
  
  //build the other players json
     JSONArray currentPlayerArrayNewPlayers = new JSONArray();
     JSONArray ArrayCurrentPlayers = new JSONArray();
     ArrayCurrentPlayers.put(currentPlayerJsonObj2);
     Iterator<Entry<Integer, Player>> it = this.gameManager.getPlayers().entrySet().iterator();
     while (it.hasNext()) {
      @SuppressWarnings("rawtypes")
   Map.Entry pair = (Map.Entry)it.next();
         Player playerIt = ((Player)pair.getValue());
          if(currentPlayerId != playerIt.getId())
         {
          //update each user 
          this.gameManager.getPlayers().get(playerIt.getId()).setDeckcard(currentPlayer.getDeckcard());
          this.gameManager.getPlayers().get(playerIt.getId()).setEvent(Config.PLAY_DONE); 
          this.gameManager.getPlayers().get(playerIt.getId()).setActiveplayerid(newActivePlayer);
          JSONObject playerJsonObjIt = setPlayerToJson(playerIt,
                     Config.PLAY_DONE,
                     playerIt.getId());
          JSONObject newPlayerForCurrent  = setPlayerToJson(playerIt,
                      Config.PLAY_DONE,
                      playerIt.getId());
          currentPlayerArrayNewPlayers.put(newPlayerForCurrent);           
          //to others 
          playerJsonObjIt.put("players",ArrayCurrentPlayers);
          String jsonStr = playerJsonObjIt.toString();
          this.gameManager.getPlayers().get(playerIt.getId()).setPlayerJson(jsonStr);
          
         }        
     }
     //current user array
     currentPlayerJsonObj.put("players",currentPlayerArrayNewPlayers);    
     String jsonStr = currentPlayerJsonObj.toString();
     this.gameManager.getPlayers().get(currentPlayerId).setPlayerJson(jsonStr);
  return true;
 }
 @SuppressWarnings("unused")
 private JSONObject setPlayerToJson(final Player _player,final int event,
          final int _playerId)
 { 
  JSONObject _jsonPlayer = new JSONObject();
  _jsonPlayer.put("id",_playerId);
  _jsonPlayer.put("event",event);
  _jsonPlayer.put("username",_player.getUserName());
  _jsonPlayer.put("activecardid",_player.getActivecardid());
  _jsonPlayer.put("activeplayerid",_player.getActiveplayerid()); 
  _jsonPlayer.put("registertionnum",_player.getRegistertionNum()); 
  _jsonPlayer.put("winner",_player.getWinner()); 
  _jsonPlayer.put("deck",_player.getDeckcard()); 
  _jsonPlayer.put("endgame",_player.getEndgame()); 
  _jsonPlayer.put("winnercards",_player.getWinnercards()); 
  _jsonPlayer.put("numcardsleft",_player.getPlayerCards().size()); 
  return _jsonPlayer;
 }
}


  1. Lines 24 - 49 : Build the response back to the other players about the result of the LOGIN event, the other players will be notified
    about the new player login and also will be receiving the new player data .
    the response event in our super simple game will be with LOGIN_DONE.
  2. Lines 51 -53 : Build the response for the current player . in this case the current player will be
    notified about the other players state if any .
  3. Lines 74 -102 : Build the response back to the other players about the result of the Play event.
    new Event will be send back depending on the Game Play in our Super simple game
    it will return PLAY_DONE event.
  4. Lines 104 - 106 : Build the response for the current player . in this case the current player will be notified about the other players state if any with PLAY_DONE event .
  5. Lines 110 - 125 : Helper function to convert Player data to JSON object.

Now its time to do the Cocos2d-x client code review in the next port:
Multiplayer Card Game using WebSockets,Java Netty Server ,Cocos2d-x-HTML5 - Part 6