Saturday, January 23, 2016

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

Game Server Source code overview


Remember the opening of this tutorial i mentioned that one of MMO principles is that the game
is played in the server and not in the client most of the times. to avoid cheating on the client and abusing the game .


In this class the data that is send from the client include events to invoke.
Those events are like "Protocol" between the client and the server both sides should understand them
in our case they are :

Config.java


1
2
3
4
5
6
7
//when the websocket http handshake complete 
 public static final int  HANDSHAKE_COMPLETE_SUCCESS = 1;
 public static final int  LOGIN = 2;
 public static final int  LOGIN_DONE = 3;
 public static final int  NEW_USER_LOGIN_DONE = 4;
 public static final int  PLAY = 5;
 public static final int  PLAY_DONE = 6;


  1. Line 2 : when Websocket protocol is confirmed.
  2. Line 3: Login request form the client .
  3. Line 4: Login is handled , response to client that login is done
  4. Line 5: New User is joined the room 
  5. Line 6: Player is done its turn and played.
  6. Line 7 :Response to all other player and the current player that the turn is done 
GameEventHandler.java

This class will handle the game logic here is where the server "Plays" 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
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
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
public class GameEventHandler {
 private final static Logger LOG = LoggerManager.GetLogger(GameEventHandler.class.getName());
 private GameManager gameManager; 
 private static int playerIdCounter = 0;
 private static int playerRegistretionCounter = 0;
 public GameEventHandler(GameManager _gameManager)
 {
  this.gameManager = _gameManager;   
 }
 public int handleEvent(String _jsonRequest,Channel channel)
 {
  JSONObject jsonObject = new JSONObject(_jsonRequest);
  int Event = jsonObject.getInt("event");
  int playerId = -1;
  String userName =  jsonObject.getString("username");          
     switch(Event)
     {
         case Config.LOGIN: 
         {             
          Player newPlayer = setPlayerNewAttributes(userName,channel,Config.LOGIN_DONE);
          setPlayerInPlayersContainer(newPlayer);
          playerId = newPlayer.getId();
          break;
         }
         case Config.PLAY:
         {
          playerId = invokePlayEvent(jsonObject);
          
         }
     }
     return playerId;
  }
 public boolean ResponseDispatcher(int _playerId,String _jsonRequest)
 {
  JSONObject jsonObject = new JSONObject(_jsonRequest);
  int Event = jsonObject.getInt("event");
  boolean bDone = false;
  switch(Event)
     {
         case Config.LOGIN: 
         {             
          bDone = this.gameManager.getGameResponseDispatcher().ResponseDispatcheLoginDone(_playerId);
          break;
         }
         case Config.PLAY:
         {
          bDone = this.gameManager.getGameResponseDispatcher().ResponseDispatchePlayDone(_playerId); 
          break;
         }
     }
  
  return bDone;
 }
 private int invokePlayEvent(JSONObject _jsonObject)
 {
  int activePlayerId  = _jsonObject.getInt("id");
  int currentPlayerID = this.gameManager.getPlayers().get(activePlayerId).getActiveplayerid();
  //validation of turn
  if(activePlayerId==currentPlayerID)
  {
   
   //find out who is the previous player
   int playerInx = getPreviousePlayerIndex(currentPlayerID);
   
   
   String currentPlayerCardId = this.gameManager.getPlayers().get(activePlayerId).getActivecardid();
   //check if the cards deck is active in there are cards in it
   if(this.gameManager.getCardsPlayDeck().size()>0)
   {
    String prevCardId = this.gameManager.getCardsPlayDeck().getFirst();
    //check which card has greater value
    int  prevCardValue = this.gameManager.getCardValueById(prevCardId);
    int  currentCardValue = this.gameManager.getCardValueById(currentPlayerCardId);
    //check if previous card is greater
    if(prevCardValue > currentCardValue)
    {
     
     //set the cards to the winner which is previous player
     this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().addLast(currentPlayerCardId);
     this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().addLast(prevCardId);
     //set as winner
     this.gameManager.getPlayerByIndex(playerInx).setWinner(playerInx);
     this.gameManager.getPlayerByIndex(playerInx).setWinnercards(currentPlayerCardId+"_"+prevCardId);
     this.gameManager.getPlayerByIndex(currentPlayerID).setWinner(playerInx);
     this.gameManager.getPlayerByIndex(currentPlayerID).setWinnercards(currentPlayerCardId+"_"+prevCardId);
     
     String currentCartId = this.gameManager.getPlayers().get(activePlayerId).getPlayerCards().getFirst();
     this.gameManager.getPlayers().get(activePlayerId).setActivecardid(currentCartId);
     
     String cardInDeck = this.gameManager.getCardsPlayDeck().getFirst();
     this.gameManager.getPlayerByIndex(playerInx).setDeckcard(cardInDeck);
     this.gameManager.getCardsPlayDeck().clear(); 
     
    }
    //check if current card is greater
    else if(prevCardValue < currentCardValue)
    {
      
      
     String prevPlayerCardId = this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().getFirst();
     this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().removeFirst();
     this.gameManager.getPlayers().get(currentPlayerID).getPlayerCards().addLast(prevPlayerCardId);
     
     //set as winner
     this.gameManager.getPlayerByIndex(playerInx).setWinner(playerInx);
     this.gameManager.getPlayerByIndex(playerInx).setWinnercards(currentPlayerCardId+"_"+prevPlayerCardId);
     this.gameManager.getPlayerByIndex(currentPlayerID).setWinner(playerInx);
     this.gameManager.getPlayerByIndex(currentPlayerID).setWinnercards(currentPlayerCardId+"_"+prevPlayerCardId);
     
     
     String currentCartId = this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().getFirst();
     this.gameManager.getPlayerByIndex(playerInx).setActivecardid(currentCartId);
     
     String cardInDeck = this.gameManager.getCardsPlayDeck().getFirst();
     this.gameManager.getPlayerByIndex(playerInx).setDeckcard(cardInDeck);
     this.gameManager.getCardsPlayDeck().clear();
     
     
    }
    else if(prevCardValue == currentCardValue)
    {
      
     String PreviousePlayerCards[] = getWarCards(playerInx);
     String currentPlayerCards[] = getWarCards(currentPlayerID); 
     
     int  prevCardValue_4 = this.gameManager.getCardValueById(PreviousePlayerCards[3]);
     int  currentCardValue_4 = this.gameManager.getCardValueById(currentPlayerCards[3]);
     //check who is the winner 
     if(prevCardValue_4 > currentCardValue_4)
     {
      String result = CardsArrayToString(PreviousePlayerCards,currentPlayerCards);
      this.gameManager.getPlayerByIndex(playerInx).setWinner(1);
      this.gameManager.getPlayerByIndex(playerInx).setWinnercards(result);
      String currentCartId = this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().getFirst();
      this.gameManager.getPlayerByIndex(playerInx).setActivecardid(currentCartId);
       
     }
     else if(prevCardValue_4 < currentCardValue_4)
     {
      String result = CardsArrayToString(currentPlayerCards,PreviousePlayerCards);
      this.gameManager.getPlayerByIndex(currentPlayerID).setWinner(1);
      this.gameManager.getPlayerByIndex(currentPlayerID).setWinnercards(result);
      String currentCartId = this.gameManager.getPlayerByIndex(currentPlayerID).getPlayerCards().getFirst();
      this.gameManager.getPlayerByIndex(currentPlayerID).setActivecardid(currentCartId);
     }
     else if(prevCardValue_4 == currentCardValue_4)
     {
      //TODO 
      int test =0;
     }
     this.gameManager.getCardsPlayDeck().clear();
    }
   }
   else
   {
    this.gameManager.getCardsPlayDeck().addFirst(currentPlayerCardId);
    this.gameManager.getPlayers().get(activePlayerId).getPlayerCards().removeFirst();
    String currentCartId = this.gameManager.getPlayers().get(activePlayerId).getPlayerCards().getFirst();
    this.gameManager.getPlayers().get(activePlayerId).setActivecardid(currentCartId);
    
    String cardInDeck = this.gameManager.getCardsPlayDeck().getFirst();
    this.gameManager.getPlayers().get(activePlayerId).setDeckcard(cardInDeck);
           
    
   } 
   
   //Check if there are winners for this game
   int prevPlayerCardsSize = this.gameManager.getPlayerByIndex(playerInx).getPlayerCards().size();
   if(prevPlayerCardsSize==0)
   {
    //game is ended
    this.gameManager.getPlayerByIndex(playerInx).setEndgame(currentPlayerID);
    this.gameManager.getPlayerByIndex(currentPlayerID).setEndgame(currentPlayerID);
    
   }
  }
  else
  {
   activePlayerId =-1;
  }
  return activePlayerId;
 }
 private String CardsArrayToString(String[] cardsPrev,String[] cardsCurrent)
 {
  String result ="";
  for (String s: cardsPrev) {           
         //Do your stuff here
   result+=s;
   result+="_";
     }
  for (String s: cardsCurrent) {           
         //Do your stuff here
   result+=s;
   result+="_";
     }
  result = result.substring(0, result.length()-1);
  return result;
 }
 private String[] getWarCards(int playerID)
 { 
  String prevPlayerCardId_1 = this.gameManager.getPlayerByIndex(playerID).getPlayerCards().getFirst();
  this.gameManager.getPlayerByIndex(playerID).getPlayerCards().removeFirst();
  String prevPlayerCardId_2 = this.gameManager.getPlayerByIndex(playerID).getPlayerCards().getFirst();
  this.gameManager.getPlayerByIndex(playerID).getPlayerCards().removeFirst();
  String prevPlayerCardId_3 = this.gameManager.getPlayerByIndex(playerID).getPlayerCards().getFirst();
  this.gameManager.getPlayerByIndex(playerID).getPlayerCards().removeFirst();
  //the fourth card is to play the war
  String prevPlayerCardId_4 = this.gameManager.getPlayerByIndex(playerID).getPlayerCards().getFirst();
  this.gameManager.getPlayerByIndex(playerID).getPlayerCards().removeFirst();
  return new String[]{prevPlayerCardId_1, prevPlayerCardId_2,prevPlayerCardId_3,prevPlayerCardId_4};
 }
 private int getPreviousePlayerIndex(int _currentPlayerID)
 {
  //find out who is the previous player
  int playerInx = this.gameManager.getPlayerIndexByKey(_currentPlayerID);
  if(playerInx == 0)
  {
   int playerSize = this.gameManager.getPlayers().size();
   playerInx = playerSize-1;
  }
  else
  {
   --playerInx;
  }
  return playerInx;
 }
 private Player setPlayerNewAttributes(String _userName,Channel channel,int nextEvent)
 {
  Player newPlayer = new Player(channel);
  newPlayer.setUserName(_userName);
  int id = GenerateUniqueId(); 
  int count = getPlayerRegistretionCounter();
  newPlayer.setRegistertionNum(count);
  newPlayer.setId(id);
  newPlayer.setEvent(nextEvent);
  setPlayerCards(newPlayer);
  setNewPlayerCardId(newPlayer);
  return newPlayer;
 }
 
 private void setPlayerInPlayersContainer(Player _player)
 {
  this.gameManager.getPlayers().put(_player.getId(), _player);
 }
 private void setPlayerCards(Player _player)
 {
  //this is only good for 2 players 
  int len = this.gameManager.getCardsRandomize().length-1;
  if(_player.getId()==0)
  {   
   for(int i=0;i<(len/2);i++)
   {
    _player.getPlayerCards().push(this.gameManager.getCardsRandomizeByIndex(i));
   }
  }
  else if(_player.getId()==1)
  {
   for(int i=len;i>(len/2);i--)
   {
    _player.getPlayerCards().push(this.gameManager.getCardsRandomizeByIndex(i));
   }
  }
 }
 private void setNewPlayerCardId(Player _player)
 {
  String cardId = _player.getPlayerCards().removeFirst();
  _player.setActivecardid(cardId);
 }
 
 private int GenerateUniqueId()
 {
  int id = this.playerIdCounter;
  this.playerIdCounter++;
  return id;
 }
 
 private int getPlayerRegistretionCounter()
 {
  int count = this.playerRegistretionCounter;
  this.playerRegistretionCounter++;
  return count;
 }
  
}
hshshhs

  1. Lines 18 -24: In this simple card game we have 2 main events first is when player login to the game .
    And the second is when player plays it turn .
    This is where the first case get handled.the client sent the server LOGIN event .
    And the Server do several things:
    A. Creates new Player object
    B. Updates the Player properties
    C. Set the Player object into the Players Container ( in GameManger object ) .
    D. Associate unique id to the player.
  2. Lines 25 -29: The second event as mentioned in the previous section,is when the player makes
    its turn . the client will send the PLAY event .
    when this event triggered in the server , the server will play the game and update the game states for each other players in the game.
  3. Lines 33 - 50: This function will dispatch the response according to the Event that is currently
    get handled by the server.
  4. Lines 56 -59: The invokePlayEvent is big function , it handles the game logic and updates The other players (in the server ) with the new game states .
    First we need to know that the player which we handling is the one that playing now its turn.
    in the JSON structure that is passing between the client and the server
    and also on of the Player Member called : activeplayerid .This member will hold the id of the Player that is currently playing .
  5. Lines 63 -66: Get the previous player. this is turn based game so we need to check what did the previous player done , and to compare between the current Player status and the previous Player Status.
  6. Lines 75 - 92: In our game there could be 2 states in the game:
    First when the middle card deck is empty and one of the players should play its turn
    Second is when there is already a card on the middle deck so the player now should:
    A. Play its card.
    B. Compare between this current player card and the previous player card .

    Here we check if the previous card is greater then current player card .
    Then the following actions are made in the server :
    A.Update the winner player ( previous player ) with the winning cards.
    B.Update the current player about the winner player.
    C.Clear the middle card deck.
  7. Lines 96 -116: Check if the current player is the winner by comparing the cards values
    and doing the same as we did in section 6 above . but this time set current player as winner.
  8. Lines 120 - 145: Check if the cards value are the same , in this case  the War Card game rule
    is that we take 4 cards from each player (lines: 123 -124). and the last (lines 126 -127 ) card
    is the card that we are going to check .
    Again like in sections 6. and 7. the cards values is compared to determine who is the winner .
  9. Lines 146 -150: to keep this code simple as possible the game support only 1 level of WAR state . so if there is again WAR between the cards there is a need to implement the WAR handling again . maybe to separate it to sub-function and call it again  .
    i leave it to you as exercise  ...
  10. Lines 156 - 162: This is the case where the previous player didn't played , so the current player
    is the one that play first. that means putting the card on the middle card deck.
    A.Update the middle card deck with current player card.
    B.Remove the played card from the player card stack.
    C.Update the other player with the card which played.
  11. Lines 168 -173: To determine who is the winner in the game the logic is to check if the previous players card stack is empty , if its empty so the current player is the winner .
    and the game Ended.
    of course we need to update the players and the server that the game is ended .
    by setting the player member : endgame with the winning player id .




    Now we are ready to learn how the response to clients is dispatched . in the next post:

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








     


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


Game Server Source code overview 


TextWebSocketFrameHandler.java


This class has 2 parts.the first part will invoke the WebSocket HandShake mechanism that send confirmation back to the client so the connection will be upgraded to open TCP  socket connection .
The second part will handle the WebSocket Request and decode the incoming text to massage text for the Game Server to handle.


 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
public class TextWebSocketFrameHandler  extends SimpleChannelInboundHandler<TextWebSocketFrame>{
  private final static Logger LOG = LoggerManager.GetLogger(GameServerMain.class.getName());      
     private  GameManager gameManager;
     private  GameEventHandler gameEventHandler;
     public TextWebSocketFrameHandler(GameManager _gameManager,GameEventHandler _gameEventHandler) {
         this.gameManager = _gameManager;
         this.gameEventHandler = _gameEventHandler;
     }
     public TextWebSocketFrameHandler()
     {
          ;
     }
     @Override
     public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
          if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
             ctx.pipeline().remove(HttpRequestHandler.class);
             JSONObject responseJson = new JSONObject();
    responseJson.put("event",Config.HANDSHAKE_COMPLETE_SUCCESS);     
    LOG.severe("HANDSHAKE_COMPLETE "+responseJson.toString());
             ctx.channel().writeAndFlush(new TextWebSocketFrame(responseJson.toString()));
         } else {
             super.userEventTriggered(ctx, evt);
         }
     }
     @Override
     public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
      msg.retain();
      TextWebSocketFrame frame = (TextWebSocketFrame) msg;
      String jsonRequest = frame.text(); 
      LOG.severe("Recived from client :"+jsonRequest); 
         try {          
          int playerId = this.gameEventHandler.handleEvent(jsonRequest,ctx.channel());
          if(playerId !=-1)
          {
           this.gameEventHandler.ResponseDispatcher(playerId,jsonRequest);
           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());
                  String PlayerMassage = playerIt.getPlayerJson().toString();
                  responseToClient(playerIt,PlayerMassage);
                  LOG.severe("Sending to client id:"+String.valueOf(playerIt.getId())+" name:"+playerIt.getUserName()+" json:" +PlayerMassage);
              }
          }
          else
          {
           LOG.severe("Sending to clients Failed playerId is -1");
          }
             
         } finally {
             frame.release();
         }
     }
     private void responseToClient(Player _player,String responseJson)
  {
   _player.getChannel().writeAndFlush(new TextWebSocketFrame(responseJson));
  }
}


  1. Lines 14 - 23 : this is the WebSocket Handshak , remember the WebSocket dialog sequence between the client and the server performed in this order :
    A .the client send HTTP request to see if the server support WebSocket
    and inform the server the future connection type is WebSocket ,
    B. if confirmed by the server  the connection is UPGRADED  to TCP open connection between the client and the server , 
  2. Lines 28 - 29: the incoming request from the client as string.
  3. Line 32: send the string to Game Event Handler , which is a object that handle events and according to the event type it constructing and sending the response's back to the clients.
  4. Lines 35 - 44 : handle the response and send them back to the clients which are connected .
  5. Lines 55 - 58 : write the actual WebSocket Frame to the open connection channel.
    WebSocket Frame is encapsolated Netty object that handle WebSocket data .
    this is helper function which using the player Channel member to send the data .
GameManager.java

This class Is the "glue" that connects all the game logic It holds the Cards Map.
And the players map container and the response dispatcher.
and it should also have the Game event Handler which will be done in the next version .


  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
public class GameManager {
 private final static Logger LOG = LoggerManager.GetLogger(GameServerMain.class.getName());
 private final String[] cardsNotRandomBase = {"c1","c2","c3","c4","c5","c6","c7","c8","c9","c10","c11","c12","c13","c14","c15","c16","c17","c18","c19","c20","c21","c22","c23","c24","c25","c26","c27","c28","c29","c30","c31","c32","c33","c34","c35","c36","c37","c38","c39","c40","c41","c42","c43","c44","c45","c46","c47","c48","c49","c50","c51","c52","c53"};
 private final String[] cardsRandomize;
 public final HashMap<String, String> cardsMap;
 private volatile Map<Integer, Player> players; 
 private LinkedList<String> cardsPlayDeck;
 private final GameResponseDispatcher gameResponseDispatcher;
 public GameManager( )
 {  
  cardsMap =  new HashMap<String, String>();
  setCardsHash();
  final ResourceBundle configurationBundle = ResourceBundle.getBundle("configuration");
  //hashmap with predictable iteration order
  players = new LinkedHashMap<Integer, Player>();
  cardsPlayDeck = new LinkedList<String>();   
  this.cardsRandomize = Arrays.copyOf(cardsNotRandomBase , cardsNotRandomBase .length);
  Collections.shuffle(Arrays.asList(cardsRandomize));
  gameResponseDispatcher = new GameResponseDispatcher(this);
 }
 public GameResponseDispatcher getGameResponseDispatcher() {
  return gameResponseDispatcher;
 }

 public HashMap<String, String> getCardsMap() {
  return cardsMap;
 }
 public LinkedList<String> getCardsPlayDeck() {
  return cardsPlayDeck;
 }

 public void setCardsPlayDeck(LinkedList<String> cardsPlayDeck) {
  this.cardsPlayDeck = cardsPlayDeck;
 }
 public int getPlayerIndexByKey(int playerKey)
 {
  int pos = new ArrayList<Integer>(players.keySet()).indexOf(playerKey);
  return pos;
 }
 public Player getPlayerByIndex(int inx)
 {
  List<Player> l = new ArrayList<Player>(players.values());
  Player p = l.get(inx);
  return p;
 }
 public Map<Integer, Player> getPlayers() {
  return players;
 }
 public void setPlayers(Map<Integer, Player> players) {
  this.players = players;
 }
 public String[] getCardsRandomize() {
  return cardsRandomize;
 }
 public String getCardsRandomizeByIndex(int i) {
  return cardsRandomize[i];
 }
 public int getCardValueById(String cardId)
 {
  String card = this.cardsMap.get(cardId);
  String rightPart= card.split("_")[1];
  String number = rightPart.split(".png")[0];
  int cardIntVal = Integer.valueOf(number);
  return cardIntVal;
 }
 public int getNextActivePlayer(int currentActivePlayer)
 {
  int currentPlyInx = getPlayerIndexByKey(currentActivePlayer);
  int activePlayer = 0;
  if(currentPlyInx < (players.size()-1))
  {
   ++activePlayer;
  }
  return activePlayer;
 }
 private void setCardsHash()
 {
  cardsMap.put("c1","cardClubsA_20.png");
  cardsMap.put("c2","cardClubsJ_17.png");
  cardsMap.put("c3","cardClubsK_19.png");
  cardsMap.put("c4","cardClubsQ_18.png");
  cardsMap.put("c5","cardDiamonds10_10.png");
  cardsMap.put("c6","cardDiamonds2_2.png");
  cardsMap.put("c7","cardDiamonds3_3.png");
  cardsMap.put("c8","cardDiamonds4_4.png");
  cardsMap.put("c9","cardDiamonds5_5.png");
  cardsMap.put("c10","cardDiamonds6_6.png");
  cardsMap.put("c11","cardDiamonds7_7.png");
  cardsMap.put("c12","cardDiamonds8_8.png");
  cardsMap.put("c13","cardDiamonds9_9.png");
  cardsMap.put("c14","cardDiamondsA_20.png");
  cardsMap.put("c15","cardDiamondsJ_17.png");
  cardsMap.put("c16","cardDiamondsK_19.png");
  cardsMap.put("c17","cardDiamondsQ_18.png");
  cardsMap.put("c18","cardHearts10_10.png");
  cardsMap.put("c19","cardHearts2_2.png");
  cardsMap.put("c20","cardHearts3_3.png");
  cardsMap.put("c21","cardHearts4_4.png");
  cardsMap.put("c22","cardHearts5_5.png");
  cardsMap.put("c23","cardHearts6_6.png");
  cardsMap.put("c24","cardHearts7_7.png");
  cardsMap.put("c25","cardHearts8_8.png");
  cardsMap.put("c26","cardHearts9_9.png");
  cardsMap.put("c27","cardHeartsA_20.png");
  cardsMap.put("c28","cardHeartsJ_17.png");
  cardsMap.put("c29","cardHeartsK_19.png");
  cardsMap.put("c30","cardHeartsQ.png");
  cardsMap.put("c31","cardJoker_21.png");
  cardsMap.put("c32","cardSpades10_10.png");
  cardsMap.put("c33","cardSpades2_2.png");
  cardsMap.put("c34","cardSpades3_3.png");
  cardsMap.put("c35","cardSpades4_4.png");
  cardsMap.put("c36","cardSpades5_5.png");
  cardsMap.put("c37","cardSpades6_6.png");
  cardsMap.put("c38","cardSpades7_7.png");
  cardsMap.put("c39","cardSpades8_8.png");
  cardsMap.put("c40","cardSpades9_9.png");
  cardsMap.put("c41","cardSpadesA_20.png");
  cardsMap.put("c42","cardSpadesJ_17.png");
  cardsMap.put("c43","cardSpadesK_19.png");
  cardsMap.put("c44","cardSpadesQ_18.png");
  cardsMap.put("c45","cardClubs10_10.png");
  cardsMap.put("c46","cardClubs2_2.png");
  cardsMap.put("c47","cardClubs3_3.png");
  cardsMap.put("c48","cardClubs4_4.png");
  cardsMap.put("c49","cardClubs5_5.png");
  cardsMap.put("c50","cardClubs6_6.png");
  cardsMap.put("c51","cardClubs7_7.png");
  cardsMap.put("c52","cardClubs8_8.png");
  cardsMap.put("c53","cardClubs9_9.png");
 }
 }


  1. Line 3 : array of the cards. those are the key names of the game cards are sorted by order. 
  2. Line 5 : the map which maps between the cards names and the cards key names.
  3. Line 6 : the players container from type Ordered Map . the key set by the order the player login.
  4. Line 7: is the deck of cards in between the players,where each player show its cards in its turn.
  5. Lines 17-18:shuffle the cards .
  6. The rest are just simple small helper functions and getters and setters that are self explanatory.
We Are ready now to continue with the Game Server Code overview .
Next we going to analize the game logic continue to the next post :

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

Friday, January 22, 2016

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


Game Server Source code overview 
In this post i will try to explain how develop basic Game Server that is using WebSockets as its networking protocol .To communicate between the server and the clients.In this tutorial to keep things very simple i don't use any third party java library's .

I only will be using Netty self contained framework that comes in 1 jar .

Super  crash course on WebSockets:

  • WebSockets protocol is the most close thing to common standard between browsers , mobiles
    desktops  clients .which enables continuous open connection over TCP
  • WebSockets have build in mechanism which checks if the server supports WebSockets using
    HTTP request 
    handshake first when connecting to server
  • WebSockets is event driven , it means both sides the client and server will know whats going on . and will be notified by callbacks.
Super  crash course on Java Netty Server:
  • Netty is asynchronous TCPIP server framework developed in JAVA. asynchronous  means it is using very few threads to handle incoming traffic , and it never waits . so it is easy on the CPU
  • Netty have build in WebSocket support , no need to add third party library.
  • Netty is relatively easy to debug using powerful java eclipse IDE , 
  • Netty have proven positive record in the industry as one of the lead framework when it comes
    to handle massive traffic.
Client Server "Architecture" 

How does our simple client server will work is drawn in this simple diagram :

GameServerMain.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
 public class GameServerMain {
 private final static Logger LOG = LoggerManager.GetLogger(GameServerMain.class.getName());
 public static void main(String[] args) {
   
  final ResourceBundle configurationBundle = ResourceBundle.getBundle("configuration");
  int port = Integer.valueOf(configurationBundle.getString("port"));
  WebSocketServer pWebSocketServer = new WebSocketServer(); 
  pWebSocketServer.start(port);
  LOG.info("server started");
  
 }

}
  1. Line 5: Load configuration from configuration.propeties file 
  2. Line 6: get the port to for the server to listen.
  3. Line 7 - 9: start the server . 
WebSocketServer.java 

This class is standard initialization sequence of Netty server .
It will initialize our Game Logic class :GameManager
It will set Netty to be A sync when it comes to  handling incoming requests ,
Called also NIO in java world . Non-blocking I/O.


 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
public class WebSocketServer {
  private final ChannelGroup channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
     private final EventLoopGroup group = new NioEventLoopGroup();    
     private final GameManager gameManager = new GameManager(); 
     private Channel channel;
     public ChannelFuture start(int address) {
         ServerBootstrap bootstrap  = new ServerBootstrap();
         bootstrap.group(group)
                 .channel(NioServerSocketChannel.class)
                 .childHandler(createInitializer(gameManager));
         ChannelFuture future = bootstrap.bind(new InetSocketAddress(address));
         future.syncUninterruptibly();
         channel = future.channel();
         return future;
     }

     protected ChannelInitializer<Channel> createInitializer(GameManager _gameManager) {
        return new GameServerInitializer(gameManager);
     }

     public void destroy() {
         if (channel != null) {
             channel.close();
         }
         channelGroup.close();
         group.shutdownGracefully();
     }
}

  1. Lines 3: Init Netty as Non-blocking I/O. server.
  2. Line 4: create one of the game logic objects ( more on this in Part 3 ) .
  3. Lines 6 - 14: initialization sequence of Netty.
  4. Lines 17 -18: set Netty server channel handlers.
    Channel is where the communication between the server and client pass through.
    Netty present the idea of Handlers . that means the raw data that pass through
    Will be handled on its way to the server when down , there are handlers that handler
    The data in the way back to the client .
    This is very wide subject . and it way out the scope on this tutorial .
    You can start to learn about it here :
    http://netty.io/4.0/api/io/netty/channel/ChannelPipeline.html

GameServerInitializer.java

This class is where the Channel handlers are set up , someone them are Netty build in handlers
And some of them are Extended Class's . Netty gives us the option to manipulate the data .


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class GameServerInitializer extends ChannelInitializer<Channel> {
 private final GameManager gameManager; 
    public GameServerInitializer(GameManager _gameManager) {
     this.gameManager = _gameManager;
    }
    @Override
    protected void initChannel(Channel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast("HttpServerCodec",new HttpServerCodec());
        pipeline.addLast("HttpObjectAggregator",new HttpObjectAggregator(64 * 1024));
        pipeline.addLast("WebSocketServerProtocolHandler",new WebSocketServerProtocolHandler("/ws"));        
        pipeline.addLast("TextWebSocketFrameHandler",new TextWebSocketFrameHandler(gameManager,new GameEventHandler(gameManager)));
     }
}


  1. Lines 4: we pass the GameManger object throw all the initiations of the objects .
    We need the GameManger to be Central point for our game logic. it will keep all the game states  and the players . 
  2. Lines 8 -14 :all the channel handlers that will handle the WebSocket requests and responses.
    The setup is done by the entering the handlers into Map ,
    Handlers which are extending SimpleChannelInboundHandler  are Usually handle Inbound data
    and those Handlers we are going to use . and extend .
  3. Line 10: HttpServerCodec is Netty builtin handler  to handle HTTP requests to decode and encode  them back.
  4. Line 11: HttpObjectAggregator is Netty builtin handler its job  to aggregate the fractions of
    HTTP request and only pass it to the next handler when the FULL request is arrived from the client .
  5. Line 12: WebSocketServerProtocolHandler  is Handler which is extended to handle the WebSocket http handshake and approval if to pass the incoming WebSocket request to the next handler.
  6. Line 13: TextWebSocketFrameHandler is handler which is extending Netty SimpleChannelInboundHandler class.
    this is where the actual Text from the client is accepted and manipolated by the server to handle game states  more on this when we analyze the  TextWebSocketFrameHandler .java source code.
      

We are ready to dive deeper into the game server logic in the next post :

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




Wednesday, January 20, 2016

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


The purpose of this tutorial series is to learn the very basics real time Game server and client
And one of many principles behind multiplayer game.
Especially for beginners with no server side networking experience .
Although the tutorial is very basic and simple what you going to learn can be used as foundation to more complex games.
Using those open source and free tools and technologies:

We are going to learn about those subjects to understand their principles.
To keep things very simple the game will be 2 player War Card game in real time .
As this is learning tutorial the server will be running from Eclipse IDE , and the Client (aka the game) will be running from Web or Desktop app or iOS Device.
One player can start the game on the web and the other on the desktop or iOS device or vise versa .
You can also watch some development videos i done when building this tutorial .
They can be watched here :
https://www.youtube.com/playlist?list=PLguC363FekVf3niPruOJ_QCh2HZy17F0r

Few words about MMO games although this game is very basic . the principles that presented here
are quite the same also in big scale MMO games , one of them is
That the game logic that we see on the client is actually played also on the server .
This is for prevent cheating on the client and abuse the game .

Here is simulation of 2 players playing the game in this case 2 browser windows are opened




Lts start with the Eclipse IDE and the Netty Server setup .

Java Netty Server Eclipse Setup 

  1. Create new JS project with cocos2d-x with the name WarCardJS
    you can refer to my tutorial on how to create new project here :
    Snake Game Using Cocos2d-x HTML5 - PART 1
  2. Get the source code from the GitHub project :
    https://github.com/meiry/multiplayer_cocos2dx-js_using_netty_websockets
    clone it to local directory
    to your local project directory it should look like this :

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    d:\dev\cpp\2d\GameDevCraft\fromgit>git clone https://github.com/meiry/multiplayer_cocos2dx-js_using_netty_websockets.git
    Cloning into 'multiplayer_cocos2dx-js_using_netty_websockets'...
    remote: Counting objects: 108, done.
    remote: Compressing objects: 100% (96/96), done.
    remote: Total 108 (delta 9), reused 93 (delta 5), pack-reuRsed 0eceiving objects:  79% (86/108), 3.55 MiB | 43 KiB/s
    Receiving objects: 100% (108/108), 3.56 MiB | 45 KiB/s, done.
    Resolving deltas: 100% (9/9), done.
    
     
    d:\dev\cpp\2d\GameDevCraft\fromgit>cd multiplayer_cocos2dx-js_using_netty_websockets
    
     
    
    d:\dev\cpp\2d\GameDevCraft\fromgit\multiplayer_cocos2dx-js_using_netty_websockets>ls -l
    total 1
    drwxr-xr-x 3 meirmaya Administrators   0 Jan 20 22:18 Client
    -rw-r--r-- 1 meirmaya Administrators 214 Jan 20 22:18 README.md
    drwxr-xr-x 3 meirmaya Administrators   0 Jan 20 22:18 Server
    

  3. Download the latest Eclipse IDE at the time of writing this tutorial the Eclipse version called
    Mars. and install it you will need java 1.7+ for this .
    https://eclipse.org/downloads/
  4. Start Eclipse and create new workspace , this is the directory which Eclipse manage its projects.

  5. After eclipse opened , To Import our Java project into eclipse go to :
     go to File -> Import

  6. The Import window will open , select General -> existing projects into workspace

  7. In the Import Project window, browse to the where you clone the project from GitHub and then select:
    Server -> CardWarNettyServer

  8. Select it and click Ok , the project is ready to be imported into Eclipse .
    when it is done loading press the : Finish button .

  9. Now that we have the project loaded into Eclipse we are going to configure it.
    This means in java and eclipse world ,
    A. Configure our Game server class path. and the Netty library
    B. Configure our JRE (java run time ).
    C. Configure the configuration of the server

    Right click on the project name on the left ( the project source tree )
    on the window that opened click on properties .

  10. After clicking it, new window will open , click on the left on : "Java Build Path"
    And on the right click and select the : "Libraries" tab.
    in this window are listed all the:
    A. Game server Libraries which are needed for the server to work.
    B. Game server directories in our case the configuration directories.
  11. The default project has my path settings of the Netty library we need to change it.
    Click on :"Add Jars" button , window will be open , browse to the "lib" directory.
    And select :
    netty-all-4.0.33.Final.jar
    And if you like to see the netty source select also :
    netty-all-4.0.33.Final-sources.jar
    Click Ok .

  12. Now do the same thing with the configuration directory , but this time click on the
    "Add Folder Directory" button on the left , pop up window will open .
    Select the "conf" directory with a check box . and click Ok . to close the popup
    And click Ok on the Properties Window.


  13. The first part is configured , the second part is the Debug Configuration .
    On the Left Source tree select the file "GameServerMain.java"
    Right click -> Debug As -> Debug Configuration -> left click .

  14. The "Create , manage . and run configuration" window will open.
    On the left look for "Java Application" double click it and a new node will created
    This new sub node represents the configuration for this project
    Give it some meaningful name in my case it is : GameServerMain.

  15. On the right go to the :"Classpath" tab.
    On the select the "User Entries" ,
    then select on the left the "Advanced..." button
    In the small window that is opens select the "Add Folder" radio button .
    From the popup window select the project "conf" directory.

  16. We are ready to start the server in Debug Mode .
    Press Ok on all and in the final window click Debug .
    It will compile the source code and then excute the server .
  17. If every thing went right you should see in the Eclipse Console in red:

    Jan 22, 2016 1:53:27 PM com.server.GameServerMain main
    INFO: server started

  18. If you don't see it mybe you need to enable the Console window
    In Eclipse go to : Window -> Show view -> Console

  19. Or maybe port is busy , to change port go to :
    on the left source tree to :
    in the conf directory : double click on configuration.properties
    there you will see: port=8888
    try to change it to something else .


    We are ready now to review the Game Server source code And How it works
    In the next post:

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