From 089586946094cc1b7b9ae977d89b7afd20991192 Mon Sep 17 00:00:00 2001
From: Andrea Cavalli
Date: Thu, 23 May 2024 14:48:50 +0200
Subject: [PATCH] First commit
---
.editorconfig | 11 ++
Dockerfile | 10 ++
README.adoc | 24 ++++
pom.xml | 122 ++++++++++++++++++
.../java/it/cavallium/connect4x4/App.java | 15 +++
.../it/cavallium/connect4x4/MainVerticle.java | 55 ++++++++
.../resources/webroot/static/connect4x4.js | 118 +++++++++++++++++
src/main/resources/webroot/static/index.html | 17 +++
src/main/resources/webroot/static/style.css | 23 ++++
.../connect4x4/TestMainVerticle.java | 22 ++++
10 files changed, 417 insertions(+)
create mode 100644 .editorconfig
create mode 100644 Dockerfile
create mode 100644 README.adoc
create mode 100644 pom.xml
create mode 100644 src/main/java/it/cavallium/connect4x4/App.java
create mode 100644 src/main/java/it/cavallium/connect4x4/MainVerticle.java
create mode 100644 src/main/resources/webroot/static/connect4x4.js
create mode 100644 src/main/resources/webroot/static/index.html
create mode 100644 src/main/resources/webroot/static/style.css
create mode 100644 src/test/java/it/cavallium/connect4x4/TestMainVerticle.java
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..3003493
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,11 @@
+# EditorConfig is awesome: https://EditorConfig.org
+
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 2
+trim_trailing_whitespace = true
+end_of_line = lf
+insert_final_newline = true
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..7e3dbbd
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,10 @@
+FROM maven:3-eclipse-temurin-22 AS build
+WORKDIR /build
+COPY --link src src
+COPY --link pom.xml pom.xml
+RUN mvn package
+
+FROM eclipse-temurin:22-jdk-alpine
+WORKDIR /app
+COPY --from=build --link /build/target/connect4x4-fat.jar app.jar
+ENTRYPOINT ["java", "-jar", "app.jar"]
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..b05ec9b
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,24 @@
+= Connect4x4
+
+image:https://img.shields.io/badge/vert.x-4.5.7-purple.svg[link="https://vertx.io"]
+
+== Building
+
+To launch your tests:
+```
+mvn clean test
+```
+
+To package your application:
+```
+mvn clean package
+```
+
+To run your application:
+```
+mvn clean compile exec:java
+```
+
+== Help
+
+* https://vertx.io/docs/[Vert.x Documentation]
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..182986c
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+ it.cavallium
+ connect4x4
+ 1.0.0-SNAPSHOT
+
+
+ UTF-8
+
+ 3.8.1
+ 3.2.4
+ 2.22.2
+ 3.0.0
+
+ 4.5.7
+ 5.10.2
+
+ it.cavallium.connect4x4.MainVerticle
+ io.vertx.core.Launcher
+
+
+
+
+
+ io.vertx
+ vertx-stack-depchain
+ ${vertx.version}
+ pom
+ import
+
+
+
+
+
+
+ io.vertx
+ vertx-web
+
+
+ io.vertx
+ vertx-rx-java3
+
+
+
+ io.vertx
+ vertx-junit5
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ ${junit-jupiter.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ ${junit-jupiter.version}
+ test
+
+
+
+
+
+
+ maven-compiler-plugin
+ ${maven-compiler-plugin.version}
+
+ 17
+
+
+
+ maven-shade-plugin
+ ${maven-shade-plugin.version}
+
+
+ package
+
+ shade
+
+
+
+
+
+ ${launcher.class}
+ ${main.verticle}
+
+
+
+
+ ${project.build.directory}/${project.artifactId}-fat.jar
+
+
+
+
+
+
+ maven-surefire-plugin
+ ${maven-surefire-plugin.version}
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ ${exec-maven-plugin.version}
+
+ io.vertx.core.Launcher
+
+ run
+ ${main.verticle}
+
+
+
+
+
+
+
+
diff --git a/src/main/java/it/cavallium/connect4x4/App.java b/src/main/java/it/cavallium/connect4x4/App.java
new file mode 100644
index 0000000..b0929f3
--- /dev/null
+++ b/src/main/java/it/cavallium/connect4x4/App.java
@@ -0,0 +1,15 @@
+package it.cavallium.connect4x4;
+
+import io.vertx.core.DeploymentOptions;
+import io.vertx.core.ThreadingModel;
+import io.vertx.core.Vertx;
+import io.vertx.core.VertxOptions;
+
+public class App {
+
+ public static void main(String[] args) throws InterruptedException {
+ var vertx = Vertx.builder().with(new VertxOptions().setPreferNativeTransport(true)).build();
+ vertx.deployVerticle(new MainVerticle(), new DeploymentOptions().setThreadingModel(ThreadingModel.VIRTUAL_THREAD));
+ Thread.currentThread().join();
+ }
+}
diff --git a/src/main/java/it/cavallium/connect4x4/MainVerticle.java b/src/main/java/it/cavallium/connect4x4/MainVerticle.java
new file mode 100644
index 0000000..ce83253
--- /dev/null
+++ b/src/main/java/it/cavallium/connect4x4/MainVerticle.java
@@ -0,0 +1,55 @@
+package it.cavallium.connect4x4;
+
+import io.reactivex.rxjava3.core.Completable;
+import io.reactivex.rxjava3.core.Single;
+import io.vertx.ext.bridge.PermittedOptions;
+import io.vertx.ext.web.handler.FileSystemAccess;
+import io.vertx.ext.web.handler.sockjs.SockJSBridgeOptions;
+import io.vertx.rxjava3.core.AbstractVerticle;
+import io.vertx.rxjava3.core.http.HttpServer;
+import io.vertx.rxjava3.ext.web.Router;
+import io.vertx.rxjava3.ext.web.handler.StaticHandler;
+import io.vertx.rxjava3.ext.web.handler.sockjs.SockJSHandler;
+import java.time.Duration;
+
+public class MainVerticle extends AbstractVerticle {
+
+ private Single httpServer;
+
+ @Override
+ public Completable rxStart() {
+
+ vertx.eventBus().consumer("connect4x4.move")
+ .handler(event -> System.out.println("Received: " + event.body()));
+
+ var server = vertx.createHttpServer();
+
+ SockJSHandler sockJSHandler = SockJSHandler.create(vertx);
+ SockJSBridgeOptions sockJSBridgeOptions = new SockJSBridgeOptions()
+ .addInboundPermitted(new PermittedOptions().setAddress("connect4x4.move"))
+ .addOutboundPermitted(new PermittedOptions().setAddress("connect4x4.moved"));
+
+ Router router = Router.router(vertx);
+ router
+ .get("/*")
+ .handler(StaticHandler
+ .create(FileSystemAccess.RELATIVE,"webroot")
+ //.setDirectoryListing(true)
+ .setCachingEnabled(true)
+ .setAlwaysAsyncFS(true)
+ .setIndexPage("static/index.html")
+ .setSendVaryHeader(true)
+ .setEnableRangeSupport(true)
+ .setMaxAgeSeconds(Duration.ofSeconds(1).toSeconds()));
+
+ router.get("/eventbus/*").subRouter(sockJSHandler.bridge(sockJSBridgeOptions));
+
+ this.httpServer = server.requestHandler(router).listen(8081).cache();
+ return httpServer.ignoreElement();
+ }
+
+ @Override
+ public Completable rxStop() {
+ return httpServer.flatMapCompletable(HttpServer::close);
+ }
+}
diff --git a/src/main/resources/webroot/static/connect4x4.js b/src/main/resources/webroot/static/connect4x4.js
new file mode 100644
index 0000000..b84bb44
--- /dev/null
+++ b/src/main/resources/webroot/static/connect4x4.js
@@ -0,0 +1,118 @@
+const chars = ["AN","AR","AT","CA","CH","CO","DE","DI","EL","EN","ER","ES","HE","IA","IL","IN","IO","LA","LE","LI","LL","MA","ME","NA","NE","NO","NT","OL","ON","OR","PE","RA","RE","RI","RO","SE","SI","SO","ST","TA","TE","TI","TO","TR","TT","UN","TO","RE","ER","ON","CO","DI","TA","EN","IN","TE","AT","RA","AN","NO","NT","ST","LA","AR","AL","OR","CH","RI","TI","IO","LE","DE","ES","NE","ME","TT","EL","PE","IL","UN","IA","LI","SE","SO","LL","SI","OL","RO","MA","CA","NA","TR","HE","ALE","ALL","ANC","AND","ANT","ARE","ATO","ATT","CHE","CHI","COM","CON","DEL","ELL","ENT","ERA","ERE","ESS","EST","ETT","GLI","ION","LLA","MEN","NON","NTE","NTI","NTO","OLO","ONE","ONO","PER","QUE","SON","STA","STO","TAT","TRA","TTO","UNA","VER","ZIO","ENT","CHE","ATO","PER","NTE","CON","ELL","STA","ARE","MEN","ION","DEL","LLA","TTO","TAT","ESS","ERE","ETT","EST","ONE","ONO","ZIO","NON","ERA","CHI","GLI","COM","TRA","STO","NTI","SON","VER","ATT","UNA","QUE","NTO","AND","ALL","OLO","ANC","ANT","ALE"];
+function randomID() {
+ var u8 = crypto.getRandomValues(new Uint8Array(3));
+ return Array.from(u8).map(n => chars[n % chars.length]).reduce((a, b) => a + b);
+}
+
+document.addEventListener("DOMContentLoaded", e => {
+
+ const gameCode = document.getElementById("game-code");
+ const gameCodeLink = document.getElementById("game-code-link");
+ const newGameLink = document.getElementById("new-game-link");
+ const mainBody = document.getElementById("main-body");
+
+ const eb = new EventBus(`${document.location.origin}/eventbus`);
+ eb.enableReconnect(true);
+ eb.onopen = function() {}; // Set up handlers here, will be called on initial connection and all reconnections
+ eb.onreconnect = function() {}; // Optional, will only be called on reconnections
+
+ // Alternatively, pass in an options object
+ var options = {
+ vertxbus_reconnect_attempts_max: Infinity, // Max reconnect attempts
+ vertxbus_reconnect_delay_min: 1000, // Initial delay (in ms) before first reconnect attempt
+ vertxbus_reconnect_delay_max: 5000, // Max delay (in ms) between reconnect attempts
+ vertxbus_reconnect_exponent: 2, // Exponential backoff factor
+ vertxbus_randomization_factor: 0.5 // Randomization factor between 0 and 1
+ };
+
+
+ const connect4x4 = document.connect4x4 = {
+ clientId: randomID(),
+ gameId: null
+ }
+
+ window.addEventListener("hashchange", e=> {
+ onHashChange(e.target.location.hash);
+ })
+
+ onHashChange(document.location.hash);
+
+ function onHashChange(hash) {
+ if (hash != null && hash.length > 0) {
+ hash = hash.substring(1);
+ } else {
+ hash = "";
+ }
+ if (hash.length === 0) {
+ if (connect4x4.gameId == null) {
+ onGameIdChange(randomID());
+ } else {
+ console.warn("Game hash unchanged.");
+ }
+ } else if (hash.length > 32) {
+ console.error("Invalid page hash length", hash);
+ document.body.innerHTML = `Invalid page hash length
Go back
`;
+ document.getElementById("hash-text").innerText = hash;
+ const redirectLink = document.location.origin;
+ document.getElementById("redirect-link").href = redirectLink;
+ setTimeout(() => {
+ document.location.href = redirectLink;
+ }, 5000);
+ } else if (connect4x4.gameId !== hash) {
+ if (connect4x4.gameId != null) {
+ console.log("Game id changed, reloading...");
+ document.location.reload();
+ } else {
+ onGameIdChange(hash);
+ }
+ } else {
+ console.warn("Game hash unchanged.");
+ }
+ }
+
+ function onGameIdChange(gameId) {
+ connect4x4.gameId = gameId;
+ console.info("Game ID", gameId);
+ gameCode.innerText = gameId;
+ const gameLink = `${document.location.origin}#${gameId}`;
+ document.location.hash = gameId;
+ gameCodeLink.href = gameLink;
+ gameCodeLink.onclick = async e => {
+ if (navigator.share !== undefined) {
+ e.preventDefault();
+ await navigator.share({
+ title: "Connect 4x4",
+ text: "Play Connect 4x4 game with me",
+ url: gameLink
+ }).then(() => console.log("Shared!")).catch(err => {
+ console.error("Share failed", err);
+ navigateToGame(gameId);
+ });
+ } else {
+ navigateToGame(gameId);
+ }
+ }
+ onGameLoad();
+ }
+
+ function navigateToGame(gameId) {
+ document.location.hash = gameId;
+ }
+
+ function onGameLoad() {
+ mainBody.classList.add("js-loaded");
+ }
+
+ eb.onopen = () => {
+
+// set a handler to receive a message
+ eb.registerHandler('connect4x4.moved', (error, message) => {
+ console.log('received a message: ' + JSON.stringify(message));
+ });
+
+// send a message
+ eb.send('connect4x4.move', {name: 'tim', age: 587});
+
+ }
+})
+
diff --git a/src/main/resources/webroot/static/index.html b/src/main/resources/webroot/static/index.html
new file mode 100644
index 0000000..cb4516b
--- /dev/null
+++ b/src/main/resources/webroot/static/index.html
@@ -0,0 +1,17 @@
+
+
+
+ Connect 4x4
+
+
+
+
+
+
+
+
+
Connect 4x4
+
Game code: [share] [new game]
+
+
+
diff --git a/src/main/resources/webroot/static/style.css b/src/main/resources/webroot/static/style.css
new file mode 100644
index 0000000..ee76bcb
--- /dev/null
+++ b/src/main/resources/webroot/static/style.css
@@ -0,0 +1,23 @@
+body {
+ font-family: sans-serif;
+}
+
+h1 {
+ user-select: none;
+}
+
+#main-body:not(.js-loaded) {
+ visibility: hidden;
+}
+
+#game-code {
+ white-space: pre;
+ font-family: monospace;
+ user-select: all;
+ background: #ffeb3b63;
+ color: black;
+ border-radius: 5px;
+ padding: 2px 5px;
+ font-size: 75%;
+ font-weight: bold;
+}
diff --git a/src/test/java/it/cavallium/connect4x4/TestMainVerticle.java b/src/test/java/it/cavallium/connect4x4/TestMainVerticle.java
new file mode 100644
index 0000000..2f8617c
--- /dev/null
+++ b/src/test/java/it/cavallium/connect4x4/TestMainVerticle.java
@@ -0,0 +1,22 @@
+package it.cavallium.connect4x4;
+
+import io.vertx.core.Vertx;
+import io.vertx.junit5.VertxExtension;
+import io.vertx.junit5.VertxTestContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+
+@ExtendWith(VertxExtension.class)
+public class TestMainVerticle {
+
+ @BeforeEach
+ void deploy_verticle(Vertx vertx, VertxTestContext testContext) {
+ vertx.deployVerticle(new MainVerticle(), testContext.succeeding(id -> testContext.completeNow()));
+ }
+
+ @Test
+ void verticle_deployed(Vertx vertx, VertxTestContext testContext) throws Throwable {
+ testContext.completeNow();
+ }
+}