Message types and server (#44)

* message type and handling

* deleted web-content and fixed bug

* edited main page
This commit is contained in:
Valentin Heiserer
2024-04-23 21:54:31 +02:00
committed by GitHub
parent cab2d36f48
commit a0a1cfaa4a
79 changed files with 1737 additions and 637 deletions

View File

@@ -135,5 +135,11 @@
<groupId>io.github.cdimascio</groupId>
<version>3.0.0</version>
</dependency>
<!-- Jackson for JSON serialization/deserialization -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
</dependencies>
</project>

View File

@@ -41,7 +41,7 @@
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
<mainClass>org.schafkopf.SchafkopfClient</mainClass>
</manifest>
</archive>
</configuration>

View File

@@ -1,63 +1,44 @@
package org.schafkopf;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import jakarta.servlet.DispatcherType;
import java.awt.Desktop;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
import java.net.URISyntaxException;
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 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.cardreader.UsbCardReader;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.LocalPlayer;
import org.schafkopf.player.Player;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/** Main Class that represents the Backend Server. */
/**
* Main Class that represents the Backend Server.
*/
public class BackendServer implements MessageSender {
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 Server server;
private final List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
private DedicatedServerConnection dedicatedServerConnection;
/** Creates an Instance of the Backend Server. */
public BackendServer() throws URISyntaxException, IOException {
/**
* Creates an Instance of the Backend Server.
*/
public BackendServer(String hostName, int port, boolean openFrontend,
MessageListener messageListener) throws Exception {
server = new Server();
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
connector = new ServerConnector(server);
connector.setHost(address.getHostName());
connector.setPort(address.getPort());
ServerConnector connector = new ServerConnector(server);
connector.setHost(hostName);
connector.setPort(port);
server.addConnector(connector);
schafkopfGame = new Schafkopf(new Player[]{new BotPlayer(), new LocalPlayer(this),
new LocalPlayer(this),
new LocalPlayer(this)}, this);
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);
@@ -71,11 +52,9 @@ public class BackendServer implements MessageSender {
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);
context.addServlet(new ServletHolder("frontend", DefaultServlet.class), "/");
// Configure specific websocket behavior
JettyWebSocketServletContainerInitializer.configure(
@@ -85,83 +64,25 @@ public class BackendServer implements MessageSender {
wsContainer.setMaxTextMessageSize(65535);
wsContainer.setIdleTimeout(Duration.ofDays(300000));
// Add websockets
wsContainer.addMapping("/schafkopf-events/*", new FrontendEndpointCreator(this));
wsContainer.addMapping("/schafkopf-events/*",
new FrontendEndpointCreator(this, messageListener));
});
// Integrate simple HTTP server
startHttpServer();
URI uri = new URI("http://localhost:8081"); // Replace with your target URL
Desktop.getDesktop().browse(uri);
startDedicatedServerConnectionThread();
}
private void startDedicatedServerConnectionThread() {
dedicatedServerConnection = new DedicatedServerConnection(this);
dedicatedServerConnection.connect();
}
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();
if (openFrontend) {
URI uri = new URI("http://" + hostName + ":" + port); // Replace with your target URL
Desktop.getDesktop().browse(uri);
}
}
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
}
// Start the server in a separate thread
Thread serverThread = new Thread(() -> {
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) {
server.start();
server.join(); // Wait for server to finish execution
} catch (Exception 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.start();
server.join();
});
serverThread.start();
}
private void configureCors(ServletContextHandler context) {
@@ -205,39 +126,14 @@ public class BackendServer implements MessageSender {
}
}
/** 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();
}
public void startDedicatedServerGame() {
dedicatedServerConnection.start();
}
@Override
public void sendMessage(String message) {
public void sendMessage(SchafkopfBaseMessage message) {
sendMessageToAllFrontendEndpoints(
message.getBaseMessage().toString());
}
public void sendMessageTest(String message) {
sendMessageToAllFrontendEndpoints(message);
}
}

View File

@@ -1,6 +1,8 @@
package org.schafkopf;
import com.google.gson.JsonObject;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
@@ -10,30 +12,58 @@ 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;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
/** Main Class that represents the Backend Server. */
/**
* Main Class that represents the Backend Server.
*/
@WebSocket
public class DedicatedServerConnection {
public class DedicatedServerConnection implements MessageSender {
private final MessageSender messageSender;
private final MessageListener messageListener;
private final CountDownLatch closeLatch;
private final CountDownLatch connectionLatch;
private static Session session;
public DedicatedServerConnection(MessageSender messageSender) {
this.messageSender = messageSender;
/**
* Class that represents one Frontend Connection.
*/
public DedicatedServerConnection(String address, MessageListener messageListener) {
URI uri = null;
try {
uri = new URI(address);
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
this.messageListener = messageListener;
this.closeLatch = new CountDownLatch(1);
this.connectionLatch = new CountDownLatch(1);
String host = uri.getHost();
int port = uri.getPort();
connect("ws://" + host + ":" + port);
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;
System.out.println("Connected to server.");
connectionLatch.countDown();
}
@OnWebSocketMessage
public void onMessage(String message) {
messageSender.sendMessage(message);
System.out.println("Received message from server: " + message);
messageListener.receiveMessage(message);
}
@OnWebSocketClose
@@ -47,10 +77,14 @@ public class DedicatedServerConnection {
System.err.println("Error occurred: " + cause.getMessage());
}
/** Main Class that represents the Backend Server. */
public static void sendMessage(String message) {
/**
* Main Class that represents the Backend Server.
*/
@Override
public void sendMessage(SchafkopfBaseMessage message) {
try {
session.getRemote().sendString(message);
session.getRemote().sendString(
new SchafkopfMessage(SchafkopfMessageOrigin.BACKEND, message).getMessageAsString());
System.out.println("Sent message to server: " + message);
} catch (Exception e) {
System.err.println("Error sending message: " + e.getMessage());
@@ -61,15 +95,15 @@ public class DedicatedServerConnection {
closeLatch.await();
}
/** Main Class that represents the Backend Server. */
public void connect() {
/**
* Main Class that represents the Backend Server.
*/
public void connect(String serverUri) {
Thread connectionThread = new Thread(() -> {
try {
String serverUri = "ws://localhost:8085/";
WebSocketClient client = new WebSocketClient();
try {
client.start();
//DedicatedServerConnection socketClient = new DedicatedServerConnection();
HeartbeatSender heartbeatSender = new HeartbeatSender(this);
heartbeatSender.start(); // Start sending heartbeat messages
URI uri = new URI(serverUri);
@@ -95,7 +129,23 @@ public class DedicatedServerConnection {
start();
}
public static void start() {
sendMessage("START_GAME");
/**
* Class that represents one Frontend Connection.
*/
public void start() {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("message_type", "START_GAME");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.START_GAME));
}
/**
* Class that represents one Frontend Connection.
*/
public void join() {
JsonObject messageObject = new JsonObject();
messageObject.addProperty("message_type", "JOIN_GAME");
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.JOIN_GAME));
}
}

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,20 +38,9 @@ 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 (messageListener != null) {
messageListener.receiveMessage(message); // Notify the listener
}
if (message.contains("stopsimulation")) {
backendServer.schafkopfGame.stopGame();
}
if (message.contains("startdedicated")) {
backendServer.startDedicatedServerGame();
}
}
@Override
@@ -59,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

@@ -2,9 +2,13 @@ package org.schafkopf;
import java.util.Timer;
import java.util.TimerTask;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
/** Creates an Instance of the Backend Server. */
/**
* Creates an Instance of the Backend Server.
*/
public class HeartbeatSender {
private static final int HEARTBEAT_INTERVAL = 15000; // 1 minute
private final DedicatedServerConnection client;
@@ -13,13 +17,16 @@ public class HeartbeatSender {
this.client = client;
}
/** Creates an Instance of the Backend Server. */
/**
* Creates an Instance of the Backend Server.
*/
public void start() {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
client.sendMessage("HEARTBEAT SYN"); // Send a heartbeat message
client.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.HEARTBEAT_SYN));
}
}, HEARTBEAT_INTERVAL, HEARTBEAT_INTERVAL);
}

View File

@@ -0,0 +1,81 @@
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"
String origin = jsonObject.get("origin").getAsString();
if (SchafkopfMessageOrigin.FRONTEND.toString().equals(origin)) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
if (SchafkopfMessageType.REQUEST_SERVER_CONNECTION.toString().equals(messageType)) {
dedicatedServerConnection = new DedicatedServerConnection(
content.get("serverAddress").getAsString(),
this);
} else if ("JOIN_GAME".equals(messageType)) {
dedicatedServerConnection.join();
} else if ("START_DEDICATED_GAME".equals(messageType)) {
dedicatedServerConnection.start();
} else if (SchafkopfMessageType.PLAYER_CARD.toString().equals(messageType)) {
dedicatedServerConnection.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.PLAYER_CARD, content));
}
System.out.println("Received message from frontend server: " + jsonMessage);
} else if (SchafkopfMessageOrigin.DEDICATED_SERVER.toString().equals(origin)) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
if (SchafkopfMessageType.GET_CARD_ONLINE_PLAYER.toString().equals(messageType)) {
System.out.println("Received get_card_online_player message from dedicated server.");
} else if (SchafkopfMessageType.GAME_STATE.toString().equals(messageType)) {
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.GAME_STATE, content));
} else if (SchafkopfMessageType.ONLINE_PLAYER_HAND.toString().equals(messageType)) {
backendServer.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessage.SchafkopfMessageType.ONLINE_PLAYER_HAND,
content));
} else if (SchafkopfMessageType.SERVER_CONNECTION_SUCCESSFUL.toString().equals(messageType)) {
System.out.println("Received server_connection_successful message from dedicated server.");
backendServer.sendMessage(new SchafkopfBaseMessage(
SchafkopfMessage.SchafkopfMessageType.SERVER_CONNECTION_SUCCESSFUL));
} else if (SchafkopfMessageType.HEARTBEAT_ACK.toString().equals(messageType)) {
return;
}
System.out.println("Received message from dedicated server: " + jsonMessage);
}
}
}

View File

@@ -1,13 +1,42 @@
package org.schafkopf.cardreader;
import org.schafkopf.BackendServer;
import java.util.concurrent.CountDownLatch;
/** Class that represents one Card Reader. */
public abstract class CardReader {
protected static BackendServer server;
private CountDownLatch nfcLatch = new CountDownLatch(1);
private Boolean readingMode = false;
private String uidString = "";
public CardReader(BackendServer server) {
this.server = server;
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

@@ -16,10 +16,8 @@ public class UsbCardReader extends CardReader {
/**
* Creates an Instance of the KartenLeser.
*
* @param server Backend Server to call methods on.
*/
public UsbCardReader(BackendServer server) {
super(server);
public UsbCardReader() {
new Thread(this::run).start();
}
@@ -80,7 +78,7 @@ public class UsbCardReader extends CardReader {
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
// Process the received data
server.nfcGelesen(data);
this.nfcGelesen(data);
}
// Optional: Add a delay to avoid consuming too much CPU

View File

@@ -1,6 +1,7 @@
package org.schafkopf.player;
import org.schafkopf.BackendServer;
import org.schafkopf.cardreader.CardReader;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
@@ -11,10 +12,10 @@ import org.schafkopf.spielcontroller.SpielController;
*/
public class LocalPlayer extends Player {
private final BackendServer server;
private final CardReader cardReader;
public LocalPlayer(BackendServer server) {
this.server = server;
public LocalPlayer(CardReader cardReader) {
this.cardReader = cardReader;
}
@Override
@@ -27,7 +28,7 @@ public class LocalPlayer extends Player {
String uid = null;
System.out.println("Starte Warten auf Karte");
try {
uid = server.waitForCardScan();
uid = cardReader.waitForCardScan();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}

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

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,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite + Vue + TS</title>
<script type="module" crossorigin src="/assets/index-DZ_vcdmw.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-8PJRhba5.css">
</head>
<body class="bg-zinc-800">
<div id="app"></div>
</body>

View File

@@ -1,75 +0,0 @@
package org.schafkopf;
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.player.BotPlayer;
import org.schafkopf.player.Player;
/** Class that represents one Frontend Connection. */
public class ClientConnection extends WebSocketAdapter implements MessageSender {
private final CountDownLatch closureLatch = new CountDownLatch(1);
private DedicatedServer dedicatedServer;
private Session session;
public ClientConnection(DedicatedServer dedicatedServer) {
this.dedicatedServer = dedicatedServer;
System.out.println("new ClientConnection created.");
}
@Override
public void onWebSocketConnect(Session session) {
this.session = session;
super.onWebSocketConnect(session);
String clientIp = session.getRemoteAddress().toString();
System.out.println("Endpoint connected from ip: " + clientIp);
dedicatedServer.addFrontendEndpoint(this);
}
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
if (message.equals("HEARTBEAT SYN")) {
System.out.println("Received HEARTBEAT message from " + session.getRemoteAddress() + ".");
sendMessage("HEARTBEAT ACK");
return;
}
if (message.equals("START_GAME")) {
System.out.println("Received START_GAME message from " + session.getRemoteAddress() + ".");
dedicatedServer.addGameSession(new GameSession(new Schafkopf(new Player[] {
new BotPlayer(), new BotPlayer(), new BotPlayer(), new BotPlayer()
}, this)));
return;
}
System.out.println("Received TEXT message:" + message);
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
dedicatedServer.removeFrontendEndpoint(this);
System.out.println("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(String message) {
try {
getRemote().sendString(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,7 +1,11 @@
package org.schafkopf;
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;
@@ -13,27 +17,50 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
/** Main Class that represents the Backend Server. */
/**
* Main Class that represents the Backend Server.
*/
public class DedicatedServer {
private final Server server;
private final ServerConnector connector;
private final List<ClientConnection> clientConnections = new ArrayList<>();
private final List<SchafkopfClientConnection> clientConnections = new ArrayList<>();
private final List<GameSession> gameSessions = new ArrayList<>();
/** Creates an Instance of the Backend Server. */
private GameSession currentGameSession;
/**
* Creates an Instance of the Backend Server.
*/
public DedicatedServer() {
server = new Server();
InetSocketAddress address = new InetSocketAddress("localhost", 8085);
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(address.getHostName());
connector.setPort(address.getPort());
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
@@ -48,9 +75,13 @@ public class DedicatedServer {
// Add websockets
wsContainer.addMapping("/*", new FrontendEndpointCreator(this));
});
currentGameSession = new GameSession();
}
/** The main entrypoint of the Application. */
/**
* The main entrypoint of the Application.
*/
public static void main(String[] args) throws Exception {
DedicatedServer server = new DedicatedServer();
server.start();
@@ -79,16 +110,23 @@ public class DedicatedServer {
server.join();
}
public void addFrontendEndpoint(ClientConnection endpoint) {
public void addFrontendEndpoint(SchafkopfClientConnection endpoint) {
clientConnections.add(endpoint);
}
public void removeFrontendEndpoint(ClientConnection endpoint) {
public void removeFrontendEndpoint(SchafkopfClientConnection endpoint) {
clientConnections.remove(endpoint);
}
public void addGameSession(GameSession gameSession) {
gameSessions.add(gameSession);
}
public List<GameSession> getGameSessions() {
return gameSessions;
}
public GameSession getCurrentGameSession() {
return currentGameSession;
}
}

View File

@@ -8,6 +8,7 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
* Creater to make new Instances of the FrontendConnection.
*/
public class FrontendEndpointCreator implements JettyWebSocketCreator {
private DedicatedServer dedicatedServer;
public FrontendEndpointCreator(DedicatedServer dedicatedServer) {
@@ -18,6 +19,6 @@ public class FrontendEndpointCreator implements JettyWebSocketCreator {
public Object createWebSocket(
JettyServerUpgradeRequest jettyServerUpgradeRequest,
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
return new ClientConnection(this.dedicatedServer);
return new SchafkopfClientConnection(this.dedicatedServer);
}
}

View File

@@ -1,19 +1,86 @@
package org.schafkopf;
/** The main entrypoint of the Application. */
public class GameSession {
import java.util.ArrayList;
import java.util.List;
import org.schafkopf.SchafkopfException.NotEnoughPlayersException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.player.BotPlayer;
import org.schafkopf.player.OnlinePlayer;
import org.schafkopf.player.Player;
/**
* The main entrypoint of the Application.
*/
public class GameSession implements MessageSender {
private Schafkopf schafkopf;
private List<Player> player;
/** The main entrypoint of the Application. */
public GameSession(Schafkopf schafkopf) {
this.schafkopf = schafkopf;
System.out.println("new GameSession created.");
startGame();
private List<SchafkopfClientConnection> clients;
private Thread spielThread;
/**
* The main entrypoint of the Application.
*/
public GameSession() {
player = new ArrayList<>();
clients = new ArrayList<>();
}
private void startGame() {
schafkopf.startGame();
/**
* Class that represents one Frontend Connection.
*/
public void addPlayer(SchafkopfClientConnection client) {
if (this.player.size() >= 4) {
throw new RuntimeException("Game is full");
}
System.out.println("Adding player to game: " + client);
clients.add(client);
OnlinePlayer onlinePlayer = new OnlinePlayer(client);
this.player.add(onlinePlayer);
client.setOnlinePlayer(onlinePlayer);
}
public Schafkopf getSchafkopf() {
return schafkopf;
}
void startGame() throws NotEnoughPlayersException {
int playerCount = this.player.size();
System.out.println("Starting game with " + playerCount + " players");
if (playerCount < 4) {
for (int i = 0; i < 4 - playerCount; i++) {
this.player.add(new BotPlayer());
}
}
System.out.println("Starting game with, now: " + this.player.size() + " players");
spielThread = new Thread(() -> {
try {
schafkopf = new Schafkopf(this.player.toArray(new Player[0]), this);
schafkopf.startGame();
} catch (NotEnoughPlayersException e) {
throw new RuntimeException(e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
spielThread.start();
// schafkopf = new Schafkopf(this.player.toArray(new Player[0]), this);
}
@Override
public void sendMessage(SchafkopfBaseMessage message) {
System.out.println("Sending message to Client: " + message);
for (SchafkopfClientConnection client : clients) {
client.sendMessage(message);
}
}
}

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,134 @@
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.NotEnoughPlayersException;
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;
/**
* Class that represents one Frontend Connection.
*/
public class SchafkopfClientConnection extends WebSocketAdapter implements MessageSender {
private final CountDownLatch connectionLatch;
private final CountDownLatch closureLatch = new CountDownLatch(1);
private DedicatedServer dedicatedServer;
private GameSession gameSession;
private Session session;
private OnlinePlayer onlinePlayer;
/**
* Class that represents one Frontend Connection.
*/
public SchafkopfClientConnection(DedicatedServer dedicatedServer) {
this.dedicatedServer = dedicatedServer;
this.connectionLatch = new CountDownLatch(1);
System.out.println("new ClientConnection created.");
}
@Override
public void onWebSocketConnect(Session session) {
this.session = session;
super.onWebSocketConnect(session);
String clientIp = session.getRemoteAddress().toString();
System.out.println("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 "frontend"
String origin = jsonObject.get("origin").getAsString();
if (SchafkopfMessageOrigin.BACKEND.toString().equals(origin)) {
JsonObject message = jsonObject.getAsJsonObject("message");
JsonObject content = message.getAsJsonObject("content");
String messageType = message.get("message_type").getAsString();
if ("HEARTBEAT_SYN".equals(messageType)) {
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.HEARTBEAT_ACK));
return;
} else if (SchafkopfMessageType.JOIN_GAME.toString().equals(messageType)) {
gameSession = dedicatedServer.getCurrentGameSession();
dedicatedServer.getCurrentGameSession().addPlayer(this);
} else if ("START_GAME".equals(messageType)) {
System.out.println("Received START_GAME message from " + session.getRemoteAddress() + ".");
try {
dedicatedServer.getCurrentGameSession().startGame();
} catch (NotEnoughPlayersException e) {
sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR,
"Not enough players to start the game."));
}
} else if (SchafkopfMessageType.PLAYER_CARD.toString().equals(messageType)) {
onlinePlayer.receiveCard(Karte.valueOf(content.get("card").getAsString()));
} else if ("list_online_games".equals(messageType)) {
System.out.println(
"Received list_online_games message from " + session.getRemoteAddress() + ".");
}
System.out.println(
"Received message from Client " + session.getRemoteAddress() + " " + jsonMessage);
}
}
public void setOnlinePlayer(OnlinePlayer onlinePlayer) {
this.onlinePlayer = onlinePlayer;
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
dedicatedServer.removeFrontendEndpoint(this);
System.out.println("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);
System.out.println("Sending message to Client: " + schafkopfMessage.getMessageAsString());
try {
getRemote().sendString(schafkopfMessage.getMessageAsString());
} catch (IOException e) {
e.printStackTrace();
}
}
}

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"),
@@ -53,7 +57,9 @@ public class GameState {
this.currentPlayer = player;
}
/** GameState. */
/**
* GameState.
*/
public GameState(GamePhase phase, Integer player, Karte card, KartenFarbe color, boolean trumpf) {
this.gamePhase = phase;
this.currentPlayer = player;
@@ -62,19 +68,34 @@ public class GameState {
this.trumpf = trumpf;
}
/** GameState. */
/**
* GameState.
*/
public GameState(GamePhase phase, Integer 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

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

View File

@@ -1,25 +1,31 @@
package org.schafkopf;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.schafkopf.GameState.GamePhase;
import org.schafkopf.SchafkopfException.NotEnoughPlayersException;
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
import org.schafkopf.SchafkopfMessage.SchafkopfMessageType;
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.OnlinePlayer;
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. */
/**
* 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. */
/**
* The game controller. This is the class that implements the game logic.
*/
private SpielController spiel = new SauSpielController(0, KartenFarbe.EICHEL);
private final Player[] player;
@@ -32,8 +38,13 @@ public class Schafkopf {
*
* @param messageSender MessageSender
*/
public Schafkopf(Player[] player, 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");
}
@@ -42,23 +53,24 @@ public class Schafkopf {
return player;
}
/** Set GameState to "started" and start Game Thread. */
public void startGame() {
/**
* 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("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--) {
System.out.println("Austeilen: " + austeilen.size());
System.out.println("Bot Hand: " + i);
botHand.addKarten(austeilen.removeKarten(austeilen.getByIndex(i)));
}
System.out.println("Bot Hand: " + botHand.getJson().toString());
@@ -66,17 +78,35 @@ public class Schafkopf {
}
}
spielThread = new Thread(() -> new Spielablauf(this, spiel));
for (Player currentPlayer : player) {
if (currentPlayer instanceof OnlinePlayer) {
Karte[] karten = new Karte[8];
for (int i = 7; i >= 0; i--) {
karten[i] = austeilen.removeKarten(austeilen.getByIndex(i));
}
Gson gson = new Gson();
JsonObject messageObject = new JsonObject();
messageObject.add("cards", gson.toJsonTree(karten));
spielThread.start();
messageSender.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.ONLINE_PLAYER_HAND, messageObject));
}
}
// spielThread = new Thread(() -> new Spielablauf(this, spiel));
//
// spielThread.start();
new Spielablauf(this, spiel);
}
}
/** Set GameState to "stopped" and interrupt Game Thread. */
/**
* 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("no active Game!");
messageSender.sendMessage(new SchafkopfBaseMessage(SchafkopfMessageType.UNKNOWN_ERROR));
} else {
gameState = new GameState(GamePhase.GAME_STOP);
setAndSendGameState(gameState);
@@ -85,68 +115,13 @@ public class Schafkopf {
spielThread.interrupt();
}
/** Set GameType. */
public void setGame(String message) {
System.out.println("Set Game: " + message);
messageSender.sendMessage("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");
}
}
/**
* Class that represents one Frontend Connection.
*/
public void setAndSendGameState(GameState gameState) {
this.gameState = gameState;
this.messageSender.sendMessage(this.gameState.getJson().toString());
this.messageSender.sendMessage(
new SchafkopfBaseMessage(SchafkopfMessageType.GAME_STATE, gameState.getJson()));
}
public GameState getGameState() {

View File

@@ -0,0 +1,37 @@
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
}
/**
* 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,141 @@
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;
public SchafkopfBaseMessage(SchafkopfMessageType messageType, String content) {
this.message = buildBaseMessage(messageType, content);
}
public SchafkopfBaseMessage(SchafkopfMessageType messageType, JsonObject content) {
this.message = buildBaseMessage(messageType, content);
}
public SchafkopfBaseMessage(SchafkopfMessageType 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,
HEARTBEAT_SYN,
HEARTBEAT_ACK,
GET_CARD_ONLINE_PLAYER,
ONLINE_PLAYER_HAND,
GAME_STATE,
SERVER_CONNECTION_SUCCESSFUL,
REQUEST_SERVER_CONNECTION,
START_GAME,
JOIN_GAME,
PLAYER_CARD
}
/**
* 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,7 +43,7 @@ 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++) {

View File

@@ -3,12 +3,14 @@ package org.schafkopf.karte;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
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,10 @@ public class KartenListe {
return this.kartenListe;
}
public void shuffle() {
Collections.shuffle(this.kartenListe);
}
/**
* A Class that represents a list of Cards.
*/
@@ -158,6 +164,24 @@ 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.
*/

View File

@@ -0,0 +1,46 @@
package org.schafkopf.player;
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 MessageSender messageSender;
private final BlockingQueue<Karte> receivedCardQueue = new LinkedBlockingQueue<>();
public OnlinePlayer(MessageSender messageSender) {
this.messageSender = messageSender;
}
@Override
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten)
throws InterruptedException {
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();
System.out.println("Karte gespielt: " + spielKarte);
return spielKarte;
}
/**
* Class that represents one Frontend Connection.
*/
public void receiveCard(Karte receivedCard) {
System.out.println("Received Card before Queue: " + receivedCard.getName());
receivedCardQueue.add(receivedCard);
}
}

View File

@@ -4,8 +4,12 @@ import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.spielcontroller.SpielController;
/** Class that represents one Player of the game. */
/**
* Class that represents one Player of the game.
*/
public abstract class Player {
public abstract Karte play(
SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten);
SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten)
throws InterruptedException;
}

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

@@ -9,12 +9,16 @@
"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",

View File

@@ -1,235 +1,8 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import CardComp from './components/CardComponent.vue';
import {BackendMessage, Card, GamePhase, GameState} from "./BackendMessage";
const messageFromServer = ref<string[]>([]);
const gameStateText = ref<string>("Schafkopf");
const gameInfoText = ref<string>("");
const socket = ref<WebSocket | null>();
const tableCards = ref<Card[]>([]);
const botCards = ref(0);
const trickCard = ref<Card>();
const showGameSelect = ref(true);
function startSimulation(): void {
sendMessageToServer("startsimulation");
}
function startDedicated(): void {
sendMessageToServer("startdedicated");
}
function stopSimulation(): void {
sendMessageToServer("stopsimulation");
}
function showTrumpf(): void {
tableCards.value = [];
sendMessageToServer("showtrumpf");
}
function showFarben(): void {
tableCards.value = [];
sendMessageToServer("showfarben");
}
function setGame(game: string): void {
sendMessageToServer(game);
}
function sendMessageToServer(message: string) {
if (socket.value) {
socket.value.send(message);
console.log("Sent message to server:", message);
}
}
function showGameState(gamestate: GameState) {
switch (gamestate.gamePhase) {
case GamePhase.GAME_START:
gameStateText.value = "Spiel startet";
showGameSelect.value = false;
botCards.value = 8;
break;
case GamePhase.TRICK_START:
gameStateText.value = "Runde startet";
tableCards.value = [];
trickCard.value = undefined
gameInfoText.value = "";
break;
case GamePhase.WAIT_FOR_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " muss eine Karte legen.";
break;
case GamePhase.PLAYER_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " hat eine Karte gespielt.";
if (gamestate.currentPlayer === 0) {
botCards.value--
}
if (gamestate.trumpf) {
gameInfoText.value = "TRUMPF";
} else {
gameInfoText.value = gamestate.color?.toString() ?? "ERROR";
}
tableCards.value.push(gamestate.card!);
break;
case GamePhase.PLAYER_TRICK:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " sticht.";
trickCard.value = gamestate.card
break;
case GamePhase.GAME_STOP:
showGameSelect.value = true;
break;
default:
gameStateText.value = "Fehler";
}
}
onMounted(() => {
const websocketIp = import.meta.env.VITE_APP_WEBSOCKET_IP;
// Open a WebSocket connection when the component is mounted
socket.value = new WebSocket("ws://" + websocketIp + ":8080/schafkopf-events/");
// Handle connection opened
socket.value.addEventListener("open", (event) => {
console.log("WebSocket connection opened:", event);
});
// Handle messages received from the server
socket.value.addEventListener("message", (event) => {
const message: BackendMessage = JSON.parse(event.data);
console.log(message)
if ('gamestate' in message) {
console.log(message.gamestate)
showGameState(message.gamestate)
} else {
console.log("Invalid BackendMessage format: ", event);
}
});
// Handle connection closed
socket.value.addEventListener("close", (event) => {
console.log("WebSocket connection closed:", event);
});
// Handle errors
socket.value.addEventListener("error", (event) => {
console.error("WebSocket error:", event);
});
});
</script>
<template>
<div>
<div v-for="message in messageFromServer" :key="message">{{ message }}</div>
<div v-if="showGameSelect">
<!-- <div class="flex gap-2 place-content-center">-->
<!-- <button @click="setGame('setgame:sauspiel')">Sauspiel</button>-->
<!-- <button @click="setGame('setgame:herzsolo')">herzsolo</button>-->
<!-- <button @click="setGame('setgame:eichelsolo')">eichelsolo</button>-->
<!-- <button @click="setGame('setgame:blattsolo')">blattsolo</button>-->
<!-- <button @click="setGame('setgame:schellsolo')">schellsolo</button>-->
<!-- <button @click="setGame('setgame:eichelwenz')">eichelwenz</button>-->
<!-- <button @click="setGame('setgame:blattwenz')">blattwenz</button>-->
<!-- <button @click="setGame('setgame:herzwenz')">herzwenz</button>-->
<!-- <button @click="setGame('setgame:schellwenz')">schellwenz</button>-->
<!-- <button @click="setGame('setgame:eichelgeier')">eichelgeier</button>-->
<!-- <button @click="setGame('setgame:blattgeier')">blattgeier</button>-->
<!-- <button @click="setGame('setgame:herzgeier')">herzgeier</button>-->
<!-- <button @click="setGame('setgame:schellgeier')">schellgeier</button>-->
<!-- <button @click="setGame('setgame:geier')">Geier</button>-->
<!-- <button @click="setGame('setgame:wenz')">Wenz</button>-->
<!-- </div>-->
<!-- <div class="flex gap-2 place-content-center">-->
<!-- <button @click="showFarben">Zeige alle Farben</button>-->
<!-- <button @click="showTrumpf">Zeige alle Trumpfkarten</button>-->
<!-- </div>-->
<div class="flex gap-2 place-content-center">
<button class="v-button" @click="startDedicated">Dedicated Starten</button>
<button class="v-button" @click="startSimulation">Starten</button>
<button class="v-button" @click="stopSimulation">Stoppen</button>
</div>
</div>
<div v-else>
<div class="flex gap-2 place-content-center">
<button class="v-button" @click="stopSimulation">Stoppen</button>
</div>
<h1 class=" top-52 text-white font-bold text-6xl absolute text-center w-full">{{ gameInfoText }}</h1>
<h1 class=" top-64 text-white font-bold text-6xl absolute text-center w-full">{{ gameStateText }}</h1>
<div v-if="tableCards.length > 0">
<!-- <div class="grid grid-cols-4 place-content-center">-->
<!-- <CardComp v-for="card in tableCards" :card="card" class="md" />-->
<!-- </div>-->
<CardComp v-if="tableCards.length > 0" :card="tableCards[0]" class="absolute card1 md"/>
<CardComp v-if="tableCards.length > 1" :card="tableCards[1]" class="absolute card2 md"/>
<CardComp v-if="tableCards.length > 2" :card="tableCards[2]" class="absolute card3 md"/>
<CardComp v-if="tableCards.length > 3" :card="tableCards[3]" class="absolute card4 md"/>
</div>
<div class="absolute left-0 top-1/2 transform -translate-y-1/2">
<CardComp v-if="trickCard" :card="trickCard" class="xl"/>
</div>
<div class="absolute bottom-0 w-full">
<div class="flex flex-row gap-3 w-fit mx-auto justify-center">
<CardComp v-for="i in botCards" :key="i" :card="Card.BACK" class="sm"/>
</div>
</div>
</div>
</div>
<router-view/>
</template>
<style lang="scss">
$card-height: 24rem;
.card0 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card1 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card2 {
z-index: 2;
top: calc(50% + ($card-height * 0.10));
left: calc(50% - ($card-height * 0.3));
transform: rotate(50deg) translate(-50%, -50%);
}
.card3 {
z-index: 3;
top: calc(50% - ($card-height * 0.125));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-30deg) translate(-50%, -50%);
}
.card4 {
z-index: 4;
top: calc(50% - ($card-height * 0.4));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-60deg) translate(-50%, -50%);
}
@import "bootstrap-icons/font/bootstrap-icons.css";
</style>

View File

@@ -71,6 +71,13 @@ export enum GamePhase {
PLAYER_TRICK = "PLAYER_TRICK"
}
export enum MessageType {
PLAYER_CARD = "PLAYER_CARD",
START_DEDICATED_GAME = "START_DEDICATED_GAME",
JOIN_GAME = "JOIN_GAME",
REQUEST_SERVER_CONNECTION = "REQUEST_SERVER_CONNECTION",
}
// Define the interface for an array of cards
export interface CardArray {
cards: Card[];
@@ -92,8 +99,13 @@ export interface GameState {
export interface GameStateJson {
gamestate: GameState
gamestate: GameState,
}
export interface EmptyMessage {
message_type: string;
content: GameStateJson | CardArray | CardObject;
}
// Define a union type for all possible message types
export type BackendMessage = CardObject | CardArray | GameStateJson;
export type BackendMessage = EmptyMessage

View File

@@ -21,7 +21,7 @@ watch(() => props.card, (newCard) => {
<template>
<div
:class="{'!scale-105 !z-10 !top-1/2 !left-1/2' : focus}" class="card transition overflow-hidden"
@click="focus=!focus">
>
<img class="h-full rounded-[1rem] mx-auto" :src="imgSrc" alt="card">
</div>
</template>

View File

@@ -1,5 +1,24 @@
import { createApp } from 'vue'
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'
import {createRouter, createWebHistory} from "vue-router";
import {setupService} from "./services/DependencyInjection.ts";
createApp(App).mount('#app')
const routes = [
{path: '/', component: () => import('./pages/MainMenu.vue'),},
{path: '/online', component: () => import('./pages/OnlineGameList.vue'),},
{path: '/localgame', component: () => import('./pages/LocalGame.vue'),},
{path: '/dedicatedgame', component: () => import('./pages/DedicatedGame.vue'),},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
const websocketIp = import.meta.env.VITE_APP_WEBSOCKET_IP;
setupService("ws://" + websocketIp + ":8080/schafkopf-events/");
const app = createApp(App)
app.use(router)
app.mount('#app')

View File

@@ -0,0 +1,181 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {scg} from 'ioc-service-container';
import CardComp from '../components/CardComponent.vue';
import {BackendMessage, Card, GamePhase, GameState, MessageType} from "../BackendMessage";
const backendConnection = scg("BackendConnection");
const messageFromServer = ref<string[]>([]);
const gameStateText = ref<string>("Schafkopf");
const gameInfoText = ref<string>("");
const socket = ref<WebSocket | null>();
const tableCards = ref<Card[]>([]);
const botCards = ref<Card[]>();
const trickCard = ref<Card>();
const showGameSelect = ref(true);
function startDedicated(): void {
backendConnection.sendMessage(MessageType.START_DEDICATED_GAME,);
}
function joinGame(): void {
backendConnection.sendMessage(MessageType.JOIN_GAME,);
}
function sendCard(cardInput: Card): void {
const index = botCards.value.findIndex(card => card === cardInput);
// If card exists in the array, remove it
if (index !== -1) {
botCards.value.splice(index, 1);
}
backendConnection.sendMessage(MessageType.PLAYER_CARD, {card: cardInput});
}
function showGameState(gamestate: GameState) {
switch (gamestate.gamePhase) {
case GamePhase.GAME_START:
gameStateText.value = "Spiel startet";
showGameSelect.value = false;
// botCards.value = 0;
break;
case GamePhase.TRICK_START:
gameStateText.value = "Runde startet";
tableCards.value = [];
trickCard.value = undefined
gameInfoText.value = "";
break;
case GamePhase.WAIT_FOR_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " muss eine Karte legen.";
break;
case GamePhase.PLAYER_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " hat eine Karte gespielt.";
if (gamestate.currentPlayer === 0) {
// botCards.value.pop();
}
if (gamestate.trumpf) {
gameInfoText.value = "TRUMPF";
} else {
gameInfoText.value = gamestate.color?.toString() ?? "ERROR";
}
tableCards.value.push(gamestate.card!);
break;
case GamePhase.PLAYER_TRICK:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " sticht.";
trickCard.value = gamestate.card
break;
case GamePhase.GAME_STOP:
showGameSelect.value = true;
break;
default:
gameStateText.value = "Fehler";
}
}
onMounted(() => {
socket.value = backendConnection.getWebSocket();
const messageListener = (message: string) => {
const message1: BackendMessage = JSON.parse(message);
console.log(message1)
if (message1.message_type === "GAME_STATE") {
console.log(message1.content)
showGameState(message1.content)
}
if (message1.message_type === "ONLINE_PLAYER_HAND") {
botCards.value = message1.content.cards;
console.log(message1.content.cards)
}
};
backendConnection.addMessageListener(messageListener);
});
</script>
<template>
<div>
<div v-for="message in messageFromServer" :key="message">{{ message }}</div>
<div v-if="showGameSelect">
<div class="flex gap-2 place-content-center">
<button class="v-button" @click="startDedicated">Starten</button>
<button class="v-button" @click="joinGame">Join</button>
</div>
</div>
<div v-else>
<div class="flex gap-2 place-content-center">
</div>
<h1 class=" top-52 text-white font-bold text-6xl absolute text-center w-full">{{ gameInfoText }}</h1>
<h1 class=" top-64 text-white font-bold text-6xl absolute text-center w-full">{{ gameStateText }}</h1>
<div v-if="tableCards.length > 0">
<!-- <div class="grid grid-cols-4 place-content-center">-->
<!-- <CardComp v-for="card in tableCards" :card="card" class="md" />-->
<!-- </div>-->
<CardComp v-if="tableCards.length > 0" :card="tableCards[0]" class="absolute card1 md"/>
<CardComp v-if="tableCards.length > 1" :card="tableCards[1]" class="absolute card2 md"/>
<CardComp v-if="tableCards.length > 2" :card="tableCards[2]" class="absolute card3 md"/>
<CardComp v-if="tableCards.length > 3" :card="tableCards[3]" class="absolute card4 md"/>
</div>
<div class="absolute left-0 top-1/2 transform -translate-y-1/2">
<CardComp v-if="trickCard" :card="trickCard" class="xl"/>
</div>
<div class="absolute bottom-0 w-full">
<div class="flex flex-row gap-3 w-fit mx-auto justify-center">
<CardComp v-for="card in botCards" :card="card" class="sm" @click="sendCard(card)"/>
</div>
</div>
</div>
</div>
<router-view/>
</template>
<style lang="scss">
$card-height: 24rem;
.card0 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card1 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card2 {
z-index: 2;
top: calc(50% + ($card-height * 0.10));
left: calc(50% - ($card-height * 0.3));
transform: rotate(50deg) translate(-50%, -50%);
}
.card3 {
z-index: 3;
top: calc(50% - ($card-height * 0.125));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-30deg) translate(-50%, -50%);
}
.card4 {
z-index: 4;
top: calc(50% - ($card-height * 0.4));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-60deg) translate(-50%, -50%);
}
</style>

View File

@@ -0,0 +1,222 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import {scg} from 'ioc-service-container';
import CardComp from '../components/CardComponent.vue';
import {BackendMessage, Card, GamePhase, GameState} from "../BackendMessage";
const backendConnection = scg("BackendConnection");
const messageFromServer = ref<string[]>([]);
const gameStateText = ref<string>("Schafkopf");
const gameInfoText = ref<string>("");
const socket = ref<WebSocket | null>();
const tableCards = ref<Card[]>([]);
const botCards = ref(0);
const trickCard = ref<Card>();
const showGameSelect = ref(true);
function startSimulation(): void {
sendMessageToServer("startsimulation");
}
function startDedicated(): void {
sendMessageToServer("startdedicated");
}
function stopSimulation(): void {
sendMessageToServer("stopsimulation");
}
function showTrumpf(): void {
tableCards.value = [];
sendMessageToServer("showtrumpf");
}
function showFarben(): void {
tableCards.value = [];
sendMessageToServer("showfarben");
}
function setGame(game: string): void {
sendMessageToServer(game);
}
function sendMessageToServer(message: string) {
if (socket.value) {
socket.value.send(message);
console.log("Sent message to server:", message);
}
}
function showGameState(gamestate: GameState) {
switch (gamestate.gamePhase) {
case GamePhase.GAME_START:
gameStateText.value = "Spiel startet";
showGameSelect.value = false;
botCards.value = 8;
break;
case GamePhase.TRICK_START:
gameStateText.value = "Runde startet";
tableCards.value = [];
trickCard.value = undefined
gameInfoText.value = "";
break;
case GamePhase.WAIT_FOR_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " muss eine Karte legen.";
break;
case GamePhase.PLAYER_CARD:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " hat eine Karte gespielt.";
if (gamestate.currentPlayer === 0) {
botCards.value--
}
if (gamestate.trumpf) {
gameInfoText.value = "TRUMPF";
} else {
gameInfoText.value = gamestate.color?.toString() ?? "ERROR";
}
tableCards.value.push(gamestate.card!);
break;
case GamePhase.PLAYER_TRICK:
gameStateText.value = "Spieler " + gamestate.currentPlayer + " sticht.";
trickCard.value = gamestate.card
break;
case GamePhase.GAME_STOP:
showGameSelect.value = true;
break;
default:
gameStateText.value = "Fehler";
}
}
onMounted(() => {
socket.value = backendConnection.getWebSocket();
const messageListener = (message: string) => {
const message1: BackendMessage = JSON.parse(message);
console.log(message1)
if ('gamestate' in message1) {
console.log(message1.gamestate)
showGameState(message1.gamestate)
}
};
backendConnection.addMessageListener(messageListener);
});
</script>
<template>
<div>
<div v-for="message in messageFromServer" :key="message">{{ message }}</div>
<div v-if="showGameSelect">
<!-- <div class="flex gap-2 place-content-center">-->
<!-- <button @click="setGame('setgame:sauspiel')">Sauspiel</button>-->
<!-- <button @click="setGame('setgame:herzsolo')">herzsolo</button>-->
<!-- <button @click="setGame('setgame:eichelsolo')">eichelsolo</button>-->
<!-- <button @click="setGame('setgame:blattsolo')">blattsolo</button>-->
<!-- <button @click="setGame('setgame:schellsolo')">schellsolo</button>-->
<!-- <button @click="setGame('setgame:eichelwenz')">eichelwenz</button>-->
<!-- <button @click="setGame('setgame:blattwenz')">blattwenz</button>-->
<!-- <button @click="setGame('setgame:herzwenz')">herzwenz</button>-->
<!-- <button @click="setGame('setgame:schellwenz')">schellwenz</button>-->
<!-- <button @click="setGame('setgame:eichelgeier')">eichelgeier</button>-->
<!-- <button @click="setGame('setgame:blattgeier')">blattgeier</button>-->
<!-- <button @click="setGame('setgame:herzgeier')">herzgeier</button>-->
<!-- <button @click="setGame('setgame:schellgeier')">schellgeier</button>-->
<!-- <button @click="setGame('setgame:geier')">Geier</button>-->
<!-- <button @click="setGame('setgame:wenz')">Wenz</button>-->
<!-- </div>-->
<!-- <div class="flex gap-2 place-content-center">-->
<!-- <button @click="showFarben">Zeige alle Farben</button>-->
<!-- <button @click="showTrumpf">Zeige alle Trumpfkarten</button>-->
<!-- </div>-->
<div class="flex gap-2 place-content-center">
<button class="v-button" @click="startSimulation">Starten</button>
<button class="v-button" @click="stopSimulation">Stoppen</button>
</div>
</div>
<div v-else>
<div class="flex gap-2 place-content-center">
<button class="v-button" @click="stopSimulation">Stoppen</button>
</div>
<h1 class=" top-52 text-white font-bold text-6xl absolute text-center w-full">{{ gameInfoText }}</h1>
<h1 class=" top-64 text-white font-bold text-6xl absolute text-center w-full">{{ gameStateText }}</h1>
<div v-if="tableCards.length > 0">
<!-- <div class="grid grid-cols-4 place-content-center">-->
<!-- <CardComp v-for="card in tableCards" :card="card" class="md" />-->
<!-- </div>-->
<CardComp v-if="tableCards.length > 0" :card="tableCards[0]" class="absolute card1 md"/>
<CardComp v-if="tableCards.length > 1" :card="tableCards[1]" class="absolute card2 md"/>
<CardComp v-if="tableCards.length > 2" :card="tableCards[2]" class="absolute card3 md"/>
<CardComp v-if="tableCards.length > 3" :card="tableCards[3]" class="absolute card4 md"/>
</div>
<div class="absolute left-0 top-1/2 transform -translate-y-1/2">
<CardComp v-if="trickCard" :card="trickCard" class="xl"/>
</div>
<div class="absolute bottom-0 w-full">
<div class="flex flex-row gap-3 w-fit mx-auto justify-center">
<CardComp v-for="i in botCards" :key="i" :card="Card.BACK" class="sm"/>
</div>
</div>
</div>
</div>
<router-view/>
</template>
<style lang="scss">
$card-height: 24rem;
.card0 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card1 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card2 {
z-index: 2;
top: calc(50% + ($card-height * 0.10));
left: calc(50% - ($card-height * 0.3));
transform: rotate(50deg) translate(-50%, -50%);
}
.card3 {
z-index: 3;
top: calc(50% - ($card-height * 0.125));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-30deg) translate(-50%, -50%);
}
.card4 {
z-index: 4;
top: calc(50% - ($card-height * 0.4));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-60deg) translate(-50%, -50%);
}
</style>

View File

@@ -0,0 +1,187 @@
<script setup lang="ts">
import {onBeforeMount, ref} from "vue";
import {useRouter} from "vue-router";
import {scg} from "ioc-service-container";
import {BackendMessage, MessageType} from "../BackendMessage.ts";
const backendConnection = scg("BackendConnection");
const serverAddress = ref("http://10.6.9.57:8085/")
const isConnected = ref<boolean>(false);
const isPingInProgress = ref<boolean>(false);
const secondsRemaining = ref<number>(10);
const checkInterval: number = 10;
const router = useRouter();
onBeforeMount(async () => {
// Call checkConnection immediately and then every 10 seconds
await checkConnectionWithCountdown();
});
async function checkConnectionWithCountdown() {
await checkConnection();
setTimeout(checkConnectionWithCountdown, checkInterval * 1000);
}
async function checkConnection(): Promise<void> {
if (!isConnected.value) {
isPingInProgress.value = true;
}
try {
// Try to fetch a resource from the internet
await fetch(serverAddress.value + "health", {mode: "no-cors"})
// If successful, set isConnected to true
isConnected.value = true;
} catch (error) {
// If an error occurs (e.g., network error), set isConnected to false
isConnected.value = false;
}
isPingInProgress.value = false;
let countDown = checkInterval;
secondsRemaining.value = countDown;
let countdownInterval = setTimeout(updateCountdown, 1000);
function updateCountdown() {
secondsRemaining.value = --countDown;
if (countDown <= 0) clearInterval(countdownInterval);
else countdownInterval = setTimeout(updateCountdown, 1000);
}
}
async function openOnlineGameList() {
backendConnection.sendMessage(MessageType.REQUEST_SERVER_CONNECTION, {serverAddress: serverAddress.value});
// Create a Promise<void> that resolves when the success message is received
const successMessageReceived = new Promise<void>((resolve) => {
const messageListener = (message: string) => {
const message1: BackendMessage = JSON.parse(message);
console.log(message)
if (message1.message_type === "SERVER_CONNECTION_SUCCESSFUL") {
// Resolve the Promise when the success message is received
resolve();
}
};
backendConnection.addMessageListener(messageListener);
});
// Wait for the success message to be received
await successMessageReceived;
// Once the success message is received, route to '/online'
await router.push("/online");
}
</script>
<template>
<div class="flex flex-col place-content-center h-screen gap-8 items-center">
<div class="grid grid-cols-1 md:grid-cols-3 items-stretch px-2 gap-8">
<div class="flex flex-col gap-6 bg-base-200 rounded-box p-8">
<div class="flex flex-col gap-4 text-center">
<h1 class="text-5xl font-bold">Lokales Spiel</h1>
<span class="text-sm">mit NFC Reader</span>
</div>
<!-- Features -->
<div class="flex flex-col">
<div class="flex gap-2 items-center">
<i class="bi bi-person text-accent"></i>
für 3-4 reale Spieler
</div>
<div class="flex gap-2 items-center">
<i class="bi bi-robot text-accent"></i>
ggf. 1 virtueller Mitspieler
</div>
<div class="flex gap-2 mt-6 items-center">
<i class="bi bi-exclamation-circle text-accent"></i>
NFC Reader erforderlich
</div>
</div>
<a class="btn btn-primary">Spielen</a>
</div>
<div class="flex flex-col gap-6 bg-base-200 rounded-box p-8">
<div class="flex flex-col gap-4 text-center">
<h1 class="text-5xl font-bold">Online</h1>
<span class="text-sm">spiele gegen Spieler aus der ganzen Welt</span>
</div>
<!-- Features -->
<div class="flex flex-col">
<div class="flex gap-2 items-center">
<i class="bi bi-person text-accent"></i>
1 Spieler
</div>
<div class="flex gap-2 items-center">
<i class="bi bi-pc-display text-accent"></i>
2-3 online Mitspieler
</div>
<div class="flex gap-2 items-center">
<i class="bi bi-robot text-accent"></i>
ggf. 1-2 virtuelle Mitspieler
</div>
<div v-if="isPingInProgress" class="flex gap-2 mt-6 items-center">
<i class="bi bi bi-arrow-repeat text-info animate-spin"></i>
Serververbindung wird hergestellt ...
</div>
<div v-else-if="!isConnected" class="flex gap-2 mt-6 items-center">
<i class="bi bi-x-lg text-error"></i>
Serververbindung fehlgeschlagen ({{ secondsRemaining }} Sekunden)
</div>
<div v-else class="flex gap-2 mt-6 items-center">
<i class="bi bi-check2 text-success"></i>
Verbindung zum Server hergestellt
</div>
</div>
<button :disabled="!isConnected" class="btn btn-primary" @click="openOnlineGameList()">Spielen</button>
<div class="divider"></div>
<input
v-model="serverAddress"
type="text" placeholder="Serveradresse" class="input input-bordered w-full max-w-xs"/>
</div>
<div class="flex flex-col gap-6 bg-base-200 rounded-box p-8">
<div class="flex flex-col gap-4 text-center">
<h1 class="text-5xl font-bold">Übung</h1>
<span class="text-sm">spiele zum Üben gegen NPCs</span>
</div>
<!-- Features -->
<div class="flex flex-col">
<div class="flex gap-2 items-center">
<i class="bi bi-person text-accent"></i>
1 Spieler
</div>
<div class="flex gap-2 items-center">
<i class="bi bi-robot text-accent"></i>
3 virtuelle Mitspieler
</div>
</div>
<a class="btn btn-primary">Spielen</a>
</div>
</div>
</div>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,14 @@
<script setup lang="ts">
</script>
<template>
<router-link to="/dedicatedgame">
<button class="btn btn-primary">Zum Spiel</button>
</router-link>
</template>
<style scoped lang="scss">
</style>

View File

@@ -0,0 +1,76 @@
import {MessageType} from "../BackendMessage.ts";
interface JsonMessage {
origin: string;
message: any; // Adjust 'any' type as per your expected message structure
}
export class BackendConnection {
private readonly webSocket: WebSocket;
private messageListeners: ((message: string) => void)[] = [];
private backendUri: string;
constructor(backendUri: string) {
this.backendUri = backendUri;
this.webSocket = new WebSocket(backendUri);
// Registering event listener for message reception
this.webSocket.addEventListener('message', this.handleMessage.bind(this));
// Handle connection closed
this.webSocket.addEventListener("close", (event) => {
console.log("WebSocket connection closed:", event);
});
// Handle errors
this.webSocket.addEventListener("error", (event) => {
console.error("WebSocket error:", event);
});
}
public sendMessage(messageType: MessageType, message?: any): void {
let jsonMessage;
if (message === undefined) {
jsonMessage = {
origin: "FRONTEND",
message: {
message_type: messageType,
}
};
} else {
jsonMessage = {
origin: "FRONTEND",
message: {
message_type: messageType,
content: message
}
};
}
console.log("Sending message:", jsonMessage);
this.webSocket.send(JSON.stringify(jsonMessage));
}
public getWebSocket(): WebSocket {
return this.webSocket;
}
public addMessageListener(listener: (message: string) => void): void {
this.messageListeners.push(listener);
}
public removeMessageListener(listener: (message: string) => void): void {
this.messageListeners = this.messageListeners.filter(l => l !== listener);
}
private handleMessage(event: MessageEvent): void {
const message = event.data as string;
// Notify all registered message listeners
this.messageListeners.forEach(listener => {
listener(message);
});
}
}

View File

@@ -0,0 +1,15 @@
import {ServiceContainer} from "ioc-service-container";
import {BackendConnection} from "./BackendConnection.ts";
type IoCTypes = {
BackendConnection: BackendConnection,
};
declare module 'ioc-service-container' {
export function scg<T extends keyof IoCTypes, U extends IoCTypes[T]>(id: T): U;
}
export function setupService(backendUri: string) {
const backendConnection = new BackendConnection(backendUri);
ServiceContainer.set('BackendConnection', () => backendConnection);
}

View File

@@ -1,11 +1,11 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
content: [
"./index.html",
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [require("daisyui")],
}