13 Commits

Author SHA1 Message Date
Valentin Heiserer
ea5df95228 edited a lot of stuff (#52) 2024-04-26 18:54:39 +00:00
Valentin Heiserer
539e29dc56 Make fit for beta release (#51)
* removed

* edited a lot of stuff
2024-04-25 23:41:50 +00:00
Valentin Heiserer
2e5a42b6d3 edited stuff (#50) 2024-04-23 21:27:01 +00:00
Valentin Heiserer
b8b89ee696 Update relaseNewVersion.yml (#49) 2024-04-23 21:25:42 +00:00
Valentin Heiserer
3689a694d4 fixed bug (#48) 2024-04-23 20:37:43 +00:00
Valentin Heiserer
6ff26f3407 Update vite.config.ts (#47) 2024-04-23 20:16:19 +00:00
Valentin Heiserer
2cd8359518 Message types and server2 (#46)
* fixed building issue

* fixed building issue
2024-04-23 22:09:22 +02:00
Valentin Heiserer
a0a1cfaa4a Message types and server (#44)
* message type and handling

* deleted web-content and fixed bug

* edited main page
2024-04-23 21:54:31 +02:00
Valentin Heiserer
cab2d36f48 refactored a lot and added possibility to play games on DedicatedServer (#43) 2024-04-19 16:15:51 +02:00
Valentin Heiserer
6259d0bef3 added arduino nfcReader files (#42) 2024-04-17 22:49:50 +00:00
Valentin Heiserer
76cb0eaf1a Update relaseNewVersion.yml (#41) 2024-04-17 22:29:19 +00:00
Valentin Heiserer
c94127acf1 split Project into multiple Modules to prepare for development of Servers 2024-04-18 00:26:11 +02:00
Manuel Weber
949c00bb28 updated cards to be more conostent and have a smaller footprint (#39)
* updated cards to be more conostent and have a smaller footprint

---------

Co-authored-by: Valentin Heiserer <73257760+Vale54321@users.noreply.github.com>
2024-04-17 22:04:29 +02:00
186 changed files with 3240 additions and 1001 deletions

View File

@@ -12,6 +12,23 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Install Frontend dependencies
run: npm install
working-directory: Frontend
- name: Create .env file
run: echo "VITE_APP_WEBSOCKET_IP=localhost" > .env
working-directory: Frontend
- name: Build Frontend
run: npm run build
working-directory: Frontend
- name: Set up JDK 21
uses: actions/setup-java@v4

View File

@@ -26,16 +26,9 @@ jobs:
run: npm install
working-directory: Frontend
- name: Create .env file
run: echo "VITE_APP_WEBSOCKET_IP=localhost" > .env
working-directory: Frontend
- name: Build Frontend
run: npm run build
working-directory: Frontend
- name: Copy built Frontend
run: rm -r Backend/src/main/resources/web-content/ && mv Frontend/dist Backend/src/main/resources/web-content
- name: Set up JDK 21
uses: actions/setup-java@v4
@@ -55,12 +48,14 @@ jobs:
- name: Archive dist folder
# run: mv Backend/target/schafkopf-backend-build-jar-with-dependencies.jar schafkopf-bot.jar && zip -r dist.zip schafkopf-bot.jar
run: mv Backend/target/schafkopf-backend-build-jar-with-dependencies.jar schafkopf-bot.jar
run: mv Backend/schafkopf-client/target/schafkopf-client-build-jar-with-dependencies.jar schafkopf-bot-client_${{ github.ref_name }}.jar && mv Backend/schafkopf-server/target/schafkopf-server-build-jar-with-dependencies.jar schafkopf-bot-server_${{ github.ref_name }}.jar
- name: Release
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
with:
files: schafkopf-bot.jar
files: |
schafkopf-bot-client_${{ github.ref_name }}.jar
schafkopf-bot-server_${{ github.ref_name }}.jar
token: ${{ secrets.PAT }}

View File

@@ -0,0 +1,41 @@
#include <Wire.h>
#include <Adafruit_PN532.h>
#define SDA_PIN 2
#define SCL_PIN 1
Adafruit_PN532 nfc(SDA_PIN, SCL_PIN);
void setup(void) {
Serial.begin(115200);
nfc.begin();
uint32_t versiondata = nfc.getFirmwareVersion();
if (!versiondata) {
Serial.print("Didn't find PN53x board");
while (1);
}
nfc.SAMConfig();
// Send a marker string to identify the device
Serial.println("Adafruit PN532 NFC Marker");
}
void loop(void) {
uint8_t success;
uint8_t uid[] = { 0, 0, 0, 0, 0, 0, 0 };
uint8_t uidLength;
success = nfc.readPassiveTargetID(PN532_MIFARE_ISO14443A, uid, &uidLength);
if (success) {
for (uint8_t i = 0; i < uidLength; i++) {
String hexString = (uid[i] < 0x10 ? "0" : "") + String(uid[i], HEX);
hexString.toUpperCase();
Serial.print(hexString);
}
Serial.println("");
delay(1000);
}
}

View File

@@ -4,7 +4,13 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<artifactId>schafkopf-backend-java</artifactId>
<groupId>org.example</groupId>
<packaging>pom</packaging>
<modules>
<module>schafkopf-client</module>
<module>schafkopf-server</module>
<module>schafkopf-shared</module>
</modules>
<groupId>org.schafkopf</groupId>
<modelVersion>4.0.0</modelVersion>
<version>1.0-SNAPSHOT</version>
@@ -20,11 +26,6 @@
<build>
<finalName>schafkopf-backend-build</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<!-- Maven Clean Plugin -->
<plugin>
@@ -65,7 +66,7 @@
<configuration>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
<mainClass>org.schafkopf.DedicatedServer</mainClass>
</manifest>
</archive>
</configuration>
@@ -82,29 +83,6 @@
</plugin>
<!-- Maven Assembly Plugin -->
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
@@ -157,15 +135,11 @@
<groupId>io.github.cdimascio</groupId>
<version>3.0.0</version>
</dependency>
<!-- Jackson for JSON serialization/deserialization -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>22</version>
</dependency>
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-web</artifactId>
<version>22</version>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,60 @@
<?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>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>schafkopf-client</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-shared</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>schafkopf-client-build</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.SchafkopfClient</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,139 @@
package org.schafkopf;
import jakarta.servlet.DispatcherType;
import java.awt.Desktop;
import java.net.URI;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/**
* Main Class that represents the Backend Server.
*/
public class BackendServer implements MessageSender {
private final Server server;
private final List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
/**
* Creates an Instance of the Backend Server.
*/
public BackendServer(String hostName, int port, boolean openFrontend,
MessageListener messageListener) throws Exception {
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setHost(hostName);
connector.setPort(port);
server.addConnector(connector);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// Configure CORS settings
configureCors(context);
URL webContentUrl = getClass().getClassLoader().getResource("web-content");
if (webContentUrl == null) {
throw new RuntimeException("Unable to find 'web-content' directory");
}
String webContentPath = webContentUrl.toExternalForm();
context.setResourceBase(webContentPath);
context.addServlet(new ServletHolder("frontend", DefaultServlet.class), "/");
// Configure specific websocket behavior
JettyWebSocketServletContainerInitializer.configure(
context,
(servletContext, wsContainer) -> {
// Configure default max size
wsContainer.setMaxTextMessageSize(65535);
wsContainer.setIdleTimeout(Duration.ofDays(300000));
// Add websockets
wsContainer.addMapping("/schafkopf-events/*",
new FrontendEndpointCreator(this, messageListener));
});
if (openFrontend) {
URI uri = new URI("http://" + hostName + ":" + port); // Replace with your target URL
Desktop.getDesktop().browse(uri);
}
// Start the server in a separate thread
Thread serverThread = new Thread(() -> {
try {
server.start();
server.join(); // Wait for server to finish execution
} catch (Exception e) {
e.printStackTrace();
}
});
serverThread.start();
}
private void configureCors(ServletContextHandler context) {
// Enable CORS for all paths
FilterHolder cors = context.addFilter(CrossOriginFilter.class, "/*", null);
// Configure allowed origins, headers, and methods
cors.setInitParameter("allowedOrigins", "*");
cors.setInitParameter("allowedHeaders", "X-Requested-With,Content-Type,Accept,Origin");
cors.setInitParameter("allowedMethods", "GET,POST,PUT,DELETE,OPTIONS");
// Add filter mappings
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST);
context.addFilter(cors, "*", types);
}
private void start() throws Exception {
server.start();
}
private void join() throws InterruptedException {
server.join();
}
public void addFrontendEndpoint(FrontendEndpoint endpoint) {
frontendEndpoints.add(endpoint);
}
public void removeFrontendEndpoint(FrontendEndpoint endpoint) {
frontendEndpoints.remove(endpoint);
}
/**
* Sends Message to all Frontend Instances.
*
* @param message Message to send (String).
*/
private void sendMessageToAllFrontendEndpoints(String message) {
for (FrontendEndpoint endpoint : frontendEndpoints) {
endpoint.sendMessage(message);
}
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
sendMessageToAllFrontendEndpoints(
message.getBaseMessage().toString());
}
public void sendMessageTest(String message) {
sendMessageToAllFrontendEndpoints(message);
}
}

View File

@@ -0,0 +1,117 @@
package org.schafkopf;
import java.net.URI;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketError;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
import org.eclipse.jetty.websocket.client.WebSocketClient;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageOrigin;
/**
* Main Class that represents the Backend Server.
*/
@WebSocket
public class DedicatedServerConnection implements MessageSender {
private final MessageListener messageListener;
private final CountDownLatch closeLatch;
private final CountDownLatch connectionLatch;
private static Session session;
/**
* Class that represents one Frontend Connection.
*/
public DedicatedServerConnection(String address, MessageListener messageListener) {
this.messageListener = messageListener;
this.closeLatch = new CountDownLatch(1);
this.connectionLatch = new CountDownLatch(1);
connect("ws://" + address);
try {
connectionLatch.await(); // Wait until the connection is established
} catch (InterruptedException e) {
System.err.println("Error waiting for connection: " + e.getMessage());
}
}
/**
* Class that represents one Frontend Connection.
*/
@OnWebSocketConnect
public void onConnect(Session session) {
this.session = session;
connectionLatch.countDown();
}
@OnWebSocketMessage
public void onMessage(String message) {
messageListener.receiveMessage(message);
}
@OnWebSocketClose
public void onClose(int statusCode, String reason) {
System.out.println("Connection closed: " + reason);
closeLatch.countDown();
}
@OnWebSocketError
public void onError(Throwable cause) {
System.err.println("Error occurred: " + cause.getMessage());
}
/**
* Main Class that represents the Backend Server.
*/
@Override
public void sendMessage(SchafkopfBaseMessage message) {
try {
session.getRemote().sendString(
new SchafkopfMessage(SchafkopfMessageOrigin.BACKEND, message).getMessageAsString());
} catch (Exception e) {
System.err.println("Error sending message: " + e.getMessage());
}
}
public void awaitClose() throws InterruptedException {
closeLatch.await();
}
/**
* Main Class that represents the Backend Server.
*/
public void connect(String serverUri) {
Thread connectionThread = new Thread(() -> {
try {
WebSocketClient client = new WebSocketClient();
try {
client.start();
HeartbeatSender heartbeatSender = new HeartbeatSender(this);
heartbeatSender.start(); // Start sending heartbeat messages
URI uri = new URI(serverUri);
ClientUpgradeRequest request = new ClientUpgradeRequest();
client.connect(this, uri, request);
System.out.println("Connecting to : " + uri);
this.awaitClose();
} catch (Exception e) {
System.err.println("Error connecting to server: " + e.getMessage());
} finally {
try {
client.stop();
} catch (Exception e) {
e.printStackTrace();
}
}
} catch (Exception e) {
System.err.println("Error starting dedicated server connection: " + e.getMessage());
}
});
connectionThread.start();
}
}

View File

@@ -5,16 +5,27 @@ import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/** Class that represents one Frontend Connection. */
/**
* Class that represents one Frontend Connection.
*/
public class FrontendEndpoint extends WebSocketAdapter {
private final CountDownLatch closureLatch = new CountDownLatch(1);
private BackendServer backendServer;
private final MessageListener messageListener;
public FrontendEndpoint(BackendServer backendServer) {
/**
* Class that represents one Frontend Connection.
*/
public FrontendEndpoint(BackendServer backendServer, MessageListener messageListener) {
this.messageListener = messageListener;
this.backendServer = backendServer;
System.out.println("new FrontendEndpoint");
}
/**
* Class that represents one Frontend Connection.
*/
@Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
@@ -27,26 +38,8 @@ public class FrontendEndpoint extends WebSocketAdapter {
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("Received TEXT message:" + message);
if (message.contains("startsimulation")) {
backendServer.schafkopfGame.startGame();
}
if (message.contains("stopsimulation")) {
backendServer.schafkopfGame.stopGame();
}
if (message.contains("showtrumpf")) {
backendServer.schafkopfGame.showTrumpf();
}
if (message.contains("showfarben")) {
backendServer.schafkopfGame.showFarbe();
}
if (message.contains("setgame")) {
backendServer.schafkopfGame.setGame(message);
if (messageListener != null) {
messageListener.receiveMessage(message); // Notify the listener
}
}
@@ -66,7 +59,9 @@ public class FrontendEndpoint extends WebSocketAdapter {
cause.printStackTrace(System.err);
}
/** send a Message to the connected FrontEnd. */
/**
* send a Message to the connected FrontEnd.
*/
public void sendMessage(String message) {
try {
getRemote().sendString(message);

View File

@@ -9,8 +9,10 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
*/
public class FrontendEndpointCreator implements JettyWebSocketCreator {
private BackendServer backendServer;
private final MessageListener messageListener;
public FrontendEndpointCreator(BackendServer backendServer) {
public FrontendEndpointCreator(BackendServer backendServer, MessageListener messageListener) {
this.messageListener = messageListener;
this.backendServer = backendServer;
}
@@ -18,6 +20,6 @@ public class FrontendEndpointCreator implements JettyWebSocketCreator {
public Object createWebSocket(
JettyServerUpgradeRequest jettyServerUpgradeRequest,
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
return new FrontendEndpoint(this.backendServer);
return new FrontendEndpoint(this.backendServer, messageListener);
}
}

View File

@@ -0,0 +1,33 @@
package org.schafkopf;
import java.util.Timer;
import java.util.TimerTask;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/**
* Creates an Instance of the Backend Server.
*/
public class HeartbeatSender {
private static final int HEARTBEAT_INTERVAL = 15000; // 1 minute
private final DedicatedServerConnection client;
public HeartbeatSender(DedicatedServerConnection client) {
this.client = client;
}
/**
* Creates an Instance of the Backend Server.
*/
public void start() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
client.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.HEARTBEAT_SYN));
}
}, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL);
}
}

View File

@@ -0,0 +1,122 @@
package org.schafkopf;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageOrigin;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfClient implements MessageListener {
private BackendServer backendServer;
private DedicatedServerConnection dedicatedServerConnection;
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfClient() throws Exception {
this.backendServer = new BackendServer("localhost", 8080, true, this);
System.out.println("Client started.");
}
public static void main(String[] args) throws Exception {
new SchafkopfClient();
}
@Override
public void receiveMessage(String jsonMessage) {
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(jsonMessage, JsonObject.class);
// Check if the origin is "frontend" or "dedicated_server"
String origin = jsonObject.get("origin").getAsString();
switch (SchafkopfMessageOrigin.valueOf(origin)) {
case FRONTEND:
handleFrontendMessage(jsonObject);
break;
case DEDICATED_SERVER:
handleDedicatedServerMessage(jsonObject);
break;
default:
// Handle messages from unknown origins
System.out.println("Received message from unknown origin: " + origin);
break;
}
}
private void handleFrontendMessage(JsonObject jsonObject) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
switch (SchafkopfMessageType.valueOf(messageType)) {
case REQUEST_SERVER_CONNECTION:
dedicatedServerConnection = new DedicatedServerConnection(
content.get("serverAddress").getAsString(),
this);
break;
case PLAYER_CARD:
case CREATE_ONLINE_GAME:
case JOIN_ONLINE_GAME:
case SET_PLAYER_NAME:
dedicatedServerConnection.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.valueOf(messageType), content));
break;
case LIST_ONLINE_GAMES:
case GET_ONLINE_GAME:
case SET_STATUS_READY:
case LEAVE_ONLINE_GAME:
case START_DEDICATED_GAME:
dedicatedServerConnection.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.valueOf(messageType)));
break;
default:
// Handle unknown message types
System.out.println("Received unknown message type from frontend server: " + messageType);
break;
}
System.out.println("Received message from frontend: " + jsonObject);
}
private void handleDedicatedServerMessage(JsonObject jsonObject) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
switch (SchafkopfMessageType.valueOf(messageType)) {
case GET_CARD_ONLINE_PLAYER:
case HEARTBEAT_ACK:
break;
case GAME_STATE:
case ONLINE_PLAYER_HAND:
case UNKNOWN_ERROR:
case INFO_MESSAGE:
case GET_ONLINE_GAME:
case LIST_ONLINE_GAMES:
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.valueOf(messageType), content));
break;
case SERVER_CONNECTION_SUCCESSFUL:
case GAME_START_READY:
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.valueOf(messageType)));
break;
default:
// Handle unknown message types
System.out.println("Received unknown message type from dedicated server: " + messageType);
break;
}
if (!messageType.equals("HEARTBEAT_ACK")) {
System.out.println("Received message from dedicated server: " + jsonObject);
}
}
}

View File

@@ -0,0 +1,42 @@
package org.schafkopf.cardreader;
import java.util.concurrent.CountDownLatch;
/** Class that represents one Card Reader. */
public abstract class CardReader {
private CountDownLatch nfcLatch = new CountDownLatch(1);
private Boolean readingMode = false;
private String uidString = "";
public CardReader() {
}
/** method to call to wait for NFC input. */
public String waitForCardScan() throws InterruptedException {
this.readingMode = true;
nfcLatch.await();
Thread.sleep(20);
this.readingMode = false;
nfcLatch = new CountDownLatch(1);
return this.uidString;
}
/**
* checks uid of scanned card and do nothing if Server is not in reading mode.
*
* @param uidString uid to check.
*/
public void nfcGelesen(String uidString) {
if (this.uidString.equals(uidString)) {
return;
}
if (!this.readingMode) {
return;
}
this.uidString = uidString;
nfcLatch.countDown();
}
}

View File

@@ -0,0 +1,99 @@
package org.schafkopf.cardreader;
import com.fazecast.jSerialComm.SerialPort;
import com.sun.tools.jconsole.JConsoleContext;
import io.github.cdimascio.dotenv.Dotenv;
import java.io.UnsupportedEncodingException;
import org.schafkopf.BackendServer;
/** Class that represents the NFC Reader. */
public class UsbCardReader extends CardReader {
private volatile boolean isRunning = true;
Dotenv dotenv = Dotenv.configure().directory("./").load();
private String comPort = null;
/**
* Creates an Instance of the KartenLeser.
*
*/
public UsbCardReader() {
new Thread(this::run).start();
}
public void stop() {
isRunning = false;
}
/** Run the reader. */
public void run() {
// SerialPort[] ports = SerialPort.getCommPorts();
//
// for (SerialPort port : ports) {
// if (port.openPort()) {
// System.out.println(port.getSystemPortName());
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// // Read any data available on the serial port
// byte[] initialBuffer = new byte[port.bytesAvailable()];
// int initialBytesRead = port.readBytes(initialBuffer, initialBuffer.length);
// String initialData = null;
// try {
// initialData = new String(initialBuffer, 0, initialBytesRead, "UTF-8").trim();
// } catch (UnsupportedEncodingException e) {
// throw new RuntimeException(e);
// }
// System.out.print("Raw data: ");
// for (byte b : initialBuffer) {
// System.out.print(b + " ");
// }
// System.out.println(initialData);
// if (initialData.contains("Adafruit PN532 NFC Marker")) {
// comPort = port.getSystemPortName();
// }
// }
// }
comPort = dotenv.get("COM_PORT");
if (comPort == null) {
System.out.println("Adafruit PN532 NFC device not found");
return;
}
SerialPort serialPort = SerialPort.getCommPort(comPort);
serialPort.setBaudRate(115200);
if (serialPort.openPort()) {
System.out.println("Serial port opened successfully");
try {
while (isRunning) {
if (serialPort.bytesAvailable() > 0) {
byte[] buffer = new byte[serialPort.bytesAvailable()];
int bytesRead = serialPort.readBytes(buffer, buffer.length);
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
// Process the received data
this.nfcGelesen(data);
}
// Optional: Add a delay to avoid consuming too much CPU
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} finally {
serialPort.closePort();
System.out.println("Serial port closed");
}
} else {
System.out.println("Failed to open serial port");
}
}
}

View File

@@ -0,0 +1,60 @@
package org.schafkopf.player;
import org.schafkopf.MessageSender;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.cardreader.CardReader;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
import org.schafkopf.spielcontroller.SpielController;
/**
* Player that plays in real life.
*/
public class LocalPlayer extends Player {
private final CardReader cardReader;
public LocalPlayer(CardReader cardReader, MessageSender messageSender) {
super("Local Player", messageSender);
this.cardReader = cardReader;
}
@Override
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten) {
return wartetAufKarte();
}
@Override
public void resetReady() {
// Not needed
}
/**
* Waits for a Card and returns a Karte Object.
*/
private Karte wartetAufKarte() {
String uid = null;
System.out.println("Starte Warten auf Karte");
try {
uid = cardReader.waitForCardScan();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Karte karte = KartenUtil.getIdOfUid(uid);
if (karte == null) {
System.out.println("Ungültige Karte");
return wartetAufKarte();
}
System.out.println("Karte gescannt: " + karte.getName());
System.out.println("Beende Warten auf Karte");
return karte;
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
System.out.println("LocalPlayer: " + message);
}
}

View File

@@ -0,0 +1,55 @@
<?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>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>schafkopf-server</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-shared</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>schafkopf-server-build</finalName>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.DedicatedServer</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,167 @@
package org.schafkopf;
import com.google.gson.JsonArray;
import jakarta.servlet.DispatcherType;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.schafkopf.SchafkopfException.NoGameSessionException;
/**
* Main Class that represents the Backend Server.
*/
public class DedicatedServer {
private final Server server;
private final ServerConnector connector;
private final List<SchafkopfClientConnection> clientConnections = new ArrayList<>();
private final List<OnlineGameSession> onlineGameSessions = new ArrayList<>();
/**
* Creates an Instance of the Backend Server.
*/
public DedicatedServer() {
server = new Server();
InetAddress address;
try (final DatagramSocket socket = new DatagramSocket()) {
socket.connect(InetAddress.getByName("8.8.8.8"), 10002);
address = socket.getLocalAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
} catch (SocketException e) {
throw new RuntimeException(e);
}
InetSocketAddress socketAddress = new InetSocketAddress(address.getHostAddress(), 8085);
System.out.println(
"Server started at: " + socketAddress.getAddress() + ":" + socketAddress.getPort());
connector = new ServerConnector(server);
connector.setHost(socketAddress.getHostName());
connector.setPort(socketAddress.getPort());
server.addConnector(connector);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
// Add the health check servlet to the servlet context
context.addServlet(HealthCheckServlet.class, "/health");
context.setContextPath("/");
server.setHandler(context);
// Configure CORS settings
configureCors(context);
// Configure specific websocket behavior
JettyWebSocketServletContainerInitializer.configure(
context,
(servletContext, wsContainer) -> {
// Configure default max size
wsContainer.setMaxTextMessageSize(65535);
wsContainer.setIdleTimeout(Duration.ofDays(300000));
// Add websockets
wsContainer.addMapping("/*", new SchafkopfClientConnectionCreator(this));
});
}
/**
* The main entrypoint of the Application.
*/
public static void main(String[] args) throws Exception {
DedicatedServer server = new DedicatedServer();
server.start();
server.join();
}
private void configureCors(ServletContextHandler context) {
// Enable CORS for all paths
FilterHolder cors = context.addFilter(CrossOriginFilter.class, "/*", null);
// Configure allowed origins, headers, and methods
cors.setInitParameter("allowedOrigins", "*");
cors.setInitParameter("allowedHeaders", "X-Requested-With,Content-Type,Accept,Origin");
cors.setInitParameter("allowedMethods", "GET,POST,PUT,DELETE,OPTIONS");
// Add filter mappings
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST);
context.addFilter(cors, "*", types);
}
private void start() throws Exception {
server.start();
}
private void join() throws InterruptedException {
server.join();
}
public void addFrontendEndpoint(SchafkopfClientConnection endpoint) {
clientConnections.add(endpoint);
}
public void removeFrontendEndpoint(SchafkopfClientConnection endpoint) {
clientConnections.remove(endpoint);
}
public void addGameSession(OnlineGameSession onlineGameSession) {
onlineGameSessions.add(onlineGameSession);
}
public List<OnlineGameSession> getGameSessions() {
return onlineGameSessions;
}
/**
* The main entrypoint of the Application.
*/
public JsonArray getGameSessionsAsJson() throws NoGameSessionException {
if (onlineGameSessions.isEmpty()) {
throw new NoGameSessionException();
}
JsonArray gameSessionsJson = new JsonArray();
for (OnlineGameSession onlineGameSession : onlineGameSessions) {
gameSessionsJson.add(onlineGameSession.getJson());
}
return gameSessionsJson;
}
/**
* The main entrypoint of the Application.
*/
public OnlineGameSession getCurrentGameSession() throws NoGameSessionException {
if (onlineGameSessions.isEmpty()) {
throw new NoGameSessionException();
}
return onlineGameSessions.get(onlineGameSessions.size() - 1);
}
/**
* The main entrypoint of the Application.
*/
public OnlineGameSession getGameSessionByName(String gameId) throws NoGameSessionException {
for (OnlineGameSession onlineGameSession : onlineGameSessions) {
if (onlineGameSession.getServerName().equals(gameId)) {
return onlineGameSession;
}
}
throw new NoGameSessionException();
}
public void removeGameSession(OnlineGameSession onlineGameSession) {
onlineGameSessions.remove(onlineGameSession);
}
}

View File

@@ -0,0 +1,23 @@
package org.schafkopf;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* Class that represents one Frontend Connection.
*/
public class HealthCheckServlet extends HttpServlet {
/**
* Class that represents one Frontend Connection.
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/plain");
resp.getWriter().println("Backend server is up and running!");
}
}

View File

@@ -0,0 +1,155 @@
package org.schafkopf;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import org.schafkopf.SchafkopfException.PlayerNotReadyException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.OnlinePlayer;
import org.schafkopf.player.Player;
/**
* The main entrypoint of the Application.
*/
public class OnlineGameSession extends BaseGameSession {
private String serverName;
private DedicatedServer dedicatedServer;
/**
* The main entrypoint of the Application.
*/
public OnlineGameSession(String serverName, DedicatedServer dedicatedServer) {
this.players = new ArrayList<>();
this.dedicatedServer = dedicatedServer;
this.serverName = serverName;
logger.info(serverName + " created.");
}
/**
* Class that represents one Frontend Connection.
*/
public void addPlayer(Player player) {
if (this.players.size() >= 4) {
throw new RuntimeException("Game is full");
}
logger.info("Adding player to game: " + player);
players.add(player);
this.sendSessionInfo();
}
void startGame() throws PlayerNotReadyException {
logger.info("Starting game: " + serverName + " with " + players.size() + " Onlineplayers");
for (Player player : players) {
if (!player.isReady()) {
throw new PlayerNotReadyException();
}
}
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.GAME_START_READY));
//wait for 5 seconds
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.startGame(players);
players.forEach(player -> player.resetReady());
}
/**
* Class that represents one Frontend Connection.
*/
public void removePlayer(OnlinePlayer player) {
logger.info("Removing player from game: " + player.getName());
players.remove(player);
if (this.getPlayerCount() == 0) {
logger.info("No players left in game: " + serverName);
if (spielThread != null) {
spielThread.interrupt();
}
this.dedicatedServer.removeGameSession(this);
return;
}
this.sendSessionInfo();
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
for (Player player : players) {
player.sendMessage(message);
}
}
/**
* The main entrypoint of the Application.
*/
public JsonObject getJson() {
JsonObject jsonObject = new JsonObject();
jsonObject.addProperty("serverName", serverName);
jsonObject.addProperty("playerCount", getPlayerCount());
// Create an array to hold player information
JsonArray playersArray = new JsonArray();
for (Player player : players) {
JsonObject playerObject = new JsonObject();
playerObject.addProperty("playerName",
player.getName()); // Assuming you have a method to get player name
playerObject.addProperty("isReady",
player.isReady()); // Assuming you have a method to check player readiness
playersArray.add(playerObject);
playerObject.addProperty("isBot",
player instanceof BotPlayer);
}
for (int i = players.size(); i < 4; i++) {
JsonObject playerObject = new JsonObject();
playerObject.addProperty("playerName",
"Bot " + i); // Assuming you have a method to get player name
playerObject.addProperty("isReady",
true);
playerObject.addProperty("isBot",
true);
playersArray.add(playerObject);
}
jsonObject.add("players", playersArray);
return jsonObject;
}
public String getServerName() {
return serverName;
}
/**
* The main entrypoint of the Application.
*/
public int getPlayerCount() {
int onlinePlayerCount = 0;
for (Player player : players) {
if (player instanceof OnlinePlayer) {
onlinePlayerCount++;
}
}
return onlinePlayerCount;
}
/**
* Class that represents one Frontend Connection.
*/
public void sendSessionInfo() {
JsonObject messageObject2 = new JsonObject();
messageObject2.add("game", this.getJson());
sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.GET_ONLINE_GAME,
messageObject2));
}
}

View File

@@ -0,0 +1,209 @@
package org.schafkopf;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
import org.schafkopf.SchafkopfException.NoGameSessionException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageOrigin;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
import org.schafkopf.karte.Karte;
import org.schafkopf.player.OnlinePlayer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfClientConnection extends WebSocketAdapter implements MessageSender {
private static final Logger logger = LoggerFactory.getLogger(SchafkopfClientConnection.class);
private final CountDownLatch connectionLatch;
private final CountDownLatch closureLatch = new CountDownLatch(1);
private DedicatedServer dedicatedServer;
private OnlineGameSession onlineGameSession;
private OnlinePlayer onlinePlayer;
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfClientConnection(DedicatedServer dedicatedServer) {
this.dedicatedServer = dedicatedServer;
this.connectionLatch = new CountDownLatch(1);
this.onlinePlayer = new OnlinePlayer(this, "DefaultName");
}
@Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
String clientIp = session.getRemoteAddress().toString();
logger.info("Endpoint connected from ip: " + clientIp);
connectionLatch.countDown();
dedicatedServer.addFrontendEndpoint(this);
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.SERVER_CONNECTION_SUCCESSFUL));
}
@Override
public void onWebSocketText(String jsonMessage) {
super.onWebSocketText(jsonMessage);
Gson gson = new Gson();
JsonObject jsonObject = gson.fromJson(jsonMessage, JsonObject.class);
// Check if the origin is "backend"
String origin = jsonObject.get("origin").getAsString();
if (!SchafkopfMessageOrigin.BACKEND.toString().equals(origin)) {
return;
}
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
switch (SchafkopfMessageType.valueOf(messageType)) {
case HEARTBEAT_SYN:
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.HEARTBEAT_ACK));
break;
case JOIN_ONLINE_GAME:
OnlineGameSession onlineGameSession = null;
try {
onlineGameSession = dedicatedServer.getGameSessionByName(
content.get("serverName").getAsString());
} catch (NoGameSessionException e) {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("error",
"No GameSession with name \"" + content.get("serverName").getAsString()
+ "\" found.");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR,
messageObject));
break;
}
joinGame(onlineGameSession);
sendServerList();
JsonObject messageObject = new JsonObject();
messageObject.addProperty("message",
"Joined GameSession \"" + onlineGameSession.getServerName() + "\".");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.INFO_MESSAGE,
messageObject));
break;
case START_DEDICATED_GAME:
try {
this.onlineGameSession.startGame();
} catch (SchafkopfException e) {
sendError(e);
}
break;
case SET_PLAYER_NAME:
String name = content.get("playerName").getAsString();
onlinePlayer.setName(name);
break;
case PLAYER_CARD:
onlinePlayer.receiveCard(Karte.valueOf(content.get("card").getAsString()));
break;
case LIST_ONLINE_GAMES:
sendServerList();
break;
case GET_ONLINE_GAME:
this.onlineGameSession.sendSessionInfo();
break;
case CREATE_ONLINE_GAME:
String servername = content.get("serverName").getAsString();
OnlineGameSession onlineGameSession2 = new OnlineGameSession(servername,
this.dedicatedServer);
dedicatedServer.addGameSession(onlineGameSession2);
joinGame(onlineGameSession2);
sendServerList();
break;
case SET_STATUS_READY:
onlinePlayer.setReady(!onlinePlayer.isReady());
this.onlineGameSession.sendSessionInfo();
break;
case LEAVE_ONLINE_GAME:
this.onlineGameSession.removePlayer(this.onlinePlayer);
this.onlineGameSession = null;
sendServerList();
break;
default:
// Handle unknown message types
logger.warn("Received unknown message type: " + messageType);
break;
}
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
if (this.onlineGameSession != null) {
this.onlineGameSession.removePlayer(this.onlinePlayer);
}
super.onWebSocketClose(statusCode, reason);
dedicatedServer.removeFrontendEndpoint(this);
logger.warn("Socket Closed: [" + statusCode + "] " + reason);
closureLatch.countDown();
}
@Override
public void onWebSocketError(Throwable cause) {
super.onWebSocketError(cause);
cause.printStackTrace(System.err);
}
/**
* Send a message to the connected FrontEnd.
*/
@Override
public void sendMessage(SchafkopfBaseMessage message) {
SchafkopfMessage schafkopfMessage = new SchafkopfMessage(
SchafkopfMessageOrigin.DEDICATED_SERVER,
message);
try {
getRemote().sendString(schafkopfMessage.getMessageAsString());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* The main entrypoint of the Application.
*/
public void joinGame(OnlineGameSession onlineGameSession) {
if (this.onlineGameSession != null) {
this.onlineGameSession.removePlayer(this.onlinePlayer);
}
this.onlineGameSession = onlineGameSession;
onlineGameSession.addPlayer(this.onlinePlayer);
}
private void sendServerList() {
JsonObject messageObject = new JsonObject();
try {
messageObject.add("games", dedicatedServer.getGameSessionsAsJson());
} catch (NoGameSessionException e) {
JsonObject error = new JsonObject();
error.addProperty("error",
"No GameSessions found.");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR,
error));
return;
}
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.LIST_ONLINE_GAMES,
messageObject));
}
/**
* Send a message to the connected FrontEnd.
*/
public void sendError(SchafkopfException e) {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("error",
e.getMessage());
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR,
messageObject));
}
}

View File

@@ -0,0 +1,24 @@
package org.schafkopf;
import org.eclipse.jetty.websocket.server.JettyServerUpgradeRequest;
import org.eclipse.jetty.websocket.server.JettyServerUpgradeResponse;
import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
/**
* Creater to make new Instances of the FrontendConnection.
*/
public class SchafkopfClientConnectionCreator implements JettyWebSocketCreator {
private DedicatedServer dedicatedServer;
public SchafkopfClientConnectionCreator(DedicatedServer dedicatedServer) {
this.dedicatedServer = dedicatedServer;
}
@Override
public Object createWebSocket(
JettyServerUpgradeRequest jettyServerUpgradeRequest,
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
return new SchafkopfClientConnection(this.dedicatedServer);
}
}

View File

@@ -0,0 +1,20 @@
<?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>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>schafkopf-shared</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

View File

@@ -0,0 +1,51 @@
package org.schafkopf;
import java.util.List;
import org.schafkopf.SchafkopfException.NotEnoughPlayersException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.Player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The main entrypoint of the Application.
*/
public abstract class BaseGameSession implements MessageSender {
protected static final Logger logger = LoggerFactory.getLogger(BaseGameSession.class);
protected Thread spielThread;
protected Schafkopf schafkopf;
protected List<Player> players;
void startGame(List<Player> players) {
logger.info("Starting game");
for (int i = players.size(); i < 4; i++) {
players.add(new BotPlayer("Bot " + i));
}
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.GAME_START_READY));
//wait for 5 seconds
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
spielThread = new Thread(() -> {
try {
schafkopf = new Schafkopf(players.toArray(Player[]::new), this);
schafkopf.startGame();
} catch (NotEnoughPlayersException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
spielThread.start();
}
}

View File

@@ -5,14 +5,18 @@ import com.google.gson.JsonObject;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
/** GameState. */
/**
* GameState.
*/
public class GameState {
public GamePhase getGamePhase() {
return this.gamePhase;
}
/** GamePhase. */
/**
* GamePhase.
*/
public enum GamePhase {
CHOOSE_GAME("Spiel muss gewählt werden"),
@@ -37,7 +41,7 @@ public class GameState {
}
private GamePhase gamePhase;
private Integer currentPlayer; // Using Integer to allow for null
private String currentPlayer; // Using Integer to allow for null
private Karte card;
private KartenFarbe color;
private boolean trumpf;
@@ -48,13 +52,15 @@ public class GameState {
this.gamePhase = phase;
}
public GameState(GamePhase phase, Integer player) {
public GameState(GamePhase phase, String player) {
this.gamePhase = phase;
this.currentPlayer = player;
}
/** GameState. */
public GameState(GamePhase phase, Integer player, Karte card, KartenFarbe color, boolean trumpf) {
/**
* GameState.
*/
public GameState(GamePhase phase, String player, Karte card, KartenFarbe color, boolean trumpf) {
this.gamePhase = phase;
this.currentPlayer = player;
this.card = card;
@@ -62,19 +68,34 @@ public class GameState {
this.trumpf = trumpf;
}
/** GameState. */
public GameState(GamePhase phase, Integer player, Karte card) {
/**
* GameState.
*/
public GameState(GamePhase phase, String player, Karte card) {
this.gamePhase = phase;
this.currentPlayer = player;
this.card = card;
}
/** GameState. */
/**
* GameState.
*/
public JsonObject getJson() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.add("gamestate", gson.toJsonTree(this));
JsonObject gameStateObject = new JsonObject();
return jsonObject;
if (this.currentPlayer != null) {
gameStateObject.addProperty("currentPlayer", this.currentPlayer);
}
if (this.card != null) {
gameStateObject.add("card", gson.toJsonTree(this.card));
}
gameStateObject.addProperty("gamePhase", this.gamePhase.name());
gameStateObject.addProperty("trumpf", this.trumpf);
if (this.color != null) {
gameStateObject.addProperty("color", this.color.name());
}
return gameStateObject;
}
}

View File

@@ -0,0 +1,9 @@
package org.schafkopf;
/**
* Class that represents one Frontend Connection.
*/
public interface MessageListener {
void receiveMessage(String message);
}

View File

@@ -0,0 +1,11 @@
package org.schafkopf;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/**
* The main entrypoint of the Application.
*/
public interface MessageSender {
void sendMessage(SchafkopfBaseMessage message);
}

View File

@@ -0,0 +1,116 @@
package org.schafkopf;
import org.schafkopf.GameState.GamePhase;
import org.schafkopf.SchafkopfException.NotEnoughPlayersException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.OnlinePlayer;
import org.schafkopf.player.Player;
import org.schafkopf.spielcontroller.SauSpielController;
import org.schafkopf.spielcontroller.SpielController;
/**
* The main class representing the Schafkopf game.
*/
public class Schafkopf {
private final MessageSender messageSender;
/**
* The game controller. This is the class that implements the game logic.
*/
private SpielController spiel = new SauSpielController(0, KartenFarbe.EICHEL);
private final Player[] player;
private GameState gameState = new GameState(GamePhase.GAME_STOP);
/**
* Constructor for the Schafkopf class.
*
* @param messageSender MessageSender
*/
public Schafkopf(Player[] player, MessageSender messageSender) throws NotEnoughPlayersException {
this.player = player;
if (player.length < 4) {
throw new NotEnoughPlayersException();
}
this.messageSender = messageSender;
System.out.println("SchaffKopfGame erstellt");
}
public Player[] getPlayer() {
return player;
}
/**
* Set GameState to "started" and start Game Thread.
*/
public void startGame() throws InterruptedException {
if (gameState.getGamePhase() != GamePhase.GAME_STOP) {
System.out.println("Game already started!");
messageSender.sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR));
} else {
gameState = new GameState(GamePhase.GAME_START);
setAndSendGameState(gameState);
System.out.println("Start Game");
KartenListe austeilen = KartenUtil.initializeSchafKopfCardDeck();
austeilen.shuffle();
for (Player currentPlayer : player) {
if (currentPlayer instanceof BotPlayer botPlayer) {
KartenListe botHand = new KartenListe();
for (int i = 7; i >= 0; i--) {
botHand.addKarten(austeilen.removeKarten(austeilen.getByIndex(i)));
}
System.out.println("Bot Hand: " + botHand.getJson().toString());
botPlayer.setCards(botHand); // Replace with the actual method you want to call
}
}
for (Player currentPlayer : player) {
if (currentPlayer instanceof OnlinePlayer onlinePlayer) {
KartenListe karten = new KartenListe();
for (int i = 7; i >= 0; i--) {
karten.addKarten(austeilen.removeKarten(austeilen.getByIndex(i)));
}
onlinePlayer.setAndSendPlayerCards(karten);
}
}
new Spielablauf(this, spiel);
}
}
/**
* Set GameState to "stopped" and interrupt Game Thread.
*/
public void stopGame() {
if (gameState.getGamePhase() == GamePhase.GAME_STOP) {
System.out.println("no active Game!");
messageSender.sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR));
} else {
gameState = new GameState(GamePhase.GAME_STOP);
setAndSendGameState(gameState);
}
}
/**
* Class that represents one Frontend Connection.
*/
public void setAndSendGameState(GameState gameState) {
this.gameState = gameState;
this.messageSender.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.GAME_STATE, gameState.getJson()));
}
public GameState getGameState() {
return this.gameState;
}
}

View File

@@ -0,0 +1,62 @@
package org.schafkopf;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfException extends Exception {
private SchafkopfException(String message) {
super(message);
}
/**
* Class that represents one Frontend Connection.
*/
public static class NotEnoughPlayersException extends SchafkopfException {
public NotEnoughPlayersException() {
super("Not enough players to start the game");
}
// You can also include additional constructors or methods if needed
}
/**
* The main entrypoint of the Application.
*/
public static class NoGameSessionException extends SchafkopfException {
public NoGameSessionException() {
super("No game session available");
}
// You can also include additional constructors or methods if needed
}
/**
* The main entrypoint of the Application.
*/
public static class PlayerNotReadyException extends SchafkopfException {
public PlayerNotReadyException() {
super("Not all Players are in Ready State");
}
// You can also include additional constructors or methods if needed
}
/**
* Class that represents one Frontend Connection.
*/
public class InvalidMoveException extends SchafkopfException {
// Constructor with a message
public InvalidMoveException(String message) {
super(message);
}
// You can also include additional constructors or methods if needed
}
}

View File

@@ -0,0 +1,156 @@
package org.schafkopf;
import com.google.gson.JsonObject;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfMessage {
/**
* Class that represents one Frontend Connection.
*/
public static class SchafkopfBaseMessage {
private JsonObject message;
private SchafkopfMessageType messageType;
public SchafkopfBaseMessage(SchafkopfMessageType messageType, String content) {
this.messageType = messageType;
this.message = buildBaseMessage(messageType, content);
}
public SchafkopfBaseMessage(SchafkopfMessageType messageType, JsonObject content) {
this.messageType = messageType;
this.message = buildBaseMessage(messageType, content);
}
public SchafkopfBaseMessage(SchafkopfMessageType messageType) {
this.messageType = messageType;
this.message = buildBaseMessage(messageType);
}
public JsonObject getBaseMessage() {
return message;
}
}
JsonObject message;
public SchafkopfMessage(SchafkopfMessageOrigin origin, SchafkopfBaseMessage baseMessage) {
this.message = buildWrapperMessage(origin, baseMessage.getBaseMessage());
}
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfMessage(SchafkopfMessageOrigin origin, SchafkopfMessageType messageType) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.add("content", buildBaseMessage(messageType));
this.message = buildWrapperMessage(origin, messageContentObject);
}
/**
* Class that represents one Frontend Connection.
*/
private SchafkopfMessage(SchafkopfMessageOrigin origin, SchafkopfMessageType messageType,
JsonObject messageContent) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.add("content", buildBaseMessage(messageType, messageContent));
this.message = buildWrapperMessage(origin, messageContentObject);
}
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfMessage(SchafkopfMessageOrigin origin, SchafkopfMessageType messageType,
String messageContent) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.add("content", buildBaseMessage(messageType, messageContent));
this.message = buildWrapperMessage(origin, messageContentObject);
}
private static JsonObject buildWrapperMessage(SchafkopfMessageOrigin origin, JsonObject message) {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("origin", origin.toString());
messageObject.add("message", message);
return messageObject;
}
private static JsonObject buildBaseMessage(SchafkopfMessageType messageType,
String messageContent) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.addProperty("message_type", messageType.toString());
messageContentObject.addProperty("content", messageContent);
return messageContentObject;
}
private static JsonObject buildBaseMessage(SchafkopfMessageType messageType,
JsonObject messageContent) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.addProperty("message_type", messageType.toString());
messageContentObject.add("content", messageContent);
return messageContentObject;
}
private static JsonObject buildBaseMessage(SchafkopfMessageType messageType) {
JsonObject messageContentObject = new JsonObject();
messageContentObject.addProperty("message_type", messageType.toString());
return messageContentObject;
}
public JsonObject getMessage() {
return message;
}
public String getMessageAsString() {
return message.toString();
}
/**
* Class that represents one Frontend Connection.
*/
public enum SchafkopfMessageType {
UNKNOWN_ERROR,
INFO_MESSAGE,
HEARTBEAT_SYN,
HEARTBEAT_ACK,
GET_CARD_ONLINE_PLAYER,
ONLINE_PLAYER_HAND,
GAME_STATE,
SERVER_CONNECTION_SUCCESSFUL,
REQUEST_SERVER_CONNECTION,
JOIN_ONLINE_GAME,
START_DEDICATED_GAME,
PLAYER_CARD,
LIST_ONLINE_GAMES,
GET_ONLINE_GAME,
CREATE_ONLINE_GAME,
SET_STATUS_READY,
SET_PLAYER_NAME,
GAME_START_READY,
LEAVE_ONLINE_GAME
}
/**
* Class that represents one Frontend Connection.
*/
public enum SchafkopfMessageOrigin {
FRONTEND,
BACKEND,
DEDICATED_SERVER
}
}

View File

@@ -8,7 +8,9 @@ import org.schafkopf.spielcontroller.SpielController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The main class that controlls the game flow. */
/**
* The main class that controlls the game flow.
*/
public class Spielablauf {
private static final Logger logger = LoggerFactory.getLogger(Spielablauf.class);
@@ -22,7 +24,7 @@ public class Spielablauf {
private final Schafkopf schafkopf;
Spielablauf(Schafkopf schafkopf, SpielController spiel) {
Spielablauf(Schafkopf schafkopf, SpielController spiel) throws InterruptedException {
this.schafkopf = schafkopf;
this.spiel = spiel;
this.players = schafkopf.getPlayer();
@@ -30,7 +32,7 @@ public class Spielablauf {
playRound();
}
private void playRound() {
private void playRound() throws InterruptedException {
int startingPlayer = 0;
logger.info("Starte Stiche");
@@ -41,14 +43,15 @@ public class Spielablauf {
schafkopf.stopGame();
}
private int playTrick(int startingPlayer) {
private int playTrick(int startingPlayer) throws InterruptedException {
schafkopf.setAndSendGameState(new GameState(GamePhase.TRICK_START));
for (int i = 0; i < 4; i++) {
int currentPlayer = (i + startingPlayer) % 4;
logger.info("Spieler ist dran: {}", currentPlayer);
schafkopf.setAndSendGameState(new GameState(GamePhase.WAIT_FOR_CARD, currentPlayer));
logger.info("Spieler ist dran: {}", players[currentPlayer].getName());
schafkopf.setAndSendGameState(
new GameState(GamePhase.WAIT_FOR_CARD, players[currentPlayer].getName()));
Karte playedCard = players[currentPlayer].play(spiel, tischKarten, gespielteKarten);
tischKarten.addKarten(playedCard);
@@ -56,7 +59,7 @@ public class Spielablauf {
schafkopf.setAndSendGameState(
new GameState(
GamePhase.PLAYER_CARD,
currentPlayer,
players[currentPlayer].getName(),
playedCard,
tischKarten.getByIndex(0).getFarbe(),
spiel.isTrumpf(tischKarten.getByIndex(0))));
@@ -70,7 +73,8 @@ public class Spielablauf {
schafkopf.setAndSendGameState(
new GameState(
GamePhase.PLAYER_TRICK, winningPlayerIndex, tischKarten.getByIndex(stichSpieler)));
GamePhase.PLAYER_TRICK, players[winningPlayerIndex].getName(),
tischKarten.getByIndex(stichSpieler)));
try {
Thread.sleep(3000);

View File

@@ -1,14 +1,16 @@
package org.schafkopf.karte;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonElement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A Class that represents a list of Cards.
*/
public class KartenListe {
private List<Karte> kartenListe;
public KartenListe() {
@@ -23,6 +25,23 @@ public class KartenListe {
return this.kartenListe;
}
/**
* A Class that represents a list of Cards.
*/
public void sort() {
KartenListe completeDeck = KartenUtil.initializeSchafKopfCardDeck();
completeDeck.removeKarten(this);
KartenListe completeDeck2 = KartenUtil.initializeSchafKopfCardDeck();
completeDeck2.removeKarten(completeDeck);
this.kartenListe = completeDeck2.getKartenListe();
}
public void shuffle() {
Collections.shuffle(this.kartenListe);
}
/**
* A Class that represents a list of Cards.
*/
@@ -158,15 +177,31 @@ public class KartenListe {
return result;
}
/**
* Class that represents one Frontend Connection.
*/
public Karte getKarten(KartenSymbol symbol, KartenFarbe farbe) {
KartenListe result = new KartenListe();
for (Karte karte : this.kartenListe) {
if (karte.getSymbol().equals(symbol)) {
result.addKarten(karte);
}
}
for (Karte karte : result.kartenListe) {
if (karte.getFarbe().equals(farbe)) {
result.addKarten(karte);
}
}
return result.getByIndex(0);
}
/**
* A Class that represents a list of Cards.
*/
public JsonObject getJson() {
public JsonElement getJson() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.add("cards", gson.toJsonTree(this.kartenListe));
return jsonObject;
return gson.toJsonTree(this.kartenListe);
}
public boolean isEmpty() {

View File

@@ -1,18 +1,31 @@
package org.schafkopf.player;
import org.schafkopf.MessageSender;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
import org.schafkopf.spielcontroller.SpielController;
/** Player that represents the Bot. */
/**
* Player that represents the Bot.
*/
public class BotPlayer extends Player {
private KartenListe eigeneKarten;
private KartenListe unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
public BotPlayer() {
// TODO document why this constructor is empty
/**
* Constructor for the BotPlayer.
*/
public BotPlayer(String name) {
super(name, new MessageSender() {
@Override
public void sendMessage(SchafkopfBaseMessage message) {
System.out.println("BotPlayer: " + message);
}
});
this.setReady(true);
}
@Override
@@ -32,12 +45,25 @@ public class BotPlayer extends Player {
return card;
}
/** Set the Cards of the Player. */
/**
* Set the Cards of the Player.
*/
public void setCards(KartenListe cards) {
System.out.println("Eigene Karte setzen");
cards.sort();
this.eigeneKarten = cards;
this.unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
this.unbekannteKarten.removeKarten(eigeneKarten);
System.out.println("Eigene Karte fertig");
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
}
@Override
public void resetReady() {
// Not needed
}
}

View File

@@ -0,0 +1,79 @@
package org.schafkopf.player;
import com.google.gson.JsonObject;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import org.schafkopf.MessageSender;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.spielcontroller.SpielController;
/**
* Player that plays in real life.
*/
public class OnlinePlayer extends Player {
private final BlockingQueue<Karte> receivedCardQueue = new LinkedBlockingQueue<>();
private KartenListe karten = new KartenListe();
public OnlinePlayer(MessageSender messageSender, String name) {
super(name, messageSender);
}
/**
* A Class that represents a list of Cards.
*/
public void setAndSendPlayerCards(KartenListe karten) {
karten.sort();
this.karten = karten;
sendPlayerCards();
}
@Override
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten)
throws InterruptedException {
sendPlayerCards();
Karte spielKarte = null;
// Send the message to request the card from the frontend
messageSender.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.GET_CARD_ONLINE_PLAYER));
spielKarte = receivedCardQueue.take();
this.karten.removeKarten(spielKarte);
sendPlayerCards();
System.out.println("Karte gespielt: " + spielKarte);
return spielKarte;
}
@Override
public void resetReady() {
this.setReady(false);
}
private void sendPlayerCards() {
JsonObject messageObject = new JsonObject();
messageObject.add("cards", this.karten.getJson());
messageSender.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.ONLINE_PLAYER_HAND, messageObject));
}
/**
* Class that represents one Frontend Connection.
*/
public void receiveCard(Karte receivedCard) {
System.out.println("Received Card before Queue: " + receivedCard.getName());
receivedCardQueue.add(receivedCard);
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
messageSender.sendMessage(message);
}
}

View File

@@ -0,0 +1,43 @@
package org.schafkopf.player;
import org.schafkopf.MessageSender;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.spielcontroller.SpielController;
/**
* Class that represents one Player of the game.
*/
public abstract class Player implements MessageSender {
protected MessageSender messageSender;
private boolean ready = false;
private String name;
protected Player(String name, MessageSender messageSender) {
this.messageSender = messageSender;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public abstract Karte play(
SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten)
throws InterruptedException;
public abstract void resetReady();
public void setReady(boolean ready) {
this.ready = ready;
}
public boolean isReady() {
return ready;
}
}

View File

@@ -1,243 +0,0 @@
package org.schafkopf;
import com.google.gson.JsonObject;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import io.github.cdimascio.dotenv.Dotenv;
import jakarta.servlet.DispatcherType;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import javafx.application.Application;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
import org.schafkopf.cardreader.CardReader;
import org.schafkopf.cardreader.UsbCardReader;
/** Main Class that represents the Backend Server. */
public class BackendServer {
private final Server server;
private final ServerConnector connector;
private CountDownLatch nfcLatch = new CountDownLatch(1);
private Boolean readingMode = false;
private String uidString = "";
/** Important variables. */
public final Schafkopf schafkopfGame;
private final CardReader nfcLeser;
private final List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
/** Creates an Instance of the Backend Server. */
public BackendServer() {
Dotenv dotenv = Dotenv.configure().directory("./").load();
server = new Server();
InetSocketAddress address = new InetSocketAddress(dotenv.get("VITE_APP_WEBSOCKET_IP"), 8080);
connector = new ServerConnector(server);
connector.setHost(address.getHostName());
connector.setPort(address.getPort());
server.addConnector(connector);
schafkopfGame = new Schafkopf(this);
nfcLeser = new UsbCardReader(this);
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/");
server.setHandler(context);
// Configure CORS settings
configureCors(context);
URL webContentUrl = getClass().getClassLoader().getResource("web-content");
if (webContentUrl == null) {
throw new RuntimeException("Unable to find 'web-content' directory");
}
String webContentPath = webContentUrl.toExternalForm();
context.setResourceBase(webContentPath);
System.out.println("Web Content Path: " + webContentPath);
// Configure specific websocket behavior
JettyWebSocketServletContainerInitializer.configure(
context,
(servletContext, wsContainer) -> {
// Configure default max size
wsContainer.setMaxTextMessageSize(65535);
wsContainer.setIdleTimeout(Duration.ofDays(300000));
// Add websockets
wsContainer.addMapping("/schafkopf-events/*", new FrontendEndpointCreator(this));
});
// Integrate simple HTTP server
startHttpServer();
new Thread(this::launchJavaFx).start();
}
private void launchJavaFx() {
Application.launch(JavaFxApp.class);
}
private void startHttpServer() {
try {
HttpServer httpServer = HttpServer.create(new InetSocketAddress(8081), 0);
httpServer.createContext("/", new MyHandler());
httpServer.setExecutor(null);
httpServer.start();
System.out.println("HTTP Server started on port 8081");
} catch (IOException e) {
e.printStackTrace();
}
}
static class MyHandler implements HttpHandler {
@Override
public void handle(HttpExchange t) throws IOException {
String path = t.getRequestURI().getPath();
if ("/".equals(path)) {
path = "/index.html"; // default to index.html
}
try {
InputStream fileStream =
getClass().getClassLoader().getResourceAsStream("web-content" + path);
if (fileStream != null) {
byte[] data = fileStream.readAllBytes();
// Set the appropriate MIME type for JavaScript files
String mimeType = getMimeType(path);
t.getResponseHeaders().set("Content-Type", mimeType);
t.sendResponseHeaders(200, data.length);
try (OutputStream os = t.getResponseBody()) {
os.write(data);
}
} else {
// File not found
t.sendResponseHeaders(404, -1);
}
} catch (IOException e) {
e.printStackTrace();
t.sendResponseHeaders(500, -1);
}
}
private String getMimeType(String path) {
if (path.endsWith(".js")) {
return "application/javascript";
} else if (path.endsWith(".html")) {
return "text/html";
} else if (path.endsWith(".css")) {
return "text/css";
}
// Add more MIME types as needed
return "application/octet-stream";
}
}
/** The main entrypoint of the Application. */
public static void main(String[] args) throws Exception {
BackendServer server = new BackendServer();
server.setPort(8080);
server.start();
server.join();
}
private void configureCors(ServletContextHandler context) {
// Enable CORS for all paths
FilterHolder cors = context.addFilter(CrossOriginFilter.class, "/*", null);
// Configure allowed origins, headers, and methods
cors.setInitParameter("allowedOrigins", "*");
cors.setInitParameter("allowedHeaders", "X-Requested-With,Content-Type,Accept,Origin");
cors.setInitParameter("allowedMethods", "GET,POST,PUT,DELETE,OPTIONS");
// Add filter mappings
EnumSet<DispatcherType> types = EnumSet.of(DispatcherType.REQUEST);
context.addFilter(cors, "*", types);
}
private void setPort(int port) {
connector.setPort(port);
}
private void start() throws Exception {
server.start();
}
private void join() throws InterruptedException {
server.join();
}
public void addFrontendEndpoint(FrontendEndpoint endpoint) {
frontendEndpoints.add(endpoint);
}
public void removeFrontendEndpoint(FrontendEndpoint endpoint) {
frontendEndpoints.remove(endpoint);
}
/**
* Sends Message to all Frontend Instances.
*
* @param message Message to send (String).
*/
public void sendMessageToAllFrontendEndpoints(String message) {
for (FrontendEndpoint endpoint : frontendEndpoints) {
endpoint.sendMessage(message);
}
}
/**
* Sends Message to all Frontend Instances.
*
* @param message Message to send (JsonObject).
*/
public void sendMessageToAllFrontendEndpoints(JsonObject message) {
for (FrontendEndpoint endpoint : frontendEndpoints) {
endpoint.sendMessage(message.toString());
}
}
/** method to call to wait for NFC input. */
public String waitForCardScan() throws InterruptedException {
this.readingMode = true;
nfcLatch.await();
Thread.sleep(20);
this.readingMode = false;
nfcLatch = new CountDownLatch(1);
return this.uidString;
}
/**
* checks uid of scanned card and do nothing if Server is not in reading mode.
*
* @param uidString uid to check.
*/
public void nfcGelesen(String uidString) {
if (this.uidString.equals(uidString)) {
return;
}
if (!this.readingMode) {
return;
}
this.uidString = uidString;
nfcLatch.countDown();
}
}

View File

@@ -1,50 +0,0 @@
package org.schafkopf;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.input.KeyCode;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
/** Frontend Window. */
public class JavaFxApp extends Application {
private static final String FRONTEND_URL =
"http://localhost:8081"; // Replace with your frontend URL
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// Create a WebView
WebView webView = new WebView();
// Load the frontend URL
webView.getEngine().load(FRONTEND_URL);
// Create a Scene with the WebView
Scene scene = new Scene(webView, 800, 600);
// Set up the Stage
primaryStage.setTitle("Schafkopfen");
primaryStage.setScene(scene);
primaryStage.setFullScreenExitHint("");
// Set the stage to fullscreen
primaryStage.setFullScreen(true);
// Add event handler for the Escape key to toggle fullscreen
scene.setOnKeyPressed(
event -> {
if (event.getCode() == KeyCode.F11) {
primaryStage.setFullScreen(!primaryStage.isFullScreen());
}
});
// Show the Stage
primaryStage.show();
}
}

View File

@@ -1,193 +0,0 @@
package org.schafkopf;
import org.schafkopf.GameState.GamePhase;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.LocalPlayer;
import org.schafkopf.player.Player;
import org.schafkopf.spielcontroller.FarbGeierController;
import org.schafkopf.spielcontroller.FarbSoloController;
import org.schafkopf.spielcontroller.FarbWenzController;
import org.schafkopf.spielcontroller.GeierController;
import org.schafkopf.spielcontroller.SauSpielController;
import org.schafkopf.spielcontroller.SpielController;
import org.schafkopf.spielcontroller.WenzController;
/** The main class representing the Schafkopf game. */
public class Schafkopf {
private final BackendServer server;
/** The game controller. This is the class that implements the game logic. */
private SpielController spiel = new SauSpielController(0, KartenFarbe.EICHEL);
private final Player[] player = {
new BotPlayer(), new LocalPlayer(this), new LocalPlayer(this), new LocalPlayer(this)
};
private GameState gameState = new GameState(GamePhase.GAME_STOP);
private Thread spielThread;
/**
* Constructor for the Schafkopf class.
*
* @param server The backend server associated with the game.
*/
Schafkopf(BackendServer server) {
this.server = server;
System.out.println("SchaffKopfGame erstellt");
}
public Player[] getPlayer() {
return player;
}
/** Sends all Trumpf Karten of the current GameType to the Frontend. */
public void showTrumpf() {
server.sendMessageToAllFrontendEndpoints(spiel.getTrumpfKarten().getJson());
}
/** Sends all Farb Karten of the current GameType to the Frontend. */
public void showFarbe() {
server.sendMessageToAllFrontendEndpoints(spiel.getFarbKarten().getJson());
}
/** Waits for a Card and returns a Karte Object. */
public Karte wartetAufKarte() {
String uid = null;
System.out.println("Starte Warten auf Karte");
try {
uid = server.waitForCardScan();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Karte karte = KartenUtil.getIdOfUid(uid);
if (karte == null) {
System.out.println("Ungültige Karte");
return wartetAufKarte();
}
System.out.println("Karte gescannt: " + karte.getName());
System.out.println("Beende Warten auf Karte");
return karte;
}
/** Set GameState to "started" and start Game Thread. */
public void startGame() {
if (gameState.getGamePhase() != GamePhase.GAME_STOP) {
System.out.println("Game already started!");
server.sendMessageToAllFrontendEndpoints("Game already started!");
} else {
gameState = new GameState(GamePhase.GAME_START);
setAndSendGameState(gameState);
System.out.println("Start Game");
// KartenListe botHand = KartenUtil.zieheZufallsHand(8);
KartenListe botHand = new KartenListe();
botHand.addKarten(Karte.EICHEL_7);
botHand.addKarten(Karte.SCHELL_7);
botHand.addKarten(Karte.BLATT_7);
botHand.addKarten(Karte.EICHEL_X);
botHand.addKarten(Karte.HERZ_X);
botHand.addKarten(Karte.HERZ_7);
botHand.addKarten(Karte.EICHEL_U);
botHand.addKarten(Karte.EICHEL_O);
for (Player currentPlayer : player) {
if (currentPlayer instanceof BotPlayer botPlayer) {
// Perform actions specific to BotPlayer
botPlayer.setCards(botHand); // Replace with the actual method you want to call
}
}
spielThread = new Thread(() -> new Spielablauf(this, spiel));
spielThread.start();
}
}
/** Set GameState to "stopped" and interrupt Game Thread. */
public void stopGame() {
if (gameState.getGamePhase() == GamePhase.GAME_STOP) {
System.out.println("no active Game!");
server.sendMessageToAllFrontendEndpoints("no active Game!");
} else {
gameState = new GameState(GamePhase.GAME_STOP);
setAndSendGameState(gameState);
}
spielThread.interrupt();
}
/** Set GameType. */
public void setGame(String message) {
System.out.println("Set Game: " + message);
server.sendMessageToAllFrontendEndpoints("Set Game: " + message);
switch (message) {
case "setgame:herzsolo":
this.spiel = new FarbSoloController(0, KartenFarbe.HERZ);
break;
case "setgame:blattsolo":
this.spiel = new FarbSoloController(0, KartenFarbe.BLATT);
break;
case "setgame:eichelsolo":
this.spiel = new FarbSoloController(0, KartenFarbe.EICHEL);
break;
case "setgame:schellsolo":
this.spiel = new FarbSoloController(0, KartenFarbe.SCHELL);
break;
case "setgame:wenz":
this.spiel = new WenzController(0);
break;
case "setgame:geier":
this.spiel = new GeierController(0);
break;
case "setgame:eichelwenz":
this.spiel = new FarbWenzController(0, KartenFarbe.EICHEL);
break;
case "setgame:herzwenz":
this.spiel = new FarbWenzController(0, KartenFarbe.HERZ);
break;
case "setgame:blattwenz":
this.spiel = new FarbWenzController(0, KartenFarbe.BLATT);
break;
case "setgame:schellwenz":
this.spiel = new FarbWenzController(0, KartenFarbe.SCHELL);
break;
case "setgame:eichelgeier":
this.spiel = new FarbGeierController(0, KartenFarbe.EICHEL);
break;
case "setgame:herzgeier":
this.spiel = new FarbGeierController(0, KartenFarbe.HERZ);
break;
case "setgame:blattgeier":
this.spiel = new FarbGeierController(0, KartenFarbe.BLATT);
break;
case "setgame:schellgeier":
this.spiel = new FarbGeierController(0, KartenFarbe.SCHELL);
break;
case "setgame:sauspiel":
this.spiel = new SauSpielController(0, KartenFarbe.EICHEL);
break;
default:
System.out.println("Ungültiges Spiel");
}
}
public void setAndSendGameState(GameState gameState) {
this.gameState = gameState;
this.server.sendMessageToAllFrontendEndpoints(this.gameState.getJson());
}
public GameState getGameState() {
return this.gameState;
}
}

View File

@@ -1,13 +0,0 @@
package org.schafkopf.cardreader;
import org.schafkopf.BackendServer;
/** Class that represents one Card Reader. */
public abstract class CardReader {
protected static BackendServer server;
public CardReader(BackendServer server) {
this.server = server;
}
}

View File

@@ -1,83 +0,0 @@
package org.schafkopf.cardreader;
import com.fazecast.jSerialComm.SerialPort;
import io.github.cdimascio.dotenv.Dotenv;
import java.io.UnsupportedEncodingException;
import org.schafkopf.BackendServer;
/** Class that represents the NFC Reader. */
public class UsbCardReader extends CardReader {
private volatile boolean isRunning = true;
Dotenv dotenv = Dotenv.configure().directory("./").load();
private final String comPort = dotenv.get("COM_PORT");
/**
* Creates an Instance of the KartenLeser.
*
* @param server Backend Server to call methods on.
*/
public UsbCardReader(BackendServer server) {
super(server);
new Thread(this::run).start();
}
public void stop() {
isRunning = false;
}
/** run the reader. */
public void run() {
SerialPort[] ports = SerialPort.getCommPorts();
SerialPort selectedPort = null;
for (SerialPort port : ports) {
if (port.getSystemPortName().equals(this.comPort)) {
selectedPort = port;
break;
}
}
if (selectedPort == null) {
System.out.println(this.comPort + " not found");
return;
}
if (ports.length == 0) {
System.out.println("No serial ports found");
return;
}
SerialPort serialPort = selectedPort; // You may need to adjust this based on your setup
serialPort.setBaudRate(115200);
if (serialPort.openPort()) {
System.out.println("Serial port opened successfully");
try {
while (isRunning) {
if (serialPort.bytesAvailable() > 0) {
byte[] buffer = new byte[serialPort.bytesAvailable()];
int bytesRead = serialPort.readBytes(buffer, buffer.length);
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
server.nfcGelesen(data);
}
// Optional: Add a delay to avoid consuming too much CPU
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} finally {
serialPort.closePort();
System.out.println("Serial port closed");
}
} else {
System.out.println("Failed to open serial port");
}
}
}

View File

@@ -1,23 +0,0 @@
package org.schafkopf.player;
import org.schafkopf.Schafkopf;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.spielcontroller.SpielController;
/**
* Player that plays in real life.
*/
public class LocalPlayer extends Player {
private final Schafkopf schafkopf;
public LocalPlayer(Schafkopf schafkopf) {
this.schafkopf = schafkopf;
}
@Override
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten) {
return schafkopf.wartetAufKarte();
}
}

View File

@@ -1,11 +0,0 @@
package org.schafkopf.player;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.spielcontroller.SpielController;
/** Class that represents one Player of the game. */
public abstract class Player {
public abstract Karte play(
SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 957 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

Before

Width:  |  Height:  |  Size: 496 B

View File

@@ -1,15 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
<script type="module" crossorigin src="/assets/index-6278b98f.js"></script>
<link rel="stylesheet" href="/assets/index-08f560d4.css">
</head>
<body class="bg-gray-800">
<div id="app"></div>
</body>
</html>

View File

@@ -8,12 +8,16 @@
"name": "frontend",
"version": "0.0.0",
"dependencies": {
"vue": "^3.4.21"
"bootstrap-icons": "^1.11.3",
"ioc-service-container": "^1.6.1",
"vue": "^3.4.21",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@typescript-eslint/parser": "^7.7.0",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"daisyui": "^4.10.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.25.0",
@@ -1078,6 +1082,11 @@
"@vue/shared": "3.4.23"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.1",
"resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
"integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
},
"node_modules/@vue/language-core": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.13.tgz",
@@ -1307,6 +1316,21 @@
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"dev": true
},
"node_modules/bootstrap-icons": {
"version": "1.11.3",
"resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.11.3.tgz",
"integrity": "sha512-+3lpHrCw/it2/7lBL15VR0HEumaBss0+f/Lb6ZvHISn1mlK83jjFpooTLsMWbIjJMDjDjOExMsTxnXSIT4k4ww==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/twbs"
},
{
"type": "opencollective",
"url": "https://opencollective.com/bootstrap"
}
]
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
@@ -1518,6 +1542,16 @@
"node": ">= 8"
}
},
"node_modules/css-selector-tokenizer": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz",
"integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==",
"dev": true,
"dependencies": {
"cssesc": "^3.0.0",
"fastparse": "^1.1.2"
}
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@@ -1535,6 +1569,34 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/culori": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz",
"integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
}
},
"node_modules/daisyui": {
"version": "4.10.2",
"resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.10.2.tgz",
"integrity": "sha512-eCWS1W/JPyxW9IvlgW5m0R6rp9ZhRsBTW37rvEUthckkjsV04u8XipV519OoccSA46ixhSJa3q7XLI1WUFtRCA==",
"dev": true,
"dependencies": {
"css-selector-tokenizer": "^0.8",
"culori": "^3",
"picocolors": "^1",
"postcss-js": "^4"
},
"engines": {
"node": ">=16.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/daisyui"
}
},
"node_modules/de-indent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
@@ -1958,6 +2020,12 @@
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true
},
"node_modules/fastparse": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
"integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==",
"dev": true
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -2246,6 +2314,11 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true
},
"node_modules/ioc-service-container": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ioc-service-container/-/ioc-service-container-1.6.1.tgz",
"integrity": "sha512-eXWKOjYubIyi7j+O3ZeJB401h9VO9TgTqkL8vXHscQfdY+VT9ng6N/bxKRjKbk9wJVIjhUOaFLPUQZZpEdW+XA=="
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3674,6 +3747,20 @@
"eslint": ">=6.0.0"
}
},
"node_modules/vue-router": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.2.tgz",
"integrity": "sha512-hKQJ1vDAZ5LVkKEnHhmm1f9pMiWIBNGF5AwU67PdH7TyXCj/a4hTccuUuYCAMgJK6rO/NVYtQIEN3yL8CECa7Q==",
"dependencies": {
"@vue/devtools-api": "^6.5.1"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/vue-template-compiler": {
"version": "2.7.16",
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",

View File

@@ -5,16 +5,20 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc && vite build",
"build": "vue-tsc && vite build --emptyOutDir",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.21"
"bootstrap-icons": "^1.11.3",
"ioc-service-container": "^1.6.1",
"vue": "^3.4.21",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@typescript-eslint/parser": "^7.7.0",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"daisyui": "^4.10.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.25.0",

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Some files were not shown because too many files have changed in this diff Show More