1 module netorcai.message; 2 3 import std.json; 4 import std.exception; 5 6 import netorcai.json_util; 7 import netorcai.metaprotocol_version; 8 9 /// Stores information about one player 10 struct PlayerInfo 11 { 12 int playerID; /// The player unique identifier (in [0..nbPlayers[) 13 string nickname; /// The player nickname 14 string remoteAddress; /// The player socket remote address 15 bool isConnected; /// Whether the player is currently connected or not 16 } 17 18 /// Parses a player information (in GAME_STARTS and GAME_ENDS messages) 19 PlayerInfo parsePlayerInfo(JSONValue o) 20 { 21 PlayerInfo info; 22 info.playerID = o["player_id"].getInt; 23 info.nickname = o["nickname"].str; 24 info.remoteAddress = o["remote_address"].str; 25 info.isConnected = o["is_connected"].getBool; 26 27 return info; 28 } 29 unittest 30 { 31 immutable string s = `{ 32 "player_id": 0, 33 "nickname": "jugador", 34 "remote_address": "127.0.0.1:59840", 35 "is_connected": true 36 }`; 37 38 const PlayerInfo pinfo = parseJSON(s).parsePlayerInfo; 39 assert(pinfo.playerID == 0); 40 assert(pinfo.nickname == "jugador"); 41 assert(pinfo.remoteAddress == "127.0.0.1:59840"); 42 assert(pinfo.isConnected == true); 43 } 44 45 /// Parses several player information (in GAME_STARTS and GAME_ENDS messages) 46 PlayerInfo[] parsePlayersInfo(JSONValue[] array) 47 { 48 PlayerInfo[] infos; 49 infos.length = array.length; 50 51 foreach (i, o; array) 52 { 53 infos[i] = o.parsePlayerInfo; 54 } 55 56 return infos; 57 } 58 unittest 59 { 60 immutable string s = `[ 61 { 62 "player_id": 0, 63 "nickname": "jugador", 64 "remote_address": "127.0.0.1:59840", 65 "is_connected": true 66 }, 67 { 68 "player_id": 1, 69 "nickname": "bot", 70 "remote_address": "127.0.0.1:59842", 71 "is_connected": false 72 } 73 ]`; 74 75 const PlayerInfo[] pinfos = parseJSON(s).array.parsePlayersInfo; 76 assert(pinfos.length == 2); 77 78 assert(pinfos[0].playerID == 0); 79 assert(pinfos[0].nickname == "jugador"); 80 assert(pinfos[0].remoteAddress == "127.0.0.1:59840"); 81 assert(pinfos[0].isConnected == true); 82 83 assert(pinfos[1].playerID == 1); 84 assert(pinfos[1].nickname == "bot"); 85 assert(pinfos[1].remoteAddress == "127.0.0.1:59842"); 86 assert(pinfos[1].isConnected == false); 87 } 88 89 /// Content of a LOGIN_ACK metaprotocol message 90 struct LoginAckMessage 91 { 92 string metaprotocolVersion; /// netorcai's metaprotocol version 93 } 94 95 /// Parses a LOGIN_ACK metaprotocol message 96 LoginAckMessage parseLoginAckMessage(JSONValue o) 97 { 98 LoginAckMessage m; 99 100 m.metaprotocolVersion = o["metaprotocol_version"].str; 101 if (m.metaprotocolVersion != metaprotocolVersion) 102 { 103 import std.stdio; 104 writefln("Warning: netorcai uses version '%s' while netorcai-client-d uses '%s'", 105 m.metaprotocolVersion, metaprotocolVersion); 106 } 107 108 return m; 109 } 110 111 /// Content of a GAME_STARTS metaprotocol message 112 struct GameStartsMessage 113 { 114 int playerID; /// Caller's player identifier. players: [0..nbPlayers+nbSpecialPlayers[. visu: -1 115 int nbPlayers; /// Number of players in the game 116 int nbSpecialPlayers; /// Number of special players in the game 117 int nbTurnsMax; /// Maximum number of turns. Game can finish before it 118 double msBeforeFirstTurn; /// Time before the first TURN is sent (in ms) 119 double msBetweenTurns; /// Time between two consecutive TURNs (in ms) 120 PlayerInfo[] playersInfo; /// (only for visus) Information about the players 121 JSONValue initialGameState; /// Game-dependent object. 122 } 123 124 /// Parses a GAME_STARTS metaprotocol message 125 GameStartsMessage parseGameStartsMessage(JSONValue o) 126 { 127 GameStartsMessage m; 128 129 m.playerID = o["player_id"].getInt; 130 m.nbPlayers = o["nb_players"].getInt; 131 m.nbSpecialPlayers = o["nb_special_players"].getInt; 132 m.nbTurnsMax = o["nb_turns_max"].getInt; 133 m.msBeforeFirstTurn = o["milliseconds_before_first_turn"].getDouble; 134 m.msBetweenTurns = o["milliseconds_between_turns"].getDouble; 135 m.initialGameState = o["initial_game_state"].object; 136 m.playersInfo = o["players_info"].array.parsePlayersInfo; 137 138 return m; 139 } 140 unittest 141 { 142 immutable string s = `{ 143 "message_type": "GAME_STARTS", 144 "player_id": 0, 145 "players_info": [ 146 { 147 "player_id": 0, 148 "nickname": "jugador", 149 "remote_address": "127.0.0.1:59840", 150 "is_connected": true 151 } 152 ], 153 "nb_players": 4, 154 "nb_special_players": 0, 155 "nb_turns_max": 100, 156 "milliseconds_before_first_turn": 1000, 157 "milliseconds_between_turns": 1000, 158 "initial_game_state": {} 159 }`; 160 161 const GameStartsMessage m = parseJSON(s).parseGameStartsMessage; 162 assert(m.playerID == 0); 163 assert(m.playersInfo.length == 1); 164 assert(m.playersInfo[0] == `{"player_id":0,"nickname":"jugador", 165 "remote_address":"127.0.0.1:59840", 166 "is_connected":true}`.parseJSON.parsePlayerInfo); 167 assert(m.nbPlayers == 4); 168 assert(m.nbSpecialPlayers == 0); 169 assert(m.nbTurnsMax == 100); 170 assert(m.msBeforeFirstTurn == 1000); 171 assert(m.msBetweenTurns == 1000); 172 assert(m.initialGameState.object.length == 0); 173 } 174 175 /// Content of a GAME_ENDS metaprotocol message 176 struct GameEndsMessage 177 { 178 int winnerPlayerID; /// Unique identifier of the player that won the game. Or -1. 179 JSONValue gameState; /// Game-dependent object. 180 } 181 182 /// Parses a GAME_ENDS metaprotocol message 183 GameEndsMessage parseGameEndsMessage(JSONValue o) 184 { 185 GameEndsMessage m; 186 m.winnerPlayerID = o["winner_player_id"].getInt; 187 m.gameState = o["game_state"].object; 188 189 return m; 190 } 191 unittest 192 { 193 immutable string s = `{ 194 "message_type": "GAME_ENDS", 195 "winner_player_id": 0, 196 "game_state": {} 197 }`; 198 199 const GameEndsMessage m = s.parseJSON.parseGameEndsMessage; 200 assert(m.winnerPlayerID == 0); 201 assert(m.gameState.object.length == 0); 202 } 203 204 /// Content of a TURN metaprotocol message 205 struct TurnMessage 206 { 207 int turnNumber; /// In [0..nbTurnsMax[ 208 PlayerInfo[] playersInfo; /// (only for visus) Information about the players 209 JSONValue gameState; /// Game-dependent object. 210 } 211 212 /// Parses a TURN metaprotocol message 213 TurnMessage parseTurnMessage(JSONValue o) 214 { 215 TurnMessage m; 216 m.turnNumber = o["turn_number"].getInt; 217 m.playersInfo = o["players_info"].array.parsePlayersInfo; 218 m.gameState = o["game_state"].object; 219 220 return m; 221 } 222 unittest 223 { 224 immutable string s = `{ 225 "message_type": "TURN", 226 "turn_number": 0, 227 "game_state": {}, 228 "players_info": [ 229 { 230 "player_id": 0, 231 "nickname": "jugador", 232 "remote_address": "127.0.0.1:59840", 233 "is_connected": true 234 } 235 ] 236 }`; 237 238 const TurnMessage m = s.parseJSON.parseTurnMessage; 239 assert(m.turnNumber == 0); 240 assert(m.gameState.object.length == 0); 241 assert(m.playersInfo == `[{"player_id":0,"nickname":"jugador", 242 "remote_address":"127.0.0.1:59840", 243 "is_connected":true}]`.parseJSON.array.parsePlayersInfo); 244 } 245 246 /// Content of a DO_INIT metaprotocol message 247 struct DoInitMessage 248 { 249 int nbPlayers; /// The number of players of the game 250 int nbSpecialPlayers; /// Number of special players in the game 251 int nbTurnsMax; /// The maximum number of turns of the game 252 } 253 254 /// Parses a DO_INIT metaprotocol message 255 DoInitMessage parseDoInitMessage(JSONValue o) 256 { 257 DoInitMessage m; 258 m.nbPlayers = o["nb_players"].getInt; 259 m.nbSpecialPlayers = o["nb_special_players"].getInt; 260 m.nbTurnsMax = o["nb_turns_max"].getInt; 261 262 return m; 263 } 264 unittest 265 { 266 immutable string s = `{ 267 "message_type": "DO_INIT", 268 "nb_players": 4, 269 "nb_special_players": 0, 270 "nb_turns_max": 100 271 }`; 272 273 immutable DoInitMessage m = s.parseJSON.parseDoInitMessage; 274 assert(m.nbPlayers == 4); 275 assert(m.nbSpecialPlayers == 0); 276 assert(m.nbTurnsMax == 100); 277 } 278 279 /// Convenient struct for the player actions of a DO_TURN metaprotocol message 280 struct PlayerActions 281 { 282 int playerID; /// The identifier of the player that issued the actions 283 int turnNumber; /// The turn number the actions come from 284 JSONValue actions; /// The actions themselves 285 } 286 287 /// Parses the playerActions field of a DO_TURN metaprotocol message 288 PlayerActions parsePlayerActions(JSONValue o) 289 { 290 PlayerActions pa; 291 pa.playerID = o["player_id"].getInt; 292 pa.turnNumber = o["turn_number"].getInt; 293 pa.actions = o["actions"].array; 294 295 return pa; 296 } 297 unittest 298 { 299 immutable string s = `{ 300 "player_id": 2, 301 "turn_number": 4, 302 "actions": [] 303 }`; 304 305 immutable PlayerActions pa = s.parseJSON.parsePlayerActions; 306 assert(pa.playerID == 2); 307 assert(pa.turnNumber == 4); 308 assert(pa.actions.array.length == 0); 309 } 310 311 /// Content of a DO_TURN metaprotocol message 312 struct DoTurnMessage 313 { 314 PlayerActions[] playerActions; /// The ordered list of player actions 315 } 316 317 /// Parses a DO_TURN metaprotocol message 318 DoTurnMessage parseDoTurnMessage(JSONValue v) 319 { 320 DoTurnMessage m; 321 322 auto actions = v["player_actions"].array; 323 m.playerActions.length = actions.length; 324 325 foreach (i, o; actions) 326 { 327 m.playerActions[i] = o.parsePlayerActions; 328 } 329 330 return m; 331 } 332 unittest 333 { 334 immutable string s = `{ 335 "message_type": "DO_TURN", 336 "player_actions": [ 337 { 338 "player_id": 1, 339 "turn_number": 2, 340 "actions": [] 341 }, 342 { 343 "player_id": 0, 344 "turn_number": 3, 345 "actions": [ 4 ] 346 } 347 ] 348 }`; 349 350 DoTurnMessage m = s.parseJSON.parseDoTurnMessage; 351 assert(m.playerActions.length == 2); 352 assert(m.playerActions[0].playerID == 1); 353 assert(m.playerActions[0].turnNumber == 2); 354 assert(m.playerActions[0].actions.array.length == 0); 355 assert(m.playerActions[1].playerID == 0); 356 assert(m.playerActions[1].turnNumber == 3); 357 assert(m.playerActions[1].actions.array.length == 1); 358 assert(m.playerActions[1].actions.array[0].getInt == 4); 359 }