First commit
This commit is contained in:
parent
83e81faf78
commit
0895869460
11
.editorconfig
Normal file
11
.editorconfig
Normal file
@ -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
|
10
Dockerfile
Normal file
10
Dockerfile
Normal file
@ -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"]
|
24
README.adoc
Normal file
24
README.adoc
Normal file
@ -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]
|
122
pom.xml
Normal file
122
pom.xml
Normal file
@ -0,0 +1,122 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>it.cavallium</groupId>
|
||||
<artifactId>connect4x4</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
||||
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
|
||||
<maven-shade-plugin.version>3.2.4</maven-shade-plugin.version>
|
||||
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
|
||||
<exec-maven-plugin.version>3.0.0</exec-maven-plugin.version>
|
||||
|
||||
<vertx.version>4.5.7</vertx.version>
|
||||
<junit-jupiter.version>5.10.2</junit-jupiter.version>
|
||||
|
||||
<main.verticle>it.cavallium.connect4x4.MainVerticle</main.verticle>
|
||||
<launcher.class>io.vertx.core.Launcher</launcher.class>
|
||||
</properties>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-stack-depchain</artifactId>
|
||||
<version>${vertx.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-rx-java3</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-junit5</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>${junit-jupiter.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>${maven-compiler-plugin.version}</version>
|
||||
<configuration>
|
||||
<release>17</release>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>${maven-shade-plugin.version}</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<transformers>
|
||||
<transformer
|
||||
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
|
||||
<manifestEntries>
|
||||
<Main-Class>${launcher.class}</Main-Class>
|
||||
<Main-Verticle>${main.verticle}</Main-Verticle>
|
||||
</manifestEntries>
|
||||
</transformer>
|
||||
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
||||
</transformers>
|
||||
<outputFile>${project.build.directory}/${project.artifactId}-fat.jar
|
||||
</outputFile>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>${maven-surefire-plugin.version}</version>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>exec-maven-plugin</artifactId>
|
||||
<version>${exec-maven-plugin.version}</version>
|
||||
<configuration>
|
||||
<mainClass>io.vertx.core.Launcher</mainClass>
|
||||
<arguments>
|
||||
<argument>run</argument>
|
||||
<argument>${main.verticle}</argument>
|
||||
</arguments>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
||||
</project>
|
15
src/main/java/it/cavallium/connect4x4/App.java
Normal file
15
src/main/java/it/cavallium/connect4x4/App.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
55
src/main/java/it/cavallium/connect4x4/MainVerticle.java
Normal file
55
src/main/java/it/cavallium/connect4x4/MainVerticle.java
Normal file
@ -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> 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);
|
||||
}
|
||||
}
|
118
src/main/resources/webroot/static/connect4x4.js
Normal file
118
src/main/resources/webroot/static/connect4x4.js
Normal file
@ -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 = `<h1>Invalid page hash length</h1><p><pre><code id="hash-text"></code></pre></p><p><a id="redirect-link">Go back</a></p>`;
|
||||
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});
|
||||
|
||||
}
|
||||
})
|
||||
|
17
src/main/resources/webroot/static/index.html
Normal file
17
src/main/resources/webroot/static/index.html
Normal file
@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Connect 4x4</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
<script src="https://unpkg.io/sockjs-client@1.5.0/dist/sockjs.min.js"></script>
|
||||
<script src='https://unpkg.io/@vertx/eventbus-bridge-client.js@1.0.0-1/vertx-eventbus.js'></script>
|
||||
<script src='/static/connect4x4.js'></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-body">
|
||||
<h1>Connect 4x4</h1>
|
||||
<p>Game code: <span id="game-code"></span> <a id="game-code-link" href="#">[share]</a> <a id="new-game" href="/">[new game]</a></p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
23
src/main/resources/webroot/static/style.css
Normal file
23
src/main/resources/webroot/static/style.css
Normal file
@ -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;
|
||||
}
|
22
src/test/java/it/cavallium/connect4x4/TestMainVerticle.java
Normal file
22
src/test/java/it/cavallium/connect4x4/TestMainVerticle.java
Normal file
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user