├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── pom.xml ├── src ├── main │ ├── README.txt │ ├── assembly │ │ └── mod.xml │ ├── java │ │ └── ch │ │ │ └── erni │ │ │ └── beer │ │ │ └── vertx │ │ │ ├── ConfigVerticle.java │ │ │ ├── Configuration.java │ │ │ ├── GameLobbyVerticle.java │ │ │ ├── GameVerticle.java │ │ │ ├── HTTPServerVerticle.java │ │ │ ├── Point.java │ │ │ ├── PongVerticle.java │ │ │ ├── dto │ │ │ ├── AsyncHandlerDTO.java │ │ │ ├── EntityCreatedDTO.java │ │ │ ├── ErrorDTO.java │ │ │ ├── HandleAsyncDTO.java │ │ │ ├── PongDTO.java │ │ │ ├── game │ │ │ │ ├── GameCommandDTO.java │ │ │ │ ├── GameDTO.java │ │ │ │ └── GameStateDTO.java │ │ │ └── lobby │ │ │ │ ├── AddGameDTO.java │ │ │ │ ├── AddPlayerDTO.java │ │ │ │ ├── AvailableGameDTO.java │ │ │ │ ├── GameEndedDTO.java │ │ │ │ ├── JoinGameDTO.java │ │ │ │ ├── ListPlayersDTO.java │ │ │ │ └── PlayerDisconnectDTO.java │ │ │ └── entity │ │ │ ├── Entity.java │ │ │ ├── Game.java │ │ │ └── Player.java │ ├── platform_lib │ │ └── README.txt │ └── resources │ │ ├── config.json │ │ ├── mod.json │ │ ├── platform_lib │ │ └── README.txt │ │ └── www │ │ ├── 404.html │ │ ├── app.js │ │ ├── images │ │ ├── black.png │ │ └── green.png │ │ ├── index.html │ │ ├── quintus-all.js │ │ └── vertxbus.js └── test │ └── java │ └── ch │ └── erni │ └── beer │ └── vertx │ └── unit │ └── README.txt ├── vertx.pptx └── vertx_classpath.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | *.ipr 4 | *.iws 5 | .classpath 6 | .project 7 | .settings/ 8 | 9 | .gradle/ 10 | build/ 11 | target/ 12 | 13 | mods/ 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: .maven/bin/mvn vertx:runmod -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | codingbeer-vertx 2 | ================ 3 | 4 | A real-time web minigame demonstrating the use of server-side vert.x (java 8) and client-side Javascript. Used as an example at ERNI Coding beer session. 5 | 6 | The server side consists of more Verticles. Main verticle is ConfigVerticle which initializes other verticles with default config values 7 | 8 | Client side is made with Javascript http://www.html5quintus.com/#demo HTML5 engine. 9 | 10 | NOTICE: This is by no means a complete fool-proof implementation of the game. Some topics that would require more attention in production-quality software: 11 | - Data efficiency: every frame is calculated on the server side and sent as a real-time update (every ~20ms). This may cause unnecessary lags on slower/high ping connections. 12 | More efficient way would be only to send change-state messages (ball has hit a wall, player has hit/missed the ball etc...) and let client-side javascript do the simple movement interpolation 13 | - Security: Current security implementation relies on clients not knowing other players' or games' GUID. GUID is supposed to be unique, so we assume there are no collisions (this is ok), but 14 | if someone finds out GUID of some other player, he could influence his games and act on his behalf. In a real world, authentication/authorization/"session" mechanisms would be needed 15 | - and some more I can't currently think of... :o) 16 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | ch.erni.beer.vertx 7 | pong 8 | jar 9 | 1.0-SNAPSHOT 10 | Project - pong 11 | http://maven.apache.org 12 | 13 | 14 | org.sonatype.oss 15 | oss-parent 16 | 7 17 | 18 | 19 | 20 | UTF-8 21 | 22 | 25 | true 26 | 27 | 29 | false 30 | 31 | 32 | ${project.groupId}~${project.artifactId}~${project.version} 33 | 34 | 36 | target/mods 37 | 38 | 39 | 2.1.3 40 | 2.0.3-final 41 | 4.11 42 | 43 | 44 | 3.0 45 | 2.6 46 | 2.5 47 | 2.0.9-final 48 | 2.14 49 | 2.14 50 | 2.14 51 | 2.9 52 | 2.7 53 | 54 | 55 | 56 | 57 | sonatype-nexus-snapshots 58 | https://oss.sonatype.org/content/repositories/snapshots 59 | 60 | 61 | 62 | 63 | 64 | 65 | io.vertx 66 | vertx-core 67 | ${vertx.version} 68 | provided 69 | 70 | 71 | io.vertx 72 | vertx-platform 73 | ${vertx.version} 74 | provided 75 | 76 | 77 | io.vertx 78 | vertx-hazelcast 79 | ${vertx.version} 80 | provided 81 | 82 | 83 | org.apache.commons 84 | commons-lang3 85 | 3.1 86 | 87 | 88 | io.vertx 89 | mod-rxvertx 90 | 1.0.0-beta4 91 | 92 | 93 | 94 | 95 | 96 | 97 | junit 98 | junit 99 | 4.11 100 | test 101 | 102 | 103 | io.vertx 104 | testtools 105 | ${vertx.testtools.version} 106 | test 107 | 108 | 109 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | io.vertx 127 | vertx-maven-plugin 128 | ${maven.vertx.plugin.version} 129 | 137 | 138 | src/main/resources/config.json 139 | 140 | 141 | 142 | 143 | io.vertx 144 | vertx-platform 145 | ${vertx.version} 146 | 147 | 148 | io.vertx 149 | vertx-core 150 | ${vertx.version} 151 | 152 | 153 | io.vertx 154 | vertx-hazelcast 155 | ${vertx.version} 156 | 157 | 158 | 159 | 160 | PullInDeps 161 | prepare-package 162 | 163 | pullInDeps 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | org.apache.maven.plugins 172 | maven-compiler-plugin 173 | ${maven.compiler.plugin.version} 174 | 175 | 1.8 176 | 1.8 177 | 178 | 179 | 180 | maven-resources-plugin 181 | ${maven.resources.plugin.version} 182 | 183 | 184 | copy-mod-to-target 185 | process-classes 186 | 187 | copy-resources 188 | 189 | 190 | true 191 | ${mods.directory}/${module.name} 192 | 193 | 194 | target/classes 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | org.apache.maven.plugins 203 | maven-dependency-plugin 204 | ${maven.dependency.plugin.version} 205 | 206 | 207 | copy-mod-dependencies-to-target 208 | process-classes 209 | 210 | copy-dependencies 211 | 212 | 213 | ${mods.directory}/${module.name}/lib 214 | runtime 215 | 216 | 217 | 218 | copy-mod-dependencies-to-target-dependencies 219 | process-classes 220 | 221 | copy-dependencies 222 | 223 | 224 | target/dependencies 225 | runtime 226 | 227 | 228 | 229 | 230 | 231 | org.apache.maven.plugins 232 | maven-surefire-plugin 233 | ${maven.surefire.plugin.version} 234 | 235 | 236 | **/unit/*Test*.java 237 | 238 | 239 | 240 | 241 | org.apache.maven.plugins 242 | maven-failsafe-plugin 243 | ${maven.failsafe.plugin.version} 244 | 245 | 246 | 247 | vertx.mods 248 | ${mods.directory} 249 | 250 | 251 | 252 | **/integration/**/*Test* 253 | 254 | 255 | 256 | 257 | 258 | integration-test 259 | verify 260 | 261 | 262 | 263 | 264 | 265 | org.apache.maven.plugins 266 | maven-surefire-report-plugin 267 | ${maven.surefire.report.plugin.version} 268 | 269 | 270 | generate-test-report 271 | test 272 | 273 | report-only 274 | 275 | 276 | 277 | generate-integration-test-report 278 | integration-test 279 | 280 | failsafe-report-only 281 | 282 | 283 | 284 | 285 | 286 | maven-assembly-plugin 287 | 288 | 289 | src/main/assembly/mod.xml 290 | 291 | 292 | 293 | 294 | assemble 295 | package 296 | 297 | single 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | org.apache.maven.plugins 308 | maven-surefire-report-plugin 309 | ${maven.surefire.report.plugin.version} 310 | 311 | 312 | org.apache.maven.plugins 313 | maven-javadoc-plugin 314 | ${maven.javadoc.plugin.version} 315 | 316 | true 317 | 318 | 319 | 320 | 321 | 322 | -------------------------------------------------------------------------------- /src/main/README.txt: -------------------------------------------------------------------------------- 1 | Put any Java or Groovy classes used in your module in the java or groovy directories. 2 | 3 | Put any other resources that you want included in your module in the resources directory, this includes any 4 | JavaScript, Ruby, Python, Groovy or CoffeeScript scripts or any other stuff you want in your module. 5 | 6 | The mod.json file also goes in the resources directory so it's copied over too. -------------------------------------------------------------------------------- /src/main/assembly/mod.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mod 7 | 8 | zip 9 | 10 | 11 | false 12 | 13 | 14 | 15 | 16 | ${mods.directory}/${module.name} 17 | 18 | ** 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/ConfigVerticle.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | import org.vertx.java.core.json.JsonArray; 4 | import org.vertx.java.core.json.JsonObject; 5 | import org.vertx.java.platform.Verticle; 6 | 7 | /** 8 | * Created by Michal Boska on 2. 12. 2014. 9 | */ 10 | public class ConfigVerticle extends Verticle { 11 | 12 | @Override 13 | public void start() { 14 | 15 | String httpAddress = Configuration.getString("http.address", container); 16 | Integer cloudPort = null; 17 | try { 18 | cloudPort = Integer.parseInt(System.getenv("PORT")); 19 | container.logger().info("Got HTTP port definition from cloud provider, will listen on port " + cloudPort); 20 | } catch (NumberFormatException e) { 21 | cloudPort = Configuration.getInteger("http.port", container); 22 | container.logger().info("Environment property PORT is not defined, will use http port from local config: " + cloudPort); 23 | } 24 | final Integer httpPort = cloudPort; //we need a final variable to use in lambda 25 | Integer numInstances = Runtime.getRuntime().availableProcessors(); 26 | JsonArray allowedEndpointsIn = Configuration.getArray("allowedEndpointsIn", container); 27 | JsonArray allowedEndpointsOut = Configuration.getArray("allowedEndpointsOut", container); 28 | 29 | JsonObject object = new JsonObject(); 30 | object.putString(HTTPServerVerticle.CONFIG_ADDRESS, httpAddress); 31 | object.putNumber(HTTPServerVerticle.CONFIG_PORT, httpPort); 32 | object.putArray(HTTPServerVerticle.CONFIG_ALLOWED_ENDPOINTS_IN, allowedEndpointsIn); 33 | object.putArray(HTTPServerVerticle.CONFIG_ALLOWED_ENDPOINTS_OUT, allowedEndpointsOut); 34 | 35 | 36 | container.deployVerticle("ch.erni.beer.vertx.HTTPServerVerticle", object, numInstances, r -> { 37 | if (r.succeeded()) { 38 | container.logger().info("Starting " + numInstances + " instances of Pong HTTP server at address " + httpAddress + " port:" + httpPort); 39 | container.logger().info("Allowed bridges for inbound eventbus endpoints: " + allowedEndpointsIn.toString()); 40 | container.logger().info("Allowed bridges for outbound eventbus endpoints: " + allowedEndpointsOut.toString()); 41 | container.deployVerticle("ch.erni.beer.vertx.GameLobbyVerticle", rr -> { 42 | if (rr.succeeded()) { 43 | container.logger().info("Pong server successfully started"); 44 | } else { 45 | onError(rr.cause()); 46 | } 47 | }); 48 | } else { 49 | onError(r.cause()); 50 | } 51 | }); 52 | } 53 | 54 | private void onError(Throwable t) { 55 | container.logger().error("An error has occured while starting Pong server", t); 56 | //maybe we shouldn't exit the whole container 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/Configuration.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | 4 | import org.vertx.java.core.json.JsonArray; 5 | import org.vertx.java.core.json.JsonObject; 6 | import org.vertx.java.platform.Container; 7 | 8 | import java.util.function.BiFunction; 9 | import java.util.regex.Matcher; 10 | import java.util.regex.Pattern; 11 | 12 | /** 13 | * Created by Michal Boska on 4. 12. 2014. 14 | */ 15 | public final class Configuration { 16 | private static final Pattern OPTION_NESTED_PATTERN = Pattern.compile("(.+?)\\.(.*)"); 17 | 18 | public static String getString(String key, Container remoteContainer) { 19 | return (String) getOptionRecursive(key, remoteContainer.config(), JsonObject::getString); 20 | } 21 | 22 | public static Integer getInteger(String key, Container remoteContainer) { 23 | return (Integer) getOptionRecursive(key, remoteContainer.config(), JsonObject::getInteger); 24 | } 25 | 26 | public static JsonArray getArray(String key, Container remoteContainer) { 27 | return (JsonArray) getOptionRecursive(key, remoteContainer.config(), JsonObject::getArray); 28 | } 29 | 30 | public static String getMandatoryString(String key, JsonObject jsonObject) { 31 | checkKeyExists(key, jsonObject); 32 | return jsonObject.getString(key); 33 | } 34 | 35 | private static Object getOptionRecursive(String key, JsonObject jsonObject, BiFunction getterFunction) { 36 | if (!key.contains(".")) { //simple key, return directly 37 | checkKeyExists(key, jsonObject); 38 | return getterFunction.apply(jsonObject, key); 39 | } else { //complicated expression in form of obj.property.property2 .... etc 40 | Matcher matcher = OPTION_NESTED_PATTERN.matcher(key); 41 | if (!matcher.matches()) { 42 | throw new IllegalArgumentException(key + " is not a valid option expression"); 43 | } 44 | String immediateKey = matcher.group(1); 45 | String followingKeys = matcher.group(2); 46 | JsonObject nestedObject = jsonObject.getObject(immediateKey); 47 | if (nestedObject == null) { 48 | throw new IllegalArgumentException("Nested JSON object " + key + " not found"); 49 | } 50 | return getOptionRecursive(followingKeys, nestedObject, getterFunction); 51 | } 52 | } 53 | 54 | private static void checkKeyExists(String key, JsonObject jsonObject) { 55 | if (!jsonObject.containsField(key)) { 56 | throw new IllegalArgumentException("Key " + key + " does not exist in this JSON object"); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/GameLobbyVerticle.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | import ch.erni.beer.vertx.dto.AsyncHandlerDTO; 4 | import ch.erni.beer.vertx.dto.ErrorDTO; 5 | import ch.erni.beer.vertx.dto.lobby.*; 6 | import ch.erni.beer.vertx.entity.Entity; 7 | import ch.erni.beer.vertx.entity.Game; 8 | import ch.erni.beer.vertx.entity.Player; 9 | import org.apache.commons.lang3.StringEscapeUtils; 10 | import org.vertx.java.core.Handler; 11 | import org.vertx.java.core.eventbus.Message; 12 | import org.vertx.java.core.json.JsonObject; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.TreeMap; 17 | import java.util.stream.IntStream; 18 | 19 | /** 20 | * Created by Michal Boska on 3. 12. 2014. 21 | */ 22 | public class GameLobbyVerticle extends PongVerticle { 23 | public static final String QUEUE_LOBBY = "ch.erni.beer.vertx.GameLobbyVerticle.queue"; 24 | public static final String QUEUE_LOBBY_PRIVATE = "ch.erni.beer.vertx.GameLobbyVerticle.private-queue"; 25 | 26 | private static final String ERROR_NO_SUCH_PLAYER = "No such player exists"; 27 | private static final String ERROR_NO_SUCH_GAME = "No such game exists"; 28 | 29 | private Map activeGames = new HashMap<>(); 30 | private Map activeGamesByName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 31 | private Map activePlayers = new HashMap<>(); 32 | private Map activePlayersByName = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 33 | private Map deploymentIDs = new HashMap<>(); 34 | 35 | private Game joinableGame; 36 | 37 | 38 | @Override 39 | public void start() { 40 | vertx.eventBus().registerHandler(QUEUE_LOBBY, createHandler(this::handleMessage)); 41 | vertx.eventBus().registerHandler(QUEUE_LOBBY_PRIVATE, createHandler(this::handlePrivateMessage)); 42 | vertx.eventBus().registerHandler(HTTPServerVerticle.TOPIC_SOCKJS_MESSAGES, createHandler(this::handleSocketMessage)); 43 | } 44 | 45 | private JsonObject handleMessage(Message message) { 46 | JsonObject result = null; 47 | JsonObject body = message.body(); 48 | switch (body.getString("type")) { 49 | case "addPlayer": 50 | container.logger().info("Adding a new player"); 51 | result = addPlayer(body); 52 | break; 53 | case "addGame": 54 | container.logger().info("Creating a new game"); 55 | result = addGame(message); 56 | break; 57 | case "joinGame": 58 | container.logger().info("Joining an existing game"); 59 | result = joinGame(message); 60 | break; 61 | case "getAvailableGame": 62 | container.logger().info("Getting available game"); 63 | result = getAvailableGame(); 64 | break; 65 | case "listPlayers": 66 | container.logger().info("Listing players"); 67 | result = listPlayers(); 68 | break; 69 | } 70 | if (result != null && ErrorDTO.isError(result)) { 71 | container.logger().error(result.getString("error")); 72 | } 73 | return result; 74 | } 75 | 76 | private JsonObject handlePrivateMessage(Message message) { 77 | JsonObject result = null; 78 | JsonObject body = message.body(); 79 | switch (body.getString("type")) { 80 | case "gameEnded": 81 | result = endGame(body); 82 | break; 83 | } 84 | return result; 85 | } 86 | 87 | private JsonObject handleSocketMessage(Message message) { 88 | JsonObject body = message.body(); 89 | if (body != null && "disconnect".equals(body.getString("type"))) { 90 | playerDisconnected(body.getString("playerGuid")); 91 | } 92 | return AsyncHandlerDTO.getInstance(); 93 | } 94 | 95 | private JsonObject addPlayer(JsonObject message) { 96 | String name = message.getString("name"); 97 | boolean exists = activePlayersByName.containsKey(name); 98 | if (exists) { 99 | return new ErrorDTO("Player name already exists"); 100 | } 101 | Player player = new Player(StringEscapeUtils.escapeHtml4(name), Entity.generateGUID()); 102 | activePlayers.put(player.getGuid(), player); 103 | activePlayersByName.put(player.getName(), player); 104 | return new AddPlayerDTO(player.getGuid()); 105 | } 106 | 107 | private JsonObject addGame(Message message) { 108 | JsonObject body = message.body(); 109 | String name = body.getString("name"); 110 | boolean exists = activeGames.containsKey(name); 111 | if (exists) { 112 | return new ErrorDTO("Game with this name already exists"); 113 | } 114 | String playerGuid = body.getString("playerGuid"); 115 | Player player = activePlayers.get(playerGuid); 116 | if (player == null) { 117 | return new ErrorDTO(ERROR_NO_SUCH_PLAYER); 118 | } 119 | String guid = Entity.generateGUID(); 120 | Game game = new Game(StringEscapeUtils.escapeHtml4(name), guid, player); 121 | activeGames.put(guid, game); 122 | activeGamesByName.put(name, game); 123 | //deploy and configure a new game verticle 124 | JsonObject config = new JsonObject(); 125 | config.putString(GameVerticle.Constants.CONFIG_GAME_GUID, guid); 126 | config.putString(GameVerticle.Constants.CONFIG_PLAYER_GUID, playerGuid); 127 | config.putString(GameVerticle.Constants.CONFIG_PLAYER_NAME, player.getName()); 128 | container.deployVerticle(GameVerticle.class.getName(), config, result -> { 129 | if (result.succeeded()) { 130 | deploymentIDs.put(guid, result.result()); 131 | container.logger().info(String.format("Deployed a new verticle for game %s with deployment ID: %s", guid, result.result())); 132 | message.reply(new AddGameDTO(guid)); 133 | } else { 134 | message.reply(new ErrorDTO(result.cause())); 135 | } 136 | }); 137 | joinableGame = game; 138 | return AsyncHandlerDTO.getInstance(); 139 | } 140 | 141 | private JsonObject joinGame(Message message) { 142 | JsonObject body = message.body(); 143 | String playerGuid = body.getString("playerGuid"); 144 | String gameGuid = body.getString("gameGuid"); 145 | Player player = activePlayers.get(playerGuid); 146 | if (player == null) { 147 | return new ErrorDTO(ERROR_NO_SUCH_PLAYER); 148 | } 149 | Game game = activeGames.get(gameGuid); 150 | if (game == null) { 151 | return new ErrorDTO(ERROR_NO_SUCH_GAME); 152 | } 153 | if (game.isFull()) { 154 | return new ErrorDTO("Game is full"); 155 | } 156 | game.addSecondPlayer(player); 157 | //send message to existing verticle that new player has joined 158 | String address = GameVerticle.Constants.getPrivateQueueAddressForGame(gameGuid); 159 | JsonObject joinMessage = new JsonObject(); 160 | joinMessage.putString("type", GameVerticle.Constants.ACTION_ADD_PLAYER); 161 | joinMessage.putString(GameVerticle.Constants.CONFIG_PLAYER_GUID, playerGuid); 162 | joinMessage.putString(GameVerticle.Constants.CONFIG_PLAYER_NAME, player.getName()); 163 | vertx.eventBus().send(address, joinMessage, (Handler>) objReply -> { 164 | JsonObject reply = objReply.body(); 165 | if (!ErrorDTO.isError(reply)) { 166 | joinableGame = null; 167 | message.reply(new JoinGameDTO()); 168 | } else { 169 | message.reply(reply); 170 | } 171 | }); 172 | return AsyncHandlerDTO.getInstance(); 173 | } 174 | 175 | private JsonObject endGame(JsonObject game) { 176 | String guid = game.getString("guid"); 177 | Game gameInMap = activeGames.get(guid); 178 | if (gameInMap != null) { 179 | activeGamesByName.remove(gameInMap.getName()); 180 | IntStream.rangeClosed(1, 2).forEach(i -> { 181 | playerDisconnected(gameInMap.getPlayer(i).getGuid()); 182 | }); 183 | } 184 | activeGames.remove(guid); 185 | String id = deploymentIDs.get(guid); 186 | if (id != null) { 187 | container.logger().info(String.format("Destroying verticle for game %s", guid)); 188 | deploymentIDs.remove(guid); 189 | container.undeployVerticle(id); 190 | } 191 | return AsyncHandlerDTO.getInstance(); 192 | } 193 | 194 | private JsonObject getAvailableGame() { 195 | return new AvailableGameDTO(joinableGame); 196 | } 197 | 198 | private JsonObject listPlayers() { 199 | String[] strings = new String[activePlayersByName.size()]; 200 | strings = activePlayersByName.keySet().toArray(strings); 201 | return new ListPlayersDTO(strings); 202 | } 203 | 204 | private void playerDisconnected(String playerGuid) { 205 | Player player = activePlayers.get(playerGuid); 206 | if (player == null) { 207 | return; 208 | } 209 | activePlayers.remove(playerGuid); 210 | activePlayersByName.remove(player.getName()); 211 | } 212 | 213 | } 214 | 215 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/GameVerticle.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | import ch.erni.beer.vertx.dto.AsyncHandlerDTO; 4 | import ch.erni.beer.vertx.dto.ErrorDTO; 5 | import ch.erni.beer.vertx.dto.game.GameCommandDTO; 6 | import ch.erni.beer.vertx.dto.game.GameStateDTO; 7 | import ch.erni.beer.vertx.dto.lobby.AddPlayerDTO; 8 | import ch.erni.beer.vertx.dto.lobby.GameEndedDTO; 9 | import ch.erni.beer.vertx.entity.Player; 10 | import org.vertx.java.core.eventbus.Message; 11 | import org.vertx.java.core.json.JsonObject; 12 | 13 | import java.util.stream.IntStream; 14 | 15 | /** 16 | * Created by Michal Boska on 3. 12. 2014. 17 | */ 18 | public class GameVerticle extends PongVerticle { 19 | 20 | private Player[] players = new Player[2]; 21 | private String guid, publicAddress, privateAddress, inputAddress; 22 | private Point ball = new Point(512, 300); 23 | private float ballSpeed = 10; 24 | private Point ballVector = new Point(-1, -1); 25 | //we don't want computeNewBallCoordinates to always return new instance to elliminate GC overhead, 26 | // so we will always modify the same one 27 | private Point newBallCoordinates = new Point(); 28 | private JsonObject playerMoveResponse = new JsonObject(); 29 | private GameStateDTO state = new GameStateDTO(); 30 | private GameCommandDTO command = new GameCommandDTO(); 31 | private int disconnectedPlayerIndex = -1; 32 | 33 | private byte speedCounter = 0; 34 | private long gameTimer; 35 | 36 | @Override 37 | public void start() { 38 | guid = Configuration.getString(Constants.CONFIG_GAME_GUID, container); 39 | String playerGuid = Configuration.getString(Constants.CONFIG_PLAYER_GUID, container); 40 | String playerName = Configuration.getString(Constants.CONFIG_PLAYER_NAME, container); 41 | players[0] = new Player(playerName, playerGuid); 42 | publicAddress = Constants.getPublicQueueAddressForGame(guid); 43 | privateAddress = Constants.getPrivateQueueAddressForGame(guid); 44 | inputAddress = Constants.getInputQueueAddressForGame(guid); 45 | vertx.eventBus().registerHandler(inputAddress, createHandler(this::handleInputMessages)); 46 | vertx.eventBus().registerHandler(privateAddress, createHandler(this::handlePrivateMessages)); 47 | vertx.eventBus().registerHandler(HTTPServerVerticle.TOPIC_SOCKJS_MESSAGES, createHandler(this::handleSockJsMessages)); 48 | } 49 | 50 | private JsonObject handleInputMessages(Message message) { 51 | JsonObject result = null; 52 | JsonObject body = message.body(); 53 | switch (body.getString("type")) { 54 | case "move": 55 | result = movePlayer(body); 56 | break; 57 | } 58 | return result; 59 | } 60 | 61 | private JsonObject handlePrivateMessages(Message message) { 62 | JsonObject result = null; 63 | JsonObject body = message.body(); 64 | switch (body.getString("type")) { 65 | case Constants.ACTION_ADD_PLAYER: 66 | result = addPlayer(body); 67 | break; 68 | } 69 | return result; 70 | } 71 | 72 | private JsonObject handleSockJsMessages(Message message) { 73 | JsonObject body = message.body(); 74 | if (body != null && body.getString("type").equals("disconnect")) { 75 | playerDisconnected(body.getString("playerGuid")); 76 | } 77 | return AsyncHandlerDTO.getInstance(); 78 | } 79 | 80 | private void gameTick(long timerID) { 81 | move(); 82 | populateGameState(); 83 | vertx.eventBus().publish(publicAddress, state); 84 | int winning = getWinningPlayer(); 85 | if (winning != -1) { //if we have a winner, end game 86 | command.setCommand("win" + (winning + 1)); 87 | vertx.cancelTimer(timerID); 88 | vertx.eventBus().publish(publicAddress, command); 89 | vertx.eventBus().send(GameLobbyVerticle.QUEUE_LOBBY_PRIVATE, new GameEndedDTO(guid)); 90 | } 91 | } 92 | 93 | private void move() { 94 | computeNewBallCoordinates(); 95 | if (newBallCoordinates.getY() < 20) { 96 | ballVector.setY(1); 97 | computeNewBallCoordinates(); 98 | } else if (newBallCoordinates.getY() > 560) { 99 | ballVector.setY(-1); 100 | computeNewBallCoordinates(); 101 | } 102 | if (newBallCoordinates.getX() < 20) { //ball is at player1's level 103 | if (ballCollidesWith(0)) { //player has caught the ball 104 | ballVector.setX(1); 105 | decideSpeed(); 106 | computeNewBallCoordinates(); 107 | } else { //missed the ball 108 | players[1].setScore(players[1].getScore() + 1); 109 | resetBall(); 110 | computeNewBallCoordinates(); 111 | } 112 | } else if (newBallCoordinates.getX() > 994) { //ball is at player2's level 113 | if (ballCollidesWith(1)) { 114 | ballVector.setX(-1); 115 | decideSpeed(); 116 | computeNewBallCoordinates(); 117 | } else { //missed the ball 118 | players[0].setScore(players[0].getScore() + 1); 119 | resetBall(); 120 | computeNewBallCoordinates(); 121 | } 122 | } 123 | ball.set(newBallCoordinates); 124 | } 125 | 126 | private void decideSpeed() { 127 | speedCounter++; 128 | if (speedCounter == 5) { 129 | ballSpeed *= 1.3; 130 | speedCounter = 0; 131 | } 132 | } 133 | 134 | private JsonObject movePlayer(JsonObject message) { 135 | Player player = null; 136 | String guid = message.getString("guid"); 137 | for (Player p: players) { 138 | if (p != null && p.getGuid().equalsIgnoreCase(guid)) { 139 | player = p; 140 | } 141 | } 142 | playerMoveResponse.putNumber("y", 0); 143 | if (player == null) { 144 | return playerMoveResponse; 145 | } 146 | playerMoveResponse.putNumber("y", player.getPosition()); 147 | Number newPosition = message.getNumber("y"); 148 | if (newPosition == null) { 149 | return playerMoveResponse; 150 | } 151 | int value = Math.round(newPosition.floatValue()); 152 | if (value >= 20 && value <= 480 && Math.abs(value - player.getPosition()) <= 10) { 153 | player.setPosition(value); 154 | populateGameState(); 155 | vertx.eventBus().publish(publicAddress, state); 156 | } 157 | return playerMoveResponse; 158 | } 159 | 160 | /** 161 | * @return index of winning player (0 or 1) or -1 if no one is winning 162 | */ 163 | private int getWinningPlayer() { 164 | if (disconnectedPlayerIndex > -1) { 165 | return 1 - disconnectedPlayerIndex; //if i = 1, return 0 and vice-versa 166 | } 167 | for (int i = 0; i <= 1; i++) { 168 | if (players[i].getScore() >= 10) { 169 | return i; 170 | } 171 | } 172 | return -1; 173 | } 174 | 175 | private void populateGameState() { 176 | state.setBallPosition(ball.getX(), ball.getY()); 177 | state.setPlayer1position(players[0].getPosition()); 178 | state.setPlayer1score(players[0].getScore()); 179 | if (players[1] != null) { 180 | state.setPlayer2position(players[1].getPosition()); 181 | state.setPlayer2score(players[1].getScore()); 182 | } 183 | } 184 | 185 | /** 186 | * Only Y coordinate is taken into account 187 | * 188 | * @param playerIndex 189 | * @return 190 | */ 191 | private boolean ballCollidesWith(int playerIndex) { 192 | Player player = players[playerIndex]; 193 | int playerYLow = player.getPosition(); 194 | int playerYHigh = playerYLow + 100; 195 | int ballYLow = newBallCoordinates.getY(); 196 | int ballYHigh = ballYLow + 20; 197 | return ballYHigh >= playerYLow && ballYLow <= playerYHigh; 198 | } 199 | 200 | private void computeNewBallCoordinates() { 201 | newBallCoordinates.setX(ball.getX() + Math.round(ballSpeed * ballVector.getX())); 202 | newBallCoordinates.setY(ball.getY() + Math.round(ballSpeed * ballVector.getY())); 203 | } 204 | 205 | private void resetBall() { 206 | ball.setX(500); 207 | ball.setY(400); 208 | ballVector = new Point(-1, -1); 209 | ballSpeed = 5; 210 | speedCounter = 0; 211 | } 212 | 213 | private void startGame() { 214 | container.logger().info(String.format("Game %s is starting", guid)); 215 | if (gameTimer != 0) { 216 | vertx.cancelTimer(gameTimer); 217 | } 218 | resetBall(); 219 | players[0].setPosition(230); 220 | players[1].setPosition(230); 221 | players[0].setScore(0); 222 | players[1].setScore(0); 223 | populateGameState(); 224 | command.setCommand("start"); 225 | vertx.eventBus().publish(publicAddress, command); 226 | gameTimer = vertx.setPeriodic(20, this::gameTick); 227 | } 228 | 229 | private JsonObject addPlayer(JsonObject message) { 230 | if (players[1] != null) { 231 | return new ErrorDTO("Game is full"); 232 | } 233 | String playerGuid = Configuration.getMandatoryString(Constants.CONFIG_PLAYER_GUID, message); 234 | String playerName = Configuration.getMandatoryString(Constants.CONFIG_PLAYER_NAME, message); 235 | players[1] = new Player(playerName, playerGuid); 236 | vertx.setTimer(2000, l -> startGame()); 237 | return new AddPlayerDTO(playerGuid); 238 | } 239 | 240 | private void playerDisconnected(String playerGuid) { 241 | IntStream.rangeClosed(0, 1).forEach(i -> { 242 | if (players[i] != null && players[i].getGuid().equals(playerGuid)) { 243 | disconnectedPlayerIndex = i; 244 | } 245 | }); 246 | } 247 | 248 | public static class Constants { 249 | public static final String QUEUE_PUBLIC_PREFIX = "Game.public-"; 250 | public static final String QUEUE_PRIVATE_PREFIX = "Game.private-"; 251 | public static final String QUEUE_INPUT_PREFIX = "Game.input-"; 252 | 253 | public static final String CONFIG_GAME_GUID = "gameGuid"; 254 | public static final String CONFIG_PLAYER_GUID = "playerGuid"; 255 | public static final String CONFIG_PLAYER_NAME = "playerName"; 256 | 257 | public static final String ACTION_ADD_PLAYER = "addPlayer"; 258 | 259 | public static String getPublicQueueAddressForGame(String guid) { 260 | return QUEUE_PUBLIC_PREFIX + guid; 261 | } 262 | public static String getPrivateQueueAddressForGame(String guid) { 263 | return QUEUE_PRIVATE_PREFIX + guid; 264 | } 265 | public static String getInputQueueAddressForGame(String guid) { 266 | return QUEUE_INPUT_PREFIX + guid; 267 | } 268 | } 269 | } 270 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/HTTPServerVerticle.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | import ch.erni.beer.vertx.dto.lobby.PlayerDisconnectDTO; 4 | import org.vertx.java.core.AsyncResult; 5 | import org.vertx.java.core.Handler; 6 | import org.vertx.java.core.http.HttpServer; 7 | import org.vertx.java.core.json.JsonArray; 8 | import org.vertx.java.core.json.JsonObject; 9 | import org.vertx.java.core.sockjs.EventBusBridgeHook; 10 | import org.vertx.java.core.sockjs.SockJSServer; 11 | import org.vertx.java.core.sockjs.SockJSSocket; 12 | import org.vertx.java.platform.Verticle; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | /** 18 | * Created by Michal Boska on 3. 12. 2014. 19 | */ 20 | public class HTTPServerVerticle extends Verticle implements EventBusBridgeHook { 21 | public static final String CONFIG_PORT = "port"; 22 | public static final String CONFIG_ADDRESS = "address"; 23 | public static final String CONFIG_ALLOWED_ENDPOINTS_IN = "allowedEndpointsIn"; 24 | public static final String CONFIG_ALLOWED_ENDPOINTS_OUT = "allowedEndpointsOut"; 25 | public static final String TOPIC_SOCKJS_MESSAGES = "ch.erni.beer.vertx.HTTPServerVerticle.topic.sockjs"; 26 | 27 | private SockJSServer sockJSServer; 28 | private Map addressToPlayerGuidMap = new HashMap<>(); 29 | 30 | @Override 31 | public void start() { 32 | HttpServer httpServer = vertx.createHttpServer(); 33 | httpServer.requestHandler(r -> { 34 | String file = ""; 35 | if (r.path().equals("/")) { 36 | file = "index.html"; 37 | } else if (!r.path().contains("..")) { 38 | file = r.path(); 39 | } 40 | r.response().sendFile("www/" + file, "www/404.html"); 41 | }); 42 | 43 | JsonObject sockJsConfig = new JsonObject().putString("prefix", "/eventbus"); 44 | JsonArray allowedEndpointsIn = Configuration.getArray(CONFIG_ALLOWED_ENDPOINTS_IN, container); 45 | JsonArray allowedEndpointsOut = Configuration.getArray(CONFIG_ALLOWED_ENDPOINTS_OUT, container); 46 | sockJSServer = vertx.createSockJSServer(httpServer); 47 | sockJSServer.setHook(this); 48 | sockJSServer.bridge(sockJsConfig, allowedEndpointsIn, allowedEndpointsOut); 49 | httpServer.listen(Configuration.getInteger(CONFIG_PORT, container), Configuration.getString(CONFIG_ADDRESS, container)); 50 | } 51 | 52 | @Override 53 | public boolean handleSocketCreated(SockJSSocket sock) { 54 | return true; 55 | } 56 | 57 | @Override 58 | public void handleSocketClosed(SockJSSocket sock) { 59 | String address = sock.remoteAddress().toString(); 60 | String guid = addressToPlayerGuidMap.get(address); 61 | if (guid == null) { 62 | //player with such remote address not found, nothing more to do 63 | return; 64 | } 65 | vertx.eventBus().publish(TOPIC_SOCKJS_MESSAGES, new PlayerDisconnectDTO(guid)); 66 | } 67 | 68 | @Override 69 | public boolean handleSendOrPub(SockJSSocket sock, boolean send, JsonObject msg, String address) { 70 | //we want to intercept only Lobby messages to listen for Create-Game or Join-Game message 71 | //These messages contain player GUID, so we can pair player GUID with the socket's remote address 72 | if (!address.equals(GameLobbyVerticle.QUEUE_LOBBY)) { 73 | return true; 74 | } 75 | JsonObject body = msg.getObject("body"); 76 | if (body == null) { 77 | return true; 78 | } 79 | String type = body.getString("type"); 80 | if (!"addGame".equals(type) && !"joinGame".equals(type)) { 81 | return true; 82 | } 83 | String playerGuid = body.getString("playerGuid"); 84 | if (playerGuid == null) { 85 | return true; 86 | } 87 | addressToPlayerGuidMap.put(sock.remoteAddress().toString(), playerGuid); 88 | return true; 89 | } 90 | 91 | @Override 92 | public boolean handlePreRegister(SockJSSocket sock, String address) { 93 | return true; 94 | } 95 | 96 | @Override 97 | public void handlePostRegister(SockJSSocket sock, String address) { 98 | } 99 | 100 | @Override 101 | public boolean handleUnregister(SockJSSocket sock, String address) { 102 | return true; 103 | } 104 | 105 | @Override 106 | public boolean handleAuthorise(JsonObject message, String sessionID, Handler> handler) { 107 | return true; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/Point.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | /** 4 | * We don't want to depend on AWT or similar libraries 5 | */ 6 | public class Point { 7 | private int x, y; 8 | 9 | public Point() { 10 | this(0, 0); 11 | } 12 | 13 | public Point(int x, int y) { 14 | this.x = x; 15 | this.y = y; 16 | } 17 | 18 | public int getX() { 19 | return x; 20 | } 21 | 22 | public void setX(int x) { 23 | this.x = x; 24 | } 25 | 26 | public int getY() { 27 | return y; 28 | } 29 | 30 | public void setY(int y) { 31 | this.y = y; 32 | } 33 | 34 | public void set(Point point) { 35 | this.x = point.x; 36 | this.y = point.y; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return String.format("Point: [x=%d; y=%d]", x, y); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/PongVerticle.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx; 2 | 3 | import ch.erni.beer.vertx.dto.AsyncHandlerDTO; 4 | import ch.erni.beer.vertx.dto.ErrorDTO; 5 | import org.vertx.java.core.Handler; 6 | import org.vertx.java.core.eventbus.Message; 7 | import org.vertx.java.core.json.JsonObject; 8 | import org.vertx.java.platform.Verticle; 9 | 10 | import java.util.function.Function; 11 | 12 | /** 13 | * Created by Michal on 6. 12. 2014. 14 | */ 15 | public abstract class PongVerticle extends Verticle { 16 | 17 | protected Handler> createHandler(Function, JsonObject> handlerFunction) { 18 | return msg -> { 19 | JsonObject result = handlerFunction.apply(msg); 20 | if (result == null) { 21 | unknownHandlerError(msg); 22 | } else if (!(result instanceof AsyncHandlerDTO)) { 23 | msg.reply(result); 24 | } 25 | //else, if the result is an instance of AsyncHandlerDTO, do nothing, as the handlerFunction will handle 26 | //the response itself 27 | }; 28 | } 29 | 30 | protected void unknownHandlerError(Message msg) { 31 | msg.reply(new ErrorDTO("No handler registered for this message type")); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/AsyncHandlerDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto; 2 | 3 | import org.vertx.java.core.json.JsonObject; 4 | 5 | /** 6 | * Created by bol on 8. 12. 2014. 7 | */ 8 | public class AsyncHandlerDTO extends JsonObject { 9 | 10 | public static AsyncHandlerDTO getInstance() { 11 | return _instance; 12 | } 13 | 14 | private static final AsyncHandlerDTO _instance = new AsyncHandlerDTO(); 15 | private AsyncHandlerDTO(){} 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/EntityCreatedDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto; 2 | 3 | /** 4 | * Created by bol on 8. 12. 2014. 5 | */ 6 | public abstract class EntityCreatedDTO extends PongDTO { 7 | 8 | public EntityCreatedDTO(String guid) { 9 | putString("guid", guid); 10 | setStatusOk(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/ErrorDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto; 2 | 3 | import org.vertx.java.core.json.JsonObject; 4 | 5 | /** 6 | * Created by Michal Boska on 5. 12. 2014. 7 | */ 8 | public class ErrorDTO extends PongDTO { 9 | 10 | public static boolean isError(JsonObject jsonObject) { 11 | return jsonObject.containsField("error") && jsonObject.getString("error") != null; 12 | } 13 | 14 | public ErrorDTO(String message) { 15 | putString("message", message); 16 | } 17 | 18 | public ErrorDTO(Throwable throwable) { 19 | putString("message", throwable.getMessage()); 20 | } 21 | 22 | @Override 23 | public String getType() { 24 | return "error"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/HandleAsyncDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto; 2 | 3 | /** 4 | * Created by Michal on 6. 12. 2014. 5 | */ 6 | public class HandleAsyncDTO { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/PongDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto; 2 | 3 | import org.vertx.java.core.json.JsonObject; 4 | 5 | /** 6 | * Created by Michal Boska on 5. 12. 2014. 7 | */ 8 | public abstract class PongDTO extends JsonObject { 9 | 10 | public PongDTO() { 11 | putString("type", getType()); 12 | } 13 | 14 | protected void setStatusOk() { 15 | putString("status", "ok"); 16 | } 17 | 18 | public abstract String getType(); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/game/GameCommandDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.game; 2 | 3 | /** 4 | * Created by bol on 12. 12. 2014. 5 | */ 6 | public class GameCommandDTO extends GameDTO { 7 | 8 | public GameCommandDTO() { 9 | setCommand(""); 10 | } 11 | 12 | public void setCommand(String command) { 13 | this.putString("command", command); 14 | } 15 | 16 | @Override 17 | public String getType() { 18 | return "command"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/game/GameDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.game; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | 5 | /** 6 | * Created by Michal Boska on 5. 12. 2014. 7 | */ 8 | public abstract class GameDTO extends PongDTO { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/game/GameStateDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.game; 2 | 3 | /** 4 | * Created by bol on 12. 12. 2014. 5 | */ 6 | public class GameStateDTO extends GameDTO{ 7 | 8 | public GameStateDTO() { 9 | setBallPosition(0, 0); 10 | setPlayer1position(0); 11 | setPlayer2position(0); 12 | setPlayer1score(0); 13 | setPlayer2score(0); 14 | } 15 | 16 | @Override 17 | public String getType() { 18 | return "state"; 19 | } 20 | 21 | public void setPlayer1position(int y) { 22 | putNumber("player1pos", y); 23 | } 24 | 25 | public void setPlayer2position(int y) { 26 | putNumber("player2pos", y); 27 | } 28 | 29 | public void setPlayer1score(int score) { 30 | putNumber("player1score", score); 31 | } 32 | 33 | public void setPlayer2score(int score) { 34 | putNumber("player2score", score); 35 | } 36 | 37 | public void setBallPosition(int x, int y) { 38 | putNumber("ballx", x); 39 | putNumber("bally", y); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/AddGameDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.EntityCreatedDTO; 4 | 5 | /** 6 | * Created by bol on 8. 12. 2014. 7 | */ 8 | public class AddGameDTO extends EntityCreatedDTO { 9 | 10 | public AddGameDTO(String guid) { 11 | super(guid); 12 | } 13 | 14 | @Override 15 | public String getType() { 16 | return "addGame"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/AddPlayerDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.EntityCreatedDTO; 4 | import ch.erni.beer.vertx.dto.PongDTO; 5 | 6 | /** 7 | * Created by Michal on 6. 12. 2014. 8 | */ 9 | public class AddPlayerDTO extends EntityCreatedDTO { 10 | 11 | public AddPlayerDTO(String guid) { 12 | super(guid); 13 | } 14 | @Override 15 | public String getType() { 16 | return "addPlayer"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/AvailableGameDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | import ch.erni.beer.vertx.entity.Game; 5 | 6 | /** 7 | * Created by Michal on 13. 12. 2014. 8 | */ 9 | public class AvailableGameDTO extends PongDTO { 10 | 11 | public AvailableGameDTO(Game game) { 12 | putString("guid", game != null ? game.getGuid() : null); 13 | putString("name", game != null ? game.getName() : null); 14 | setStatusOk(); 15 | } 16 | 17 | @Override 18 | public String getType() { 19 | return "availableGame"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/GameEndedDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | 5 | /** 6 | * Created by Michal on 14. 12. 2014. 7 | */ 8 | public class GameEndedDTO extends PongDTO { 9 | 10 | public GameEndedDTO(String gameGuid) { 11 | putString("guid", gameGuid); 12 | } 13 | 14 | @Override 15 | public String getType() { 16 | return "gameEnded"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/JoinGameDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | 5 | /** 6 | * Created by bol on 8. 12. 2014. 7 | */ 8 | public class JoinGameDTO extends PongDTO { 9 | 10 | public JoinGameDTO() { 11 | setStatusOk(); 12 | } 13 | 14 | @Override 15 | public String getType() { 16 | return "joinGame"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/ListPlayersDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | import org.vertx.java.core.json.JsonArray; 5 | 6 | /** 7 | * Created by Michal on 6. 12. 2014. 8 | */ 9 | public class ListPlayersDTO extends PongDTO { 10 | 11 | public ListPlayersDTO(String[] playerNames) { 12 | putArray("players", new JsonArray(playerNames)); 13 | setStatusOk(); 14 | } 15 | 16 | @Override 17 | public String getType() { 18 | return "listPlayers"; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/dto/lobby/PlayerDisconnectDTO.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.dto.lobby; 2 | 3 | import ch.erni.beer.vertx.dto.PongDTO; 4 | 5 | /** 6 | * Created by Michal on 29. 12. 2014. 7 | */ 8 | public class PlayerDisconnectDTO extends PongDTO { 9 | 10 | public PlayerDisconnectDTO(String playerGuid) { 11 | putString("playerGuid", playerGuid); 12 | } 13 | 14 | @Override 15 | public String getType() { 16 | return "disconnect"; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/entity/Entity.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.entity; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.UUID; 6 | 7 | /** 8 | * Created by bol on 8. 12. 2014. 9 | */ 10 | public abstract class Entity { 11 | protected String name; 12 | protected String guid; 13 | 14 | public static String generateGUID() { 15 | return UUID.randomUUID().toString(); 16 | } 17 | 18 | 19 | public Entity(String name, String guid) { 20 | this.name = name; 21 | this.guid = guid; 22 | } 23 | 24 | //GUID is an unique ID for an internal dto, program logic assumes there cannot be 25 | //different objects with the same guid, therefore guid field is sufficient for equals and hashCode 26 | @Override 27 | public boolean equals(Object o) { 28 | if (o == null || !this.getClass().isInstance(o)) { 29 | return false; 30 | } 31 | Entity oo = (Entity) o; 32 | return guid.equals(oo.guid); 33 | } 34 | 35 | @Override 36 | public int hashCode() { 37 | return guid.hashCode(); 38 | } 39 | 40 | public String getName() { 41 | return name; 42 | } 43 | 44 | public String getGuid() { 45 | return guid; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/entity/Game.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.entity; 2 | 3 | /** 4 | * Created by bol on 8. 12. 2014. 5 | */ 6 | public class Game extends Entity { 7 | 8 | private Player[] players = new Player[2]; 9 | 10 | public Game(String name, String guid, Player firstPlayer) { 11 | super(name, guid); 12 | this.players[0] = firstPlayer; 13 | } 14 | 15 | /** 16 | * 17 | * @param index 1 or 2 18 | * @return 19 | */ 20 | public Player getPlayer(int index) { 21 | return players[index - 1]; 22 | } 23 | 24 | public void addSecondPlayer(Player player) { 25 | if (isFull()) { 26 | throw new IllegalStateException("The game " + guid + " is already full"); 27 | } 28 | players[1] = player; 29 | } 30 | 31 | public boolean isFull() { 32 | return players[0] != null && players[1] != null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/ch/erni/beer/vertx/entity/Player.java: -------------------------------------------------------------------------------- 1 | package ch.erni.beer.vertx.entity; 2 | 3 | /** 4 | * Created by bol on 8. 12. 2014. 5 | */ 6 | public class Player extends Entity { 7 | private int position; //player cursor position 8 | private int score; 9 | 10 | public Player(String name, String guid) { 11 | super(name, guid); 12 | } 13 | 14 | public int getPosition() { 15 | return position; 16 | } 17 | 18 | public void setPosition(int position) { 19 | this.position = position; 20 | } 21 | 22 | public int getScore() { 23 | return score; 24 | } 25 | 26 | public void setScore(int score) { 27 | this.score = score; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/platform_lib/README.txt: -------------------------------------------------------------------------------- 1 | If you want override the default langs.properties, cluster.xml or any other config for the Vert.x platform (i.e. 2 | not for the module!) then you can add them in here and they will be added to the platform classpath when running 3 | your module using Gradle. -------------------------------------------------------------------------------- /src/main/resources/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "http": { 3 | "port": 8080, 4 | "address": "0.0.0.0" 5 | }, 6 | "allowedEndpointsIn": [ 7 | { 8 | "address": "ch.erni.beer.vertx.GameLobbyVerticle.queue" 9 | }, 10 | { 11 | "address_re": "Game\\.input-[a-zA-Z0-9\\-]+" 12 | } 13 | ], 14 | "allowedEndpointsOut": [ 15 | { 16 | "address_re": "Game\\.public-[a-zA-Z0-9\\-]+" 17 | } 18 | ] 19 | 20 | } -------------------------------------------------------------------------------- /src/main/resources/mod.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | // Java verticle 4 | "main":"ch.erni.beer.vertx.ConfigVerticle", 5 | 6 | 7 | "auto-redeploy": true 8 | } -------------------------------------------------------------------------------- /src/main/resources/platform_lib/README.txt: -------------------------------------------------------------------------------- 1 | If you are using fatjars and you want override the default langs.properties, cluster.xml or any other config for the Vert.x platform (i.e. 2 | not for the module!) then you can add them in here and they will be added to the platform classpath when running 3 | your module using Gradle. 4 | 5 | The fatjar starter knows to add this directory (and any jars/zips inside it) to the Vert.x platform classpath when executing your module as a fatjar. -------------------------------------------------------------------------------- /src/main/resources/www/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |

HTTP 404

9 |

Page not found!

10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/www/app.js: -------------------------------------------------------------------------------- 1 | var UIModel = function() { 2 | var Q = Quintus({development: true}).include("Sprites, Scenes, Input, 2D, Touch, UI").setup("game").controls(true).touch(); 3 | var thisUiModel = this; 4 | var lblScore1, lblScore2, lblOverlay; 5 | 6 | this.onPlayerMove = null; 7 | this.onStageLoaded = null; 8 | 9 | function playerObject(playerNum) { 10 | var result = { 11 | init: function (p) { 12 | this._super(p, { 13 | asset: "black.png", 14 | x: playerNum == 1 ? 10 : 1014, 15 | y: 280 16 | }); 17 | this.add("2d"); 18 | } 19 | } 20 | return result; 21 | } 22 | 23 | function wallObject(wall) { 24 | var y = wall == "top" ? 0 : 580; 25 | var result = { 26 | init: function(p) { 27 | this._super(p, { 28 | x: 512, 29 | y: wall == "top" ? 10 : 590, 30 | w: 1024, 31 | h: 20, 32 | cx: 512, 33 | cy: 10, 34 | color: "blue" 35 | }); 36 | }, 37 | draw: function(ctx) { 38 | ctx.fillStyle = this.p.color; 39 | ctx.fillRect(-this.p.cx, -this.p.cy, this.p.w, this.p.h); 40 | } 41 | }; 42 | return result; 43 | } 44 | 45 | Q.Sprite.extend("Player1", playerObject(1)); 46 | Q.Sprite.extend("Player2", playerObject(2)); 47 | Q.Sprite.extend("WallTop", wallObject("top")); 48 | Q.Sprite.extend("WallBottom", wallObject("bottom")); 49 | Q.Sprite.extend("Ball", { 50 | init: function(p) { 51 | this._super(p, { 52 | color: "red", 53 | x: 512, 54 | y: 300, 55 | w: 20, 56 | h: 20, 57 | cx: 10, 58 | cy: 10 59 | }); 60 | this.add("2d"); 61 | }, 62 | draw: function(ctx) { 63 | ctx.fillStyle = this.p.color; 64 | ctx.fillRect(-this.p.cx, -this.p.cy, this.p.w, this.p.h); 65 | } 66 | }); 67 | 68 | Q.scene("mainScene", function (stage) { 69 | thisUiModel.player1 = stage.insert(new Q.Player1); 70 | thisUiModel.player2 = stage.insert(new Q.Player2); 71 | thisUiModel.ball = stage.insert(new Q.Ball); 72 | lblScore1 = stage.insert(new Q.UI.Text({x: 30, y: 50, label: "0"})); 73 | lblScore2 = stage.insert(new Q.UI.Text({x: 984, y: 50, label: "0"})); 74 | lblOverlay = stage.insert(new Q.UI.Text({x: 512, y: 300, size: 30, label: "Waiting for other player..."})); 75 | stage.insert(new Q.WallTop); 76 | stage.insert(new Q.WallBottom); 77 | if (thisUiModel.onStageLoaded != null) { 78 | thisUiModel.onStageLoaded(); 79 | } 80 | }); 81 | 82 | Q.gravityX = 0; 83 | Q.gravityY = 0; 84 | Q.input.keyboardControls(); 85 | 86 | this.uiInit = function() { 87 | Q.load("black.png, green.png", 88 | function () { 89 | Q.stageScene("mainScene"); 90 | } 91 | ); 92 | } 93 | 94 | this.setControllablePlayer = function(playerNum) { 95 | var object = playerNum == 1 ? thisUiModel.player1 : thisUiModel.player2; 96 | object.step = function () { 97 | var vy = Q.inputs['up'] ? -200 : 0; 98 | vy = Q.inputs['down'] ? 200 : vy; 99 | var vySingle = vy / 30; 100 | if (vy != 0 && this.p.y + vySingle >= 50 && this.p.y + vySingle <= 650 - this.p.h) { 101 | this.p.y += vySingle; 102 | if (thisUiModel.onPlayerMove != null) { 103 | thisUiModel.onPlayerMove(object); 104 | } 105 | } 106 | }; 107 | object.p.asset = "green.png"; 108 | lblOverlay.p.hidden = true; 109 | }; 110 | 111 | this.setScore = function(score1, score2) { 112 | lblScore1.p.label = score1.toString(); 113 | lblScore2.p.label = score2.toString(); 114 | }; 115 | 116 | this.setWinningPlayer = function(playerNum) { 117 | lblOverlay.p.label = "Player " + playerNum + " wins!"; 118 | lblOverlay.p.hidden = false; 119 | } 120 | }; 121 | 122 | 123 | var Controller = function() { 124 | const GAME_PUBLIC_QUEUE_PREFIX = "Game.public-"; 125 | const GAME_INPUT_QUEUE_PREFIX = "Game.input-"; 126 | const LOBBY_QUEUE = "ch.erni.beer.vertx.GameLobbyVerticle.queue"; 127 | var inputQueue; 128 | var playerGUID, gameGUID; 129 | var player1, player2, ball; 130 | var myPlayerNumber; 131 | var eb; 132 | var model = new UIModel(); 133 | 134 | model.onPlayerMove = onPlayerMove; 135 | model.onStageLoaded = function() { 136 | player1 = model.player1; 137 | player2 = model.player2; 138 | ball = model.ball; 139 | eb = new vertx.EventBus(document.URL + "eventbus"); 140 | eb.onopen = function() { 141 | registerToGame(); 142 | } 143 | }; 144 | model.uiInit(); 145 | 146 | function registerToGame() { 147 | eb.send(LOBBY_QUEUE, {type: "addPlayer", "name": "Player" + new Date().getTime()}, function(addPlayerResult){ 148 | if (addPlayerResult.status == "ok") { 149 | playerGUID = addPlayerResult.guid; 150 | eb.send(LOBBY_QUEUE, {type: "getAvailableGame"}, function(availGameResult){ 151 | var onGameRegistered = function(result) { 152 | var address = GAME_PUBLIC_QUEUE_PREFIX + gameGUID; 153 | eb.registerHandler(address, onGameMessageReceived); 154 | inputQueue = GAME_INPUT_QUEUE_PREFIX + gameGUID; 155 | }; 156 | var availGameGuid = availGameResult.guid; 157 | if (availGameGuid != null) { //join existing game 158 | gameGUID = availGameGuid; 159 | myPlayerNumber = 2; 160 | eb.send(LOBBY_QUEUE, {type: "joinGame", playerGuid: playerGUID, gameGuid: gameGUID}, function(joinGameResult){ 161 | if (joinGameResult.status == "ok") { 162 | onGameRegistered(joinGameResult); 163 | } 164 | }); 165 | } else { //create a new game 166 | myPlayerNumber = 1; 167 | eb.send(LOBBY_QUEUE, {type: "addGame", playerGuid: playerGUID, name: "Game" + new Date().getTime()}, function(addGameResult) { 168 | if (addGameResult.status == "ok") { 169 | gameGUID = addGameResult.guid; 170 | onGameRegistered(addGameResult); 171 | } 172 | }); 173 | } 174 | }); 175 | } 176 | }); 177 | } 178 | 179 | function onGameMessageReceived(message) { 180 | if (message.type == "state") { 181 | ball.p.x = message.ballx + ball.p.cx; 182 | ball.p.y = message.bally + ball.p.cy; 183 | //always update state of the other player 184 | var y = 0; 185 | if (myPlayerNumber == 1) { 186 | y = message.player2pos; 187 | player2.p.y = y + player2.p.cy; 188 | } else if (myPlayerNumber == 2) { 189 | y = message.player1pos; 190 | player1.p.y = y + player1.p.cy; 191 | } 192 | model.setScore(message.player1score, message.player2score); 193 | } else if (message.type == "command") { 194 | if (message.command == "start") { 195 | model.setControllablePlayer(myPlayerNumber); 196 | } else if (message.command == "win1") { 197 | model.setWinningPlayer(1); 198 | } else if (message.command == "win2") { 199 | model.setWinningPlayer(2); 200 | } 201 | } 202 | } 203 | 204 | function onPlayerMove(player) { 205 | if (inputQueue == null) { 206 | return; 207 | } 208 | eb.send(inputQueue, {type: "move", guid: playerGUID, y: player.p.y - player.p.cy}, function(reply){ 209 | var y = reply.y; 210 | var clientY = player.p.y - player.p.cy; 211 | //fix position from server if the difference is too high 212 | if (Math.abs(y - clientY) > 20) { 213 | player.p.y = y + player.p.cy; 214 | } 215 | }); 216 | } 217 | } 218 | 219 | 220 | window.onload = function() { 221 | window.controller = new Controller(); 222 | } 223 | -------------------------------------------------------------------------------- /src/main/resources/www/images/black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalboska/codingbeer-vertx/afc0dc060ee460511932ec52c740fd616995cf27/src/main/resources/www/images/black.png -------------------------------------------------------------------------------- /src/main/resources/www/images/green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalboska/codingbeer-vertx/afc0dc060ee460511932ec52c740fd616995cf27/src/main/resources/www/images/green.png -------------------------------------------------------------------------------- /src/main/resources/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hello page 6 | 7 | 8 | 9 | 10 | 11 |

Pong real-time vert.x game

12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/main/resources/www/vertxbus.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2011-2012 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | var vertx = vertx || {}; 18 | 19 | !function(factory) { 20 | if (typeof define === "function" && define.amd) { 21 | // Expose as an AMD module with SockJS dependency. 22 | // "vertxbus" and "sockjs" names are used because 23 | // AMD module names are derived from file names. 24 | define("vertxbus", ["sockjs"], factory); 25 | } else { 26 | // No AMD-compliant loader 27 | factory(SockJS); 28 | } 29 | }(function(SockJS) { 30 | 31 | vertx.EventBus = function(url, options) { 32 | 33 | var that = this; 34 | var sockJSConn = new SockJS(url, undefined, options); 35 | var handlerMap = {}; 36 | var replyHandlers = {}; 37 | var state = vertx.EventBus.CONNECTING; 38 | var sessionID = null; 39 | var pingTimerID = null; 40 | 41 | that.onopen = null; 42 | that.onclose = null; 43 | 44 | that.login = function(username, password, replyHandler) { 45 | sendOrPub("send", 'vertx.basicauthmanager.login', {username: username, password: password}, function(reply) { 46 | if (reply.status === 'ok') { 47 | that.sessionID = reply.sessionID; 48 | } 49 | if (replyHandler) { 50 | delete reply.sessionID; 51 | replyHandler(reply) 52 | } 53 | }); 54 | } 55 | 56 | that.send = function(address, message, replyHandler) { 57 | sendOrPub("send", address, message, replyHandler) 58 | } 59 | 60 | that.publish = function(address, message, replyHandler) { 61 | sendOrPub("publish", address, message, replyHandler) 62 | } 63 | 64 | that.registerHandler = function(address, handler) { 65 | checkSpecified("address", 'string', address); 66 | checkSpecified("handler", 'function', handler); 67 | checkOpen(); 68 | var handlers = handlerMap[address]; 69 | if (!handlers) { 70 | handlers = [handler]; 71 | handlerMap[address] = handlers; 72 | // First handler for this address so we should register the connection 73 | var msg = { type : "register", 74 | address: address }; 75 | sockJSConn.send(JSON.stringify(msg)); 76 | } else { 77 | handlers[handlers.length] = handler; 78 | } 79 | } 80 | 81 | that.unregisterHandler = function(address, handler) { 82 | checkSpecified("address", 'string', address); 83 | checkSpecified("handler", 'function', handler); 84 | checkOpen(); 85 | var handlers = handlerMap[address]; 86 | if (handlers) { 87 | var idx = handlers.indexOf(handler); 88 | if (idx != -1) handlers.splice(idx, 1); 89 | if (handlers.length == 0) { 90 | // No more local handlers so we should unregister the connection 91 | 92 | var msg = { type : "unregister", 93 | address: address}; 94 | sockJSConn.send(JSON.stringify(msg)); 95 | delete handlerMap[address]; 96 | } 97 | } 98 | } 99 | 100 | that.close = function() { 101 | checkOpen(); 102 | if (pingTimerID) clearInterval(pingTimerID); 103 | state = vertx.EventBus.CLOSING; 104 | sockJSConn.close(); 105 | } 106 | 107 | that.readyState = function() { 108 | return state; 109 | } 110 | 111 | sockJSConn.onopen = function() { 112 | // Send the first ping then send a ping every 5 seconds 113 | sendPing(); 114 | pingTimerID = setInterval(sendPing, 5000); 115 | state = vertx.EventBus.OPEN; 116 | if (that.onopen) { 117 | that.onopen(); 118 | } 119 | }; 120 | 121 | sockJSConn.onclose = function() { 122 | state = vertx.EventBus.CLOSED; 123 | if (that.onclose) { 124 | that.onclose(); 125 | } 126 | }; 127 | 128 | sockJSConn.onmessage = function(e) { 129 | var msg = e.data; 130 | var json = JSON.parse(msg); 131 | var body = json.body; 132 | var replyAddress = json.replyAddress; 133 | var address = json.address; 134 | var replyHandler; 135 | if (replyAddress) { 136 | replyHandler = function(reply, replyHandler) { 137 | // Send back reply 138 | that.send(replyAddress, reply, replyHandler); 139 | }; 140 | } 141 | var handlers = handlerMap[address]; 142 | if (handlers) { 143 | // We make a copy since the handler might get unregistered from within the 144 | // handler itself, which would screw up our iteration 145 | var copy = handlers.slice(0); 146 | for (var i = 0; i < copy.length; i++) { 147 | copy[i](body, replyHandler); 148 | } 149 | } else { 150 | // Might be a reply message 151 | var handler = replyHandlers[address]; 152 | if (handler) { 153 | delete replyHandlers[address]; 154 | handler(body, replyHandler); 155 | } 156 | } 157 | } 158 | 159 | function sendPing() { 160 | var msg = { 161 | type: "ping" 162 | } 163 | sockJSConn.send(JSON.stringify(msg)); 164 | } 165 | 166 | function sendOrPub(sendOrPub, address, message, replyHandler) { 167 | checkSpecified("address", 'string', address); 168 | checkSpecified("replyHandler", 'function', replyHandler, true); 169 | checkOpen(); 170 | var envelope = { type : sendOrPub, 171 | address: address, 172 | body: message }; 173 | if (that.sessionID) { 174 | envelope.sessionID = that.sessionID; 175 | } 176 | if (replyHandler) { 177 | var replyAddress = makeUUID(); 178 | envelope.replyAddress = replyAddress; 179 | replyHandlers[replyAddress] = replyHandler; 180 | } 181 | var str = JSON.stringify(envelope); 182 | sockJSConn.send(str); 183 | } 184 | 185 | function checkOpen() { 186 | if (state != vertx.EventBus.OPEN) { 187 | throw new Error('INVALID_STATE_ERR'); 188 | } 189 | } 190 | 191 | function checkSpecified(paramName, paramType, param, optional) { 192 | if (!optional && !param) { 193 | throw new Error("Parameter " + paramName + " must be specified"); 194 | } 195 | if (param && typeof param != paramType) { 196 | throw new Error("Parameter " + paramName + " must be of type " + paramType); 197 | } 198 | } 199 | 200 | function isFunction(obj) { 201 | return !!(obj && obj.constructor && obj.call && obj.apply); 202 | } 203 | 204 | function makeUUID(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" 205 | .replace(/[xy]/g,function(a,b){return b=Math.random()*16,(a=="y"?b&3|8:b|0).toString(16)})} 206 | 207 | } 208 | 209 | vertx.EventBus.CONNECTING = 0; 210 | vertx.EventBus.OPEN = 1; 211 | vertx.EventBus.CLOSING = 2; 212 | vertx.EventBus.CLOSED = 3; 213 | 214 | return vertx.EventBus; 215 | 216 | }); 217 | -------------------------------------------------------------------------------- /src/test/java/ch/erni/beer/vertx/unit/README.txt: -------------------------------------------------------------------------------- 1 | Put your unit tests in here. 2 | 3 | Unit tests talk directly to your project test classes and aren't run inside the Vert.x container. -------------------------------------------------------------------------------- /vertx.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/michalboska/codingbeer-vertx/afc0dc060ee460511932ec52c740fd616995cf27/vertx.pptx -------------------------------------------------------------------------------- /vertx_classpath.txt: -------------------------------------------------------------------------------- 1 | # This file contains information on where to find the resources of your module during development 2 | # This file is used when running your module as you develop - it tells Vert.x where to find 3 | # the resources of your module 4 | 5 | # Feel free to edit it if you have a non standard project structure and put the resources of your 6 | # module elsewhere 7 | 8 | src/main/resources 9 | target/classes 10 | target/dependencies 11 | bin --------------------------------------------------------------------------------