package camelinaction; import java.io.InputStream; import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; import io.vertx.core.AbstractVerticle; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.StaticHandler; import io.vertx.ext.web.handler.sockjs.BridgeEventType; import io.vertx.ext.web.handler.sockjs.BridgeOptions; import io.vertx.ext.web.handler.sockjs.PermittedOptions; import io.vertx.ext.web.handler.sockjs.SockJSHandler; /** * Vert.x verticle for live scores. */ public class LiveScoreVerticle extends AbstractVerticle { // to use fast mode where each 5 second is a minute private boolean fastMode = true; private final AtomicInteger gameTime = new AtomicInteger(); private final AtomicBoolean clockRunning = new AtomicBoolean(); @Override public void start() throws Exception { // create a vertx router to setup websocket and http server Router router = Router.router(vertx); // configure allowed inbound and outbound traffics BridgeOptions options = new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("control")) .addOutboundPermitted(new PermittedOptions().setAddress("clock")) .addOutboundPermitted(new PermittedOptions().setAddress("games")) .addOutboundPermitted(new PermittedOptions().setAddress("goals")); // route websocket to vertx router.route("/eventbus/*").handler(SockJSHandler.create(vertx).bridge(options, event -> { if (event.type() == BridgeEventType.SOCKET_CREATED) { System.out.println("Websocket connection created"); // a new client connected so setup its screen with the list of games vertx.setTimer(500, h -> initGames()); } else if (event.type() == BridgeEventType.SOCKET_CLOSED) { System.out.println("Websocket connection closed"); } event.complete(true); })); // serve the static resources (src/main/resources/webroot) router.route().handler(StaticHandler.create()); // let router accept on port 8080 System.out.println("Listening on http://localhost:8080"); vertx.createHttpServer().requestHandler(router::accept).listen(8080); // init control buttons initControls(); // start streaming live score streamLiveScore(); } private void initControls() { vertx.eventBus().localConsumer("control", h -> { String action = (String) h.body(); if ("start".equals(action)) { System.out.println("Starting clock"); clockRunning.set(true); vertx.eventBus().publish("clock", gameTime.get() + ":00"); } else if ("stop".equals(action)) { System.out.println("Stopping clock"); clockRunning.set(false); vertx.eventBus().publish("clock", "Stopped"); } }); } private void initGames() { try { // load list of games from file InputStream is = LiveScoreVerticle.class.getClassLoader().getResourceAsStream("games.csv"); String text = IOHelper.loadText(is); Stream<String> games = Arrays.stream(text.split("\n")); // split each line and publish to vertx eventbus games.forEach(game -> vertx.eventBus().publish("games", game)); } catch (Exception e) { System.out.println("Error reading games.csv file due " + e.getMessage()); } // publish clock time also if (clockRunning.get()) { vertx.eventBus().publish("clock", gameTime.get() + ":00"); } else { vertx.eventBus().publish("clock", "Stopped"); } } private void streamLiveScore() { List<String> lines; try { // read the goal scores from file InputStream is = LiveScoreVerticle.class.getClassLoader().getResourceAsStream("goals.csv"); String text = IOHelper.loadText(is); Stream<String> goals = Arrays.stream(text.split("\n")); // sort goals scored on minutes goals = goals.sorted((a, b) -> goalTime(a).compareTo(goalTime(b))); // store goals in a list lines = goals.collect(Collectors.toList()); } catch (Exception e) { System.out.println("Error reading goals.csv file due " + e.getMessage()); return; } int time = fastMode ? 5 * 1000 : 60 * 1000; System.out.println("Publishing game clock"); vertx.setPeriodic(time, event -> { if (!clockRunning.get()) { // show clock as stopped vertx.eventBus().publish("clock", "Stopped"); return; } int min = gameTime.incrementAndGet(); if (min > 92) { return; } System.out.println("Game time " + min + ":00"); // publish game time vertx.eventBus().publish("clock", min + ":00"); }); System.out.println("Publishing live score"); vertx.setPeriodic(time, event -> { if (!clockRunning.get()) { return; } int min = gameTime.get(); if (min > 92) { return; } // stream all goals for the current game time List<String> goals = lines.stream().filter(next -> goalTime(next) == gameTime.get()).collect(Collectors.toList()); if (goals.isEmpty()) { vertx.eventBus().publish("goals", "empty"); } else { // to remember delay between each goal AtomicInteger delay = new AtomicInteger(); int initial = fastMode ? 1 : 5 * 1000; delay.set(initial); goals.forEach(c -> { // for each goal then publish them to the goals eventbus // but simulate some delay between each goal so they are not all published at the same time // there are sometimes more goals so wait 5 sec between each goal System.out.println("Publish goal in " + delay.get() + " msec for goal: " + c); vertx.setTimer(delay.get(), t -> vertx.eventBus().publish("goals", c)); // delay between 8 - 12 sec for next goal int extra = fastMode ? 2000 : 8000 + new Random().nextInt(4000); delay.set(delay.get() + extra); }); } }); } private static Integer goalTime(String line) { return Integer.valueOf(line.split(",")[1]); } }