Compare commits
10 Commits
v0.0.1-alp
...
v0.0.1-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea5df95228 | ||
|
|
539e29dc56 | ||
|
|
2e5a42b6d3 | ||
|
|
b8b89ee696 | ||
|
|
3689a694d4 | ||
|
|
6ff26f3407 | ||
|
|
2cd8359518 | ||
|
|
a0a1cfaa4a | ||
|
|
cab2d36f48 | ||
|
|
6259d0bef3 |
17
.github/workflows/buildAndTest.yml
vendored
@@ -12,6 +12,23 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- 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
|
- name: Set up JDK 21
|
||||||
uses: actions/setup-java@v4
|
uses: actions/setup-java@v4
|
||||||
|
|||||||
10
.github/workflows/relaseNewVersion.yml
vendored
@@ -26,10 +26,6 @@ jobs:
|
|||||||
run: npm install
|
run: npm install
|
||||||
working-directory: Frontend
|
working-directory: Frontend
|
||||||
|
|
||||||
- name: Create .env file
|
|
||||||
run: echo "VITE_APP_WEBSOCKET_IP=localhost" > .env
|
|
||||||
working-directory: Frontend
|
|
||||||
|
|
||||||
- name: Build Frontend
|
- name: Build Frontend
|
||||||
run: npm run build
|
run: npm run build
|
||||||
working-directory: Frontend
|
working-directory: Frontend
|
||||||
@@ -52,12 +48,14 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive dist folder
|
- 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 && zip -r dist.zip schafkopf-bot.jar
|
||||||
run: mv Backend/schafkopf-client/target/schafkopf-client-build-jar-with-dependencies.jar schafkopf-bot-client_${{ github.ref_name }}.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
|
- name: Release
|
||||||
uses: softprops/action-gh-release@v2
|
uses: softprops/action-gh-release@v2
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: schafkopf-bot-client_${{ github.ref_name }}.jar
|
files: |
|
||||||
|
schafkopf-bot-client_${{ github.ref_name }}.jar
|
||||||
|
schafkopf-bot-server_${{ github.ref_name }}.jar
|
||||||
token: ${{ secrets.PAT }}
|
token: ${{ secrets.PAT }}
|
||||||
|
|
||||||
|
|||||||
41
Arduino/nfcReader/nfcReader.ino
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
<configuration>
|
<configuration>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>org.schafkopf.BackendServer</mainClass>
|
<mainClass>org.schafkopf.DedicatedServer</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -135,5 +135,11 @@
|
|||||||
<groupId>io.github.cdimascio</groupId>
|
<groupId>io.github.cdimascio</groupId>
|
||||||
<version>3.0.0</version>
|
<version>3.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Jackson for JSON serialization/deserialization -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.json</groupId>
|
||||||
|
<artifactId>json</artifactId>
|
||||||
|
<version>20240303</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
</project>
|
</project>
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
</descriptorRefs>
|
</descriptorRefs>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>org.schafkopf.BackendServer</mainClass>
|
<mainClass>org.schafkopf.SchafkopfClient</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -1,57 +1,44 @@
|
|||||||
package org.schafkopf;
|
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 jakarta.servlet.DispatcherType;
|
import jakarta.servlet.DispatcherType;
|
||||||
import java.awt.Desktop;
|
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.URI;
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.server.ServerConnector;
|
import org.eclipse.jetty.server.ServerConnector;
|
||||||
|
import org.eclipse.jetty.servlet.DefaultServlet;
|
||||||
import org.eclipse.jetty.servlet.FilterHolder;
|
import org.eclipse.jetty.servlet.FilterHolder;
|
||||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
import org.eclipse.jetty.servlets.CrossOriginFilter;
|
||||||
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer;
|
||||||
import org.schafkopf.cardreader.UsbCardReader;
|
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main Class that represents the Backend Server.
|
||||||
|
*/
|
||||||
|
public class BackendServer implements MessageSender {
|
||||||
|
|
||||||
/** Main Class that represents the Backend Server. */
|
|
||||||
public class BackendServer {
|
|
||||||
private final Server server;
|
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 List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
|
private final List<FrontendEndpoint> frontendEndpoints = new ArrayList<>();
|
||||||
|
|
||||||
/** 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();
|
server = new Server();
|
||||||
InetSocketAddress address = new InetSocketAddress("localhost", 8080);
|
|
||||||
connector = new ServerConnector(server);
|
ServerConnector connector = new ServerConnector(server);
|
||||||
connector.setHost(address.getHostName());
|
connector.setHost(hostName);
|
||||||
connector.setPort(address.getPort());
|
connector.setPort(port);
|
||||||
server.addConnector(connector);
|
server.addConnector(connector);
|
||||||
|
|
||||||
schafkopfGame = new Schafkopf(this);
|
|
||||||
|
|
||||||
new UsbCardReader(this);
|
|
||||||
|
|
||||||
// Setup the basic application "context" for this application at "/"
|
// Setup the basic application "context" for this application at "/"
|
||||||
// This is also known as the handler tree (in jetty speak)
|
// This is also known as the handler tree (in jetty speak)
|
||||||
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
|
||||||
@@ -65,11 +52,9 @@ public class BackendServer {
|
|||||||
if (webContentUrl == null) {
|
if (webContentUrl == null) {
|
||||||
throw new RuntimeException("Unable to find 'web-content' directory");
|
throw new RuntimeException("Unable to find 'web-content' directory");
|
||||||
}
|
}
|
||||||
|
|
||||||
String webContentPath = webContentUrl.toExternalForm();
|
String webContentPath = webContentUrl.toExternalForm();
|
||||||
context.setResourceBase(webContentPath);
|
context.setResourceBase(webContentPath);
|
||||||
|
context.addServlet(new ServletHolder("frontend", DefaultServlet.class), "/");
|
||||||
System.out.println("Web Content Path: " + webContentPath);
|
|
||||||
|
|
||||||
// Configure specific websocket behavior
|
// Configure specific websocket behavior
|
||||||
JettyWebSocketServletContainerInitializer.configure(
|
JettyWebSocketServletContainerInitializer.configure(
|
||||||
@@ -79,77 +64,25 @@ public class BackendServer {
|
|||||||
wsContainer.setMaxTextMessageSize(65535);
|
wsContainer.setMaxTextMessageSize(65535);
|
||||||
wsContainer.setIdleTimeout(Duration.ofDays(300000));
|
wsContainer.setIdleTimeout(Duration.ofDays(300000));
|
||||||
// Add websockets
|
// Add websockets
|
||||||
wsContainer.addMapping("/schafkopf-events/*", new FrontendEndpointCreator(this));
|
wsContainer.addMapping("/schafkopf-events/*",
|
||||||
|
new FrontendEndpointCreator(this, messageListener));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Integrate simple HTTP server
|
if (openFrontend) {
|
||||||
startHttpServer();
|
URI uri = new URI("http://" + hostName + ":" + port); // Replace with your target URL
|
||||||
URI uri = new URI("http://localhost:8081"); // Replace with your target URL
|
Desktop.getDesktop().browse(uri);
|
||||||
Desktop.getDesktop().browse(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Start the server in a separate thread
|
||||||
|
Thread serverThread = new Thread(() -> {
|
||||||
try {
|
try {
|
||||||
InputStream fileStream =
|
server.start();
|
||||||
getClass().getClassLoader().getResourceAsStream("web-content" + path);
|
server.join(); // Wait for server to finish execution
|
||||||
if (fileStream != null) {
|
} catch (Exception e) {
|
||||||
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();
|
e.printStackTrace();
|
||||||
t.sendResponseHeaders(500, -1);
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
serverThread.start();
|
||||||
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) {
|
private void configureCors(ServletContextHandler context) {
|
||||||
@@ -166,10 +99,6 @@ public class BackendServer {
|
|||||||
context.addFilter(cors, "*", types);
|
context.addFilter(cors, "*", types);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPort(int port) {
|
|
||||||
connector.setPort(port);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void start() throws Exception {
|
private void start() throws Exception {
|
||||||
server.start();
|
server.start();
|
||||||
}
|
}
|
||||||
@@ -191,47 +120,20 @@ public class BackendServer {
|
|||||||
*
|
*
|
||||||
* @param message Message to send (String).
|
* @param message Message to send (String).
|
||||||
*/
|
*/
|
||||||
public void sendMessageToAllFrontendEndpoints(String message) {
|
private void sendMessageToAllFrontendEndpoints(String message) {
|
||||||
for (FrontendEndpoint endpoint : frontendEndpoints) {
|
for (FrontendEndpoint endpoint : frontendEndpoints) {
|
||||||
endpoint.sendMessage(message);
|
endpoint.sendMessage(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Sends Message to all Frontend Instances.
|
public void sendMessage(SchafkopfBaseMessage message) {
|
||||||
*
|
sendMessageToAllFrontendEndpoints(
|
||||||
* @param message Message to send (JsonObject).
|
message.getBaseMessage().toString());
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void sendMessageTest(String message) {
|
||||||
* checks uid of scanned card and do nothing if Server is not in reading mode.
|
sendMessageToAllFrontendEndpoints(message);
|
||||||
*
|
|
||||||
* @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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,16 +5,27 @@ import java.util.concurrent.CountDownLatch;
|
|||||||
import org.eclipse.jetty.websocket.api.Session;
|
import org.eclipse.jetty.websocket.api.Session;
|
||||||
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
|
||||||
|
|
||||||
/** Class that represents one Frontend Connection. */
|
/**
|
||||||
|
* Class that represents one Frontend Connection.
|
||||||
|
*/
|
||||||
public class FrontendEndpoint extends WebSocketAdapter {
|
public class FrontendEndpoint extends WebSocketAdapter {
|
||||||
|
|
||||||
private final CountDownLatch closureLatch = new CountDownLatch(1);
|
private final CountDownLatch closureLatch = new CountDownLatch(1);
|
||||||
private BackendServer backendServer;
|
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;
|
this.backendServer = backendServer;
|
||||||
System.out.println("new FrontendEndpoint");
|
System.out.println("new FrontendEndpoint");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that represents one Frontend Connection.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public void onWebSocketConnect(Session session) {
|
public void onWebSocketConnect(Session session) {
|
||||||
super.onWebSocketConnect(session);
|
super.onWebSocketConnect(session);
|
||||||
@@ -27,26 +38,8 @@ public class FrontendEndpoint extends WebSocketAdapter {
|
|||||||
@Override
|
@Override
|
||||||
public void onWebSocketText(String message) {
|
public void onWebSocketText(String message) {
|
||||||
super.onWebSocketText(message);
|
super.onWebSocketText(message);
|
||||||
System.out.println("Received TEXT message:" + message);
|
if (messageListener != null) {
|
||||||
|
messageListener.receiveMessage(message); // Notify the listener
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +59,9 @@ public class FrontendEndpoint extends WebSocketAdapter {
|
|||||||
cause.printStackTrace(System.err);
|
cause.printStackTrace(System.err);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** send a Message to the connected FrontEnd. */
|
/**
|
||||||
|
* send a Message to the connected FrontEnd.
|
||||||
|
*/
|
||||||
public void sendMessage(String message) {
|
public void sendMessage(String message) {
|
||||||
try {
|
try {
|
||||||
getRemote().sendString(message);
|
getRemote().sendString(message);
|
||||||
|
|||||||
@@ -9,8 +9,10 @@ import org.eclipse.jetty.websocket.server.JettyWebSocketCreator;
|
|||||||
*/
|
*/
|
||||||
public class FrontendEndpointCreator implements JettyWebSocketCreator {
|
public class FrontendEndpointCreator implements JettyWebSocketCreator {
|
||||||
private BackendServer backendServer;
|
private BackendServer backendServer;
|
||||||
|
private final MessageListener messageListener;
|
||||||
|
|
||||||
public FrontendEndpointCreator(BackendServer backendServer) {
|
public FrontendEndpointCreator(BackendServer backendServer, MessageListener messageListener) {
|
||||||
|
this.messageListener = messageListener;
|
||||||
this.backendServer = backendServer;
|
this.backendServer = backendServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -18,6 +20,6 @@ public class FrontendEndpointCreator implements JettyWebSocketCreator {
|
|||||||
public Object createWebSocket(
|
public Object createWebSocket(
|
||||||
JettyServerUpgradeRequest jettyServerUpgradeRequest,
|
JettyServerUpgradeRequest jettyServerUpgradeRequest,
|
||||||
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
|
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
|
||||||
return new FrontendEndpoint(this.backendServer);
|
return new FrontendEndpoint(this.backendServer, messageListener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,13 +1,42 @@
|
|||||||
package org.schafkopf.cardreader;
|
package org.schafkopf.cardreader;
|
||||||
|
|
||||||
import org.schafkopf.BackendServer;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
||||||
/** Class that represents one Card Reader. */
|
/** Class that represents one Card Reader. */
|
||||||
public abstract class CardReader {
|
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) {
|
public CardReader() {
|
||||||
this.server = server;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.schafkopf.cardreader;
|
package org.schafkopf.cardreader;
|
||||||
|
|
||||||
import com.fazecast.jSerialComm.SerialPort;
|
import com.fazecast.jSerialComm.SerialPort;
|
||||||
|
import com.sun.tools.jconsole.JConsoleContext;
|
||||||
import io.github.cdimascio.dotenv.Dotenv;
|
import io.github.cdimascio.dotenv.Dotenv;
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
import org.schafkopf.BackendServer;
|
import org.schafkopf.BackendServer;
|
||||||
@@ -10,16 +11,13 @@ public class UsbCardReader extends CardReader {
|
|||||||
|
|
||||||
private volatile boolean isRunning = true;
|
private volatile boolean isRunning = true;
|
||||||
Dotenv dotenv = Dotenv.configure().directory("./").load();
|
Dotenv dotenv = Dotenv.configure().directory("./").load();
|
||||||
private final String comPort = dotenv.get("COM_PORT");
|
private String comPort = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an Instance of the KartenLeser.
|
* Creates an Instance of the KartenLeser.
|
||||||
*
|
*
|
||||||
* @param server Backend Server to call methods on.
|
|
||||||
*/
|
*/
|
||||||
public UsbCardReader(BackendServer server) {
|
public UsbCardReader() {
|
||||||
super(server);
|
|
||||||
|
|
||||||
new Thread(this::run).start();
|
new Thread(this::run).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,29 +25,46 @@ public class UsbCardReader extends CardReader {
|
|||||||
isRunning = false;
|
isRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** run the reader. */
|
/** Run the reader. */
|
||||||
public void run() {
|
public void run() {
|
||||||
SerialPort[] ports = SerialPort.getCommPorts();
|
// SerialPort[] ports = SerialPort.getCommPorts();
|
||||||
SerialPort selectedPort = null;
|
//
|
||||||
|
// 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();
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
for (SerialPort port : ports) {
|
comPort = dotenv.get("COM_PORT");
|
||||||
if (port.getSystemPortName().equals(this.comPort)) {
|
|
||||||
selectedPort = port;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedPort == null) {
|
if (comPort == null) {
|
||||||
System.out.println(this.comPort + " not found");
|
System.out.println("Adafruit PN532 NFC device not found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ports.length == 0) {
|
SerialPort serialPort = SerialPort.getCommPort(comPort);
|
||||||
System.out.println("No serial ports found");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialPort serialPort = selectedPort; // You may need to adjust this based on your setup
|
|
||||||
serialPort.setBaudRate(115200);
|
serialPort.setBaudRate(115200);
|
||||||
|
|
||||||
if (serialPort.openPort()) {
|
if (serialPort.openPort()) {
|
||||||
@@ -60,9 +75,10 @@ public class UsbCardReader extends CardReader {
|
|||||||
if (serialPort.bytesAvailable() > 0) {
|
if (serialPort.bytesAvailable() > 0) {
|
||||||
byte[] buffer = new byte[serialPort.bytesAvailable()];
|
byte[] buffer = new byte[serialPort.bytesAvailable()];
|
||||||
int bytesRead = serialPort.readBytes(buffer, buffer.length);
|
int bytesRead = serialPort.readBytes(buffer, buffer.length);
|
||||||
|
|
||||||
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
|
String data = new String(buffer, 0, bytesRead, "UTF-8").trim();
|
||||||
server.nfcGelesen(data);
|
|
||||||
|
// Process the received data
|
||||||
|
this.nfcGelesen(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: Add a delay to avoid consuming too much CPU
|
// Optional: Add a delay to avoid consuming too much CPU
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.schafkopf.player;
|
package org.schafkopf.player;
|
||||||
|
|
||||||
import org.schafkopf.Schafkopf;
|
import org.schafkopf.MessageSender;
|
||||||
|
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
|
||||||
|
import org.schafkopf.cardreader.CardReader;
|
||||||
import org.schafkopf.karte.Karte;
|
import org.schafkopf.karte.Karte;
|
||||||
import org.schafkopf.karte.KartenListe;
|
import org.schafkopf.karte.KartenListe;
|
||||||
|
import org.schafkopf.karte.KartenUtil;
|
||||||
import org.schafkopf.spielcontroller.SpielController;
|
import org.schafkopf.spielcontroller.SpielController;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,14 +13,48 @@ import org.schafkopf.spielcontroller.SpielController;
|
|||||||
*/
|
*/
|
||||||
public class LocalPlayer extends Player {
|
public class LocalPlayer extends Player {
|
||||||
|
|
||||||
private final Schafkopf schafkopf;
|
private final CardReader cardReader;
|
||||||
|
|
||||||
public LocalPlayer(Schafkopf schafkopf) {
|
public LocalPlayer(CardReader cardReader, MessageSender messageSender) {
|
||||||
this.schafkopf = schafkopf;
|
super("Local Player", messageSender);
|
||||||
|
this.cardReader = cardReader;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten) {
|
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten) {
|
||||||
return schafkopf.wartetAufKarte();
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 815 KiB |
|
Before Width: | Height: | Size: 859 KiB |
|
Before Width: | Height: | Size: 971 KiB |
|
Before Width: | Height: | Size: 954 KiB |
|
Before Width: | Height: | Size: 797 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 603 KiB |
|
Before Width: | Height: | Size: 646 KiB |
|
Before Width: | Height: | Size: 730 KiB |
|
Before Width: | Height: | Size: 768 KiB |
|
Before Width: | Height: | Size: 905 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1008 KiB |
|
Before Width: | Height: | Size: 870 KiB |
|
Before Width: | Height: | Size: 412 KiB |
|
Before Width: | Height: | Size: 470 KiB |
|
Before Width: | Height: | Size: 498 KiB |
|
Before Width: | Height: | Size: 626 KiB |
|
Before Width: | Height: | Size: 957 KiB |
|
Before Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 1010 KiB |
|
Before Width: | Height: | Size: 959 KiB |
|
Before Width: | Height: | Size: 686 KiB |
|
Before Width: | Height: | Size: 600 KiB |
|
Before Width: | Height: | Size: 704 KiB |
|
Before Width: | Height: | Size: 741 KiB |
|
Before Width: | Height: | Size: 881 KiB |
|
Before Width: | Height: | Size: 1009 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 1.0 MiB |
|
Before Width: | Height: | Size: 1015 KiB |
|
Before Width: | Height: | Size: 958 KiB |
@@ -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>
|
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
</descriptorRefs>
|
</descriptorRefs>
|
||||||
<archive>
|
<archive>
|
||||||
<manifest>
|
<manifest>
|
||||||
<mainClass>org.schafkopf.Main</mainClass>
|
<mainClass>org.schafkopf.DedicatedServer</mainClass>
|
||||||
</manifest>
|
</manifest>
|
||||||
</archive>
|
</archive>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package org.schafkopf;
|
|
||||||
|
|
||||||
import org.schafkopf.karte.Karte;
|
|
||||||
import org.schafkopf.karte.KartenListe;
|
|
||||||
import org.schafkopf.karte.KartenUtil;
|
|
||||||
|
|
||||||
/** Creates an Instance of the Backend Server. */
|
|
||||||
public class Main {
|
|
||||||
/** Creates an Instance of the Backend Server. */
|
|
||||||
public static void main(String[] args) {
|
|
||||||
|
|
||||||
System.out.println("Hello and welcome!");
|
|
||||||
KartenListe testHand = KartenUtil.zieheZufallsHand(8);
|
|
||||||
for (Karte karte : testHand.getKartenListe()) {
|
|
||||||
System.out.println(karte.getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,14 +5,18 @@ import com.google.gson.JsonObject;
|
|||||||
import org.schafkopf.karte.Karte;
|
import org.schafkopf.karte.Karte;
|
||||||
import org.schafkopf.karte.KartenFarbe;
|
import org.schafkopf.karte.KartenFarbe;
|
||||||
|
|
||||||
/** GameState. */
|
/**
|
||||||
|
* GameState.
|
||||||
|
*/
|
||||||
public class GameState {
|
public class GameState {
|
||||||
|
|
||||||
public GamePhase getGamePhase() {
|
public GamePhase getGamePhase() {
|
||||||
return this.gamePhase;
|
return this.gamePhase;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GamePhase. */
|
/**
|
||||||
|
* GamePhase.
|
||||||
|
*/
|
||||||
public enum GamePhase {
|
public enum GamePhase {
|
||||||
CHOOSE_GAME("Spiel muss gewählt werden"),
|
CHOOSE_GAME("Spiel muss gewählt werden"),
|
||||||
|
|
||||||
@@ -37,7 +41,7 @@ public class GameState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private GamePhase gamePhase;
|
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 Karte card;
|
||||||
private KartenFarbe color;
|
private KartenFarbe color;
|
||||||
private boolean trumpf;
|
private boolean trumpf;
|
||||||
@@ -48,13 +52,15 @@ public class GameState {
|
|||||||
this.gamePhase = phase;
|
this.gamePhase = phase;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GameState(GamePhase phase, Integer player) {
|
public GameState(GamePhase phase, String player) {
|
||||||
this.gamePhase = phase;
|
this.gamePhase = phase;
|
||||||
this.currentPlayer = player;
|
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.gamePhase = phase;
|
||||||
this.currentPlayer = player;
|
this.currentPlayer = player;
|
||||||
this.card = card;
|
this.card = card;
|
||||||
@@ -62,19 +68,34 @@ public class GameState {
|
|||||||
this.trumpf = trumpf;
|
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.gamePhase = phase;
|
||||||
this.currentPlayer = player;
|
this.currentPlayer = player;
|
||||||
this.card = card;
|
this.card = card;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** GameState. */
|
/**
|
||||||
|
* GameState.
|
||||||
|
*/
|
||||||
public JsonObject getJson() {
|
public JsonObject getJson() {
|
||||||
Gson gson = new Gson();
|
Gson gson = new Gson();
|
||||||
JsonObject jsonObject = new JsonObject();
|
JsonObject gameStateObject = new JsonObject();
|
||||||
jsonObject.add("gamestate", gson.toJsonTree(this));
|
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package org.schafkopf;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that represents one Frontend Connection.
|
||||||
|
*/
|
||||||
|
public interface MessageListener {
|
||||||
|
|
||||||
|
void receiveMessage(String message);
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,9 @@ import org.schafkopf.spielcontroller.SpielController;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/** The main class that controlls the game flow. */
|
/**
|
||||||
|
* The main class that controlls the game flow.
|
||||||
|
*/
|
||||||
public class Spielablauf {
|
public class Spielablauf {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(Spielablauf.class);
|
private static final Logger logger = LoggerFactory.getLogger(Spielablauf.class);
|
||||||
@@ -22,7 +24,7 @@ public class Spielablauf {
|
|||||||
|
|
||||||
private final Schafkopf schafkopf;
|
private final Schafkopf schafkopf;
|
||||||
|
|
||||||
Spielablauf(Schafkopf schafkopf, SpielController spiel) {
|
Spielablauf(Schafkopf schafkopf, SpielController spiel) throws InterruptedException {
|
||||||
this.schafkopf = schafkopf;
|
this.schafkopf = schafkopf;
|
||||||
this.spiel = spiel;
|
this.spiel = spiel;
|
||||||
this.players = schafkopf.getPlayer();
|
this.players = schafkopf.getPlayer();
|
||||||
@@ -30,7 +32,7 @@ public class Spielablauf {
|
|||||||
playRound();
|
playRound();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void playRound() {
|
private void playRound() throws InterruptedException {
|
||||||
int startingPlayer = 0;
|
int startingPlayer = 0;
|
||||||
|
|
||||||
logger.info("Starte Stiche");
|
logger.info("Starte Stiche");
|
||||||
@@ -41,14 +43,15 @@ public class Spielablauf {
|
|||||||
schafkopf.stopGame();
|
schafkopf.stopGame();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int playTrick(int startingPlayer) {
|
private int playTrick(int startingPlayer) throws InterruptedException {
|
||||||
schafkopf.setAndSendGameState(new GameState(GamePhase.TRICK_START));
|
schafkopf.setAndSendGameState(new GameState(GamePhase.TRICK_START));
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) {
|
for (int i = 0; i < 4; i++) {
|
||||||
int currentPlayer = (i + startingPlayer) % 4;
|
int currentPlayer = (i + startingPlayer) % 4;
|
||||||
|
|
||||||
logger.info("Spieler ist dran: {}", currentPlayer);
|
logger.info("Spieler ist dran: {}", players[currentPlayer].getName());
|
||||||
schafkopf.setAndSendGameState(new GameState(GamePhase.WAIT_FOR_CARD, currentPlayer));
|
schafkopf.setAndSendGameState(
|
||||||
|
new GameState(GamePhase.WAIT_FOR_CARD, players[currentPlayer].getName()));
|
||||||
|
|
||||||
Karte playedCard = players[currentPlayer].play(spiel, tischKarten, gespielteKarten);
|
Karte playedCard = players[currentPlayer].play(spiel, tischKarten, gespielteKarten);
|
||||||
tischKarten.addKarten(playedCard);
|
tischKarten.addKarten(playedCard);
|
||||||
@@ -56,7 +59,7 @@ public class Spielablauf {
|
|||||||
schafkopf.setAndSendGameState(
|
schafkopf.setAndSendGameState(
|
||||||
new GameState(
|
new GameState(
|
||||||
GamePhase.PLAYER_CARD,
|
GamePhase.PLAYER_CARD,
|
||||||
currentPlayer,
|
players[currentPlayer].getName(),
|
||||||
playedCard,
|
playedCard,
|
||||||
tischKarten.getByIndex(0).getFarbe(),
|
tischKarten.getByIndex(0).getFarbe(),
|
||||||
spiel.isTrumpf(tischKarten.getByIndex(0))));
|
spiel.isTrumpf(tischKarten.getByIndex(0))));
|
||||||
@@ -70,7 +73,8 @@ public class Spielablauf {
|
|||||||
|
|
||||||
schafkopf.setAndSendGameState(
|
schafkopf.setAndSendGameState(
|
||||||
new GameState(
|
new GameState(
|
||||||
GamePhase.PLAYER_TRICK, winningPlayerIndex, tischKarten.getByIndex(stichSpieler)));
|
GamePhase.PLAYER_TRICK, players[winningPlayerIndex].getName(),
|
||||||
|
tischKarten.getByIndex(stichSpieler)));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Thread.sleep(3000);
|
Thread.sleep(3000);
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
package org.schafkopf.karte;
|
package org.schafkopf.karte;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.JsonObject;
|
import com.google.gson.JsonElement;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A Class that represents a list of Cards.
|
* A Class that represents a list of Cards.
|
||||||
*/
|
*/
|
||||||
public class KartenListe {
|
public class KartenListe {
|
||||||
|
|
||||||
private List<Karte> kartenListe;
|
private List<Karte> kartenListe;
|
||||||
|
|
||||||
public KartenListe() {
|
public KartenListe() {
|
||||||
@@ -23,6 +25,23 @@ public class KartenListe {
|
|||||||
return this.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.
|
* A Class that represents a list of Cards.
|
||||||
*/
|
*/
|
||||||
@@ -158,15 +177,31 @@ public class KartenListe {
|
|||||||
return result;
|
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.
|
* A Class that represents a list of Cards.
|
||||||
*/
|
*/
|
||||||
public JsonObject getJson() {
|
public JsonElement getJson() {
|
||||||
Gson gson = new Gson();
|
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() {
|
public boolean isEmpty() {
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
package org.schafkopf.player;
|
package org.schafkopf.player;
|
||||||
|
|
||||||
|
import org.schafkopf.MessageSender;
|
||||||
|
import org.schafkopf.SchafkopfMessage.SchafkopfBaseMessage;
|
||||||
import org.schafkopf.karte.Karte;
|
import org.schafkopf.karte.Karte;
|
||||||
import org.schafkopf.karte.KartenListe;
|
import org.schafkopf.karte.KartenListe;
|
||||||
import org.schafkopf.karte.KartenUtil;
|
import org.schafkopf.karte.KartenUtil;
|
||||||
import org.schafkopf.spielcontroller.SpielController;
|
import org.schafkopf.spielcontroller.SpielController;
|
||||||
|
|
||||||
/** Player that represents the Bot. */
|
/**
|
||||||
|
* Player that represents the Bot.
|
||||||
|
*/
|
||||||
public class BotPlayer extends Player {
|
public class BotPlayer extends Player {
|
||||||
|
|
||||||
private KartenListe eigeneKarten;
|
private KartenListe eigeneKarten;
|
||||||
private KartenListe unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
|
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
|
@Override
|
||||||
@@ -32,12 +45,25 @@ public class BotPlayer extends Player {
|
|||||||
return card;
|
return card;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set the Cards of the Player. */
|
/**
|
||||||
|
* Set the Cards of the Player.
|
||||||
|
*/
|
||||||
public void setCards(KartenListe cards) {
|
public void setCards(KartenListe cards) {
|
||||||
System.out.println("Eigene Karte setzen");
|
System.out.println("Eigene Karte setzen");
|
||||||
|
cards.sort();
|
||||||
this.eigeneKarten = cards;
|
this.eigeneKarten = cards;
|
||||||
this.unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
|
this.unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
|
||||||
this.unbekannteKarten.removeKarten(eigeneKarten);
|
this.unbekannteKarten.removeKarten(eigeneKarten);
|
||||||
System.out.println("Eigene Karte fertig");
|
System.out.println("Eigene Karte fertig");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(SchafkopfBaseMessage message) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetReady() {
|
||||||
|
// Not needed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
89
Frontend/package-lock.json
generated
@@ -8,12 +8,16 @@
|
|||||||
"name": "frontend",
|
"name": "frontend",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"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": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "^7.7.0",
|
"@typescript-eslint/parser": "^7.7.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
|
"daisyui": "^4.10.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-vue": "^9.25.0",
|
"eslint-plugin-vue": "^9.25.0",
|
||||||
@@ -1078,6 +1082,11 @@
|
|||||||
"@vue/shared": "3.4.23"
|
"@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": {
|
"node_modules/@vue/language-core": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.13",
|
||||||
"resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.13.tgz",
|
"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==",
|
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/brace-expansion": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||||
@@ -1518,6 +1542,16 @@
|
|||||||
"node": ">= 8"
|
"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": {
|
"node_modules/cssesc": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||||
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
|
"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": {
|
"node_modules/de-indent": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
|
||||||
@@ -1958,6 +2020,12 @@
|
|||||||
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/fastq": {
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||||
@@ -2246,6 +2314,11 @@
|
|||||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
@@ -3674,6 +3747,20 @@
|
|||||||
"eslint": ">=6.0.0"
|
"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": {
|
"node_modules/vue-template-compiler": {
|
||||||
"version": "2.7.16",
|
"version": "2.7.16",
|
||||||
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
"resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz",
|
||||||
|
|||||||
@@ -9,12 +9,16 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"devDependencies": {
|
||||||
"@typescript-eslint/parser": "^7.7.0",
|
"@typescript-eslint/parser": "^7.7.0",
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
|
"daisyui": "^4.10.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-vue": "^9.25.0",
|
"eslint-plugin-vue": "^9.25.0",
|
||||||
|
|||||||
@@ -1,228 +1,10 @@
|
|||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {onMounted, ref} from 'vue';
|
import MessageBoard from "./components/MessageBoard.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 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>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<router-view/>
|
||||||
<div v-for="message in messageFromServer" :key="message">{{ message }}</div>
|
<MessageBoard></MessageBoard>
|
||||||
|
|
||||||
<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>
|
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
$card-height: 24rem;
|
@import "bootstrap-icons/font/bootstrap-icons.css";
|
||||||
|
|
||||||
.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>
|
</style>
|
||||||
|
|||||||
@@ -71,29 +71,107 @@ export enum GamePhase {
|
|||||||
PLAYER_TRICK = "PLAYER_TRICK"
|
PLAYER_TRICK = "PLAYER_TRICK"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum MessageType {
|
||||||
|
PLAYER_CARD = "PLAYER_CARD",
|
||||||
|
START_DEDICATED_GAME = "START_DEDICATED_GAME",
|
||||||
|
JOIN_ONLINE_GAME = "JOIN_ONLINE_GAME",
|
||||||
|
LEAVE_ONLINE_GAME = "LEAVE_ONLINE_GAME",
|
||||||
|
REQUEST_SERVER_CONNECTION = "REQUEST_SERVER_CONNECTION",
|
||||||
|
CREATE_ONLINE_GAME = "CREATE_ONLINE_GAME",
|
||||||
|
LIST_ONLINE_GAMES = "LIST_ONLINE_GAMES",
|
||||||
|
UNKNOWN_ERROR = "UNKNOWN_ERROR",
|
||||||
|
INFO_MESSAGE = "INFO_MESSAGE",
|
||||||
|
GET_ONLINE_GAME = "GET_ONLINE_GAME",
|
||||||
|
SET_STATUS_READY = "SET_STATUS_READY",
|
||||||
|
GAME_STATE = "GAME_STATE",
|
||||||
|
ONLINE_PLAYER_HAND = "ONLINE_PLAYER_HAND",
|
||||||
|
SERVER_CONNECTION_SUCCESSFUL = "SERVER_CONNECTION_SUCCESSFUL",
|
||||||
|
SET_PLAYER_NAME = "SET_PLAYER_NAME",
|
||||||
|
GAME_START_READY = "GAME_START_READY",
|
||||||
|
}
|
||||||
|
|
||||||
// Define the interface for an array of cards
|
// Define the interface for an array of cards
|
||||||
export interface CardArray {
|
export interface CardArrayMessage {
|
||||||
cards: Card[];
|
message_type: MessageType.ONLINE_PLAYER_HAND;
|
||||||
|
content: { cards: Card[] };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CardObject {
|
export interface CardMessage {
|
||||||
card: Card;
|
message_type: MessageType.PLAYER_CARD;
|
||||||
|
content: { card: Card };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Define the interface for the game state
|
// Define the interface for the game state
|
||||||
export interface GameState {
|
export interface GameState {
|
||||||
gamePhase: GamePhase;
|
gamePhase: GamePhase;
|
||||||
currentPlayer?: number;
|
currentPlayer?: string;
|
||||||
card?: Card;
|
card?: Card;
|
||||||
color?: KartenFarbe;
|
color?: KartenFarbe;
|
||||||
trumpf?: boolean;
|
trumpf?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GameSession {
|
||||||
|
serverName: string;
|
||||||
|
playerCount: number;
|
||||||
|
players: OnlinePlayer[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface GameStateJson {
|
export interface OnlinePlayer {
|
||||||
gamestate: GameState
|
playerName: string;
|
||||||
|
isReady: boolean;
|
||||||
|
isBot?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameStateMessage {
|
||||||
|
message_type: MessageType.GAME_STATE;
|
||||||
|
content: GameState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameListMessage {
|
||||||
|
message_type: MessageType.LIST_ONLINE_GAMES;
|
||||||
|
content: { games: GameSession[] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GameInfoMessage {
|
||||||
|
message_type: MessageType.GET_ONLINE_GAME;
|
||||||
|
content: { game: GameSession };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface JoinGameMessage {
|
||||||
|
message_type: MessageType.JOIN_ONLINE_GAME;
|
||||||
|
content: { serverName: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ErrorMessage {
|
||||||
|
message_type: MessageType.UNKNOWN_ERROR;
|
||||||
|
content: { error: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InfoMessage {
|
||||||
|
message_type: MessageType.INFO_MESSAGE;
|
||||||
|
content: { message: string };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EmptyMessage {
|
||||||
|
message_type: MessageType.SERVER_CONNECTION_SUCCESSFUL | MessageType.GAME_START_READY | MessageType.REQUEST_SERVER_CONNECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetPlayerNameMessage {
|
||||||
|
message_type: MessageType.SET_PLAYER_NAME;
|
||||||
|
content: { playerName: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Define a union type for all possible message types
|
// Define a union type for all possible message types
|
||||||
export type BackendMessage = CardObject | CardArray | GameStateJson;
|
export type BackendMessage =
|
||||||
|
GameListMessage
|
||||||
|
| JoinGameMessage
|
||||||
|
| ErrorMessage
|
||||||
|
| InfoMessage
|
||||||
|
| GameInfoMessage | GameStateMessage | CardArrayMessage | CardMessage | EmptyMessage | SetPlayerNameMessage;
|
||||||
|
|
||||||
|
export enum MessageBoardType {
|
||||||
|
ERROR = "alert-error",
|
||||||
|
WARNING = "alert-warning",
|
||||||
|
INFO = "alert-info",
|
||||||
|
SUCCESS = "alert-success"
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ watch(() => props.card, (newCard) => {
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
:class="{'!scale-105 !z-10 !top-1/2 !left-1/2' : focus}" class="card transition overflow-hidden"
|
: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">
|
<img class="h-full rounded-[1rem] mx-auto" :src="imgSrc" alt="card">
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
51
Frontend/src/components/MessageBoard.vue
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import MessageComponent from "./MessageComponent.vue";
|
||||||
|
import {BackendMessage, MessageBoardType, MessageType} from "../BackendMessage.ts";
|
||||||
|
import {scg} from "ioc-service-container";
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
|
||||||
|
const backendConnection = scg("BackendConnection");
|
||||||
|
const socket = ref<WebSocket | null>();
|
||||||
|
|
||||||
|
const errorMessages = ref<{ message: string, type: MessageBoardType }[]>([]);
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
socket.value = backendConnection.getWebSocket();
|
||||||
|
|
||||||
|
const messageListener = (message: string) => {
|
||||||
|
const message1: BackendMessage = JSON.parse(message);
|
||||||
|
if (message1.message_type === MessageType.UNKNOWN_ERROR && "error" in message1.content) {
|
||||||
|
errorMessages.value.push({message: message1.content.error, type: MessageBoardType.ERROR});
|
||||||
|
|
||||||
|
// Schedule removal for the newly added message
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMessages.value.shift();
|
||||||
|
}, 3000); // Adjust 3000 to your desired delay in milliseconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message1.message_type === MessageType.INFO_MESSAGE && "message" in message1.content) {
|
||||||
|
errorMessages.value.push({message: message1.content.message, type: MessageBoardType.INFO});
|
||||||
|
|
||||||
|
// Schedule removal for the newly added message
|
||||||
|
setTimeout(() => {
|
||||||
|
errorMessages.value.shift();
|
||||||
|
}, 3000); // Adjust 3000 to your desired delay in milliseconds
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backendConnection.addMessageListener(messageListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="fixed bottom-0 left-0 mx-auto px-8 py-2 w-screen flex flex-col gap-1">
|
||||||
|
<MessageComponent v-for="message in errorMessages" :message="message.message" :type="message.type">
|
||||||
|
message="Error! Task failed successfully." :type="MessageBoardType.WARNING">
|
||||||
|
</MessageComponent>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
22
Frontend/src/components/MessageComponent.vue
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
|
||||||
|
|
||||||
|
import {MessageBoardType} from "../BackendMessage.ts";
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
message: string;
|
||||||
|
type: MessageBoardType;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div role="alert" class="alert" :class="type">
|
||||||
|
<i class="bi bi-exclamation"></i>
|
||||||
|
<span>{{ message }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,24 @@
|
|||||||
import { createApp } from 'vue'
|
import {createApp} from 'vue'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
import App from './App.vue'
|
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: '/gamesession', component: () => import('./pages/GameSession.vue'),},
|
||||||
|
{path: '/localgame', component: () => import('./pages/LocalGame.vue'),},
|
||||||
|
{path: '/dedicatedgame', component: () => import('./pages/DedicatedGame.vue'),},
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
setupService("ws://localhost:8080/schafkopf-events/");
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.use(router)
|
||||||
|
app.mount('#app')
|
||||||
|
|||||||
184
Frontend/src/pages/DedicatedGame.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<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, GameSession, GameState, MessageType} from "../BackendMessage";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const backendConnection = scg("BackendConnection");
|
||||||
|
|
||||||
|
|
||||||
|
const messageFromServer = ref<string[]>([]);
|
||||||
|
|
||||||
|
const gameStateText = ref<string>("Spiel startet...");
|
||||||
|
const gameInfoText = ref<string>("");
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const socket = ref<WebSocket | null>();
|
||||||
|
const tableCards = ref<Card[]>([]);
|
||||||
|
const botCards = ref<Card[]>();
|
||||||
|
const trickCard = ref<Card>();
|
||||||
|
const gameState = ref<GameState>();
|
||||||
|
|
||||||
|
|
||||||
|
const gameSession = ref<GameSession>({
|
||||||
|
serverName: "",
|
||||||
|
playerCount: 0,
|
||||||
|
players: []
|
||||||
|
});
|
||||||
|
|
||||||
|
function sendCard(cardInput: Card): void {
|
||||||
|
backendConnection.sendMessage(MessageType.PLAYER_CARD, {card: cardInput});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function showGameState(gamestate: GameState) {
|
||||||
|
|
||||||
|
|
||||||
|
switch (gamestate.gamePhase) {
|
||||||
|
case GamePhase.GAME_START:
|
||||||
|
gameStateText.value = "Spiel startet";
|
||||||
|
break;
|
||||||
|
case GamePhase.TRICK_START:
|
||||||
|
gameStateText.value = "Runde startet";
|
||||||
|
tableCards.value = [];
|
||||||
|
trickCard.value = undefined
|
||||||
|
gameInfoText.value = "";
|
||||||
|
break;
|
||||||
|
case GamePhase.WAIT_FOR_CARD:
|
||||||
|
gameState.value = gamestate;
|
||||||
|
gameStateText.value = gamestate.currentPlayer + " muss eine Karte legen.";
|
||||||
|
break;
|
||||||
|
case GamePhase.PLAYER_CARD:
|
||||||
|
gameStateText.value = gamestate.currentPlayer + " hat eine Karte gespielt.";
|
||||||
|
|
||||||
|
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 = gamestate.currentPlayer + " sticht.";
|
||||||
|
trickCard.value = gamestate.card
|
||||||
|
break;
|
||||||
|
case GamePhase.GAME_STOP:
|
||||||
|
await router.push("/gamesession");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gameStateText.value = "Fehler";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
socket.value = backendConnection.getWebSocket();
|
||||||
|
|
||||||
|
const messageListener = (message: string) => {
|
||||||
|
const message1: BackendMessage = JSON.parse(message);
|
||||||
|
if (message1.message_type === MessageType.GET_ONLINE_GAME) {
|
||||||
|
gameSession.value = message1.content.game;
|
||||||
|
console.log(message1.content)
|
||||||
|
}
|
||||||
|
if (message1.message_type === MessageType.GAME_STATE) {
|
||||||
|
console.log(message1.content)
|
||||||
|
showGameState(message1.content)
|
||||||
|
}
|
||||||
|
if (message1.message_type === MessageType.ONLINE_PLAYER_HAND) {
|
||||||
|
botCards.value = message1.content.cards;
|
||||||
|
console.log(message1.content.cards)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backendConnection.addMessageListener(messageListener);
|
||||||
|
|
||||||
|
backendConnection.sendMessage(MessageType.GAME_START_READY);
|
||||||
|
backendConnection.sendMessage(MessageType.GET_ONLINE_GAME);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div v-for="message in messageFromServer" :key="message">{{ message }}</div>
|
||||||
|
<div>
|
||||||
|
<div class="flex gap-2 place-content-center">
|
||||||
|
<div class="text-sm breadcrumbs">
|
||||||
|
<ul>
|
||||||
|
<li v-for="player in gameSession.players">
|
||||||
|
<span
|
||||||
|
:class="{'text-primary': gameState!.currentPlayer === player.playerName}"
|
||||||
|
class="inline-flex gap-2 items-center">
|
||||||
|
<i v-if="!player.isBot" class="bi bi-person"></i>
|
||||||
|
<i v-else class="bi bi-robot"></i>
|
||||||
|
<p>{{ player.playerName }}</p>
|
||||||
|
</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</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>
|
||||||
112
Frontend/src/pages/GameSession.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {scg} from "ioc-service-container";
|
||||||
|
import {BackendMessage, GameSession, MessageType} from "../BackendMessage.ts";
|
||||||
|
import {computed, onMounted, ref} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const backendConnection = scg("BackendConnection");
|
||||||
|
const socket = ref<WebSocket | null>();
|
||||||
|
|
||||||
|
const gameSession = ref<GameSession>({
|
||||||
|
serverName: "",
|
||||||
|
playerCount: 0,
|
||||||
|
players: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const allPlayersReady = computed(() => {
|
||||||
|
// Check if all players are ready by iterating through them
|
||||||
|
return gameSession.value.players.every(player => player.isReady);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
refreshGameInfo();
|
||||||
|
socket.value = backendConnection.getWebSocket();
|
||||||
|
|
||||||
|
|
||||||
|
const messageListener = async (message: string) => {
|
||||||
|
const message1: BackendMessage = JSON.parse(message);
|
||||||
|
console.log(message1)
|
||||||
|
if (message1.message_type === MessageType.GET_ONLINE_GAME && "game" in message1.content) {
|
||||||
|
gameSession.value = message1.content.game;
|
||||||
|
}
|
||||||
|
if (message1.message_type === MessageType.GAME_START_READY) {
|
||||||
|
// Resolve the Promise when the success message is received
|
||||||
|
await router.push("/dedicatedgame");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backendConnection.addMessageListener(messageListener);
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshGameInfo() {
|
||||||
|
backendConnection.sendMessage(MessageType.GET_ONLINE_GAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function leaveGame() {
|
||||||
|
backendConnection.sendMessage(MessageType.LEAVE_ONLINE_GAME);
|
||||||
|
await router.push("/online");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setStatusReady() {
|
||||||
|
backendConnection.sendMessage(MessageType.SET_STATUS_READY);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startDedicated() {
|
||||||
|
backendConnection.sendMessage(MessageType.START_DEDICATED_GAME);
|
||||||
|
|
||||||
|
const successMessageReceived = new Promise<void>((resolve) => {
|
||||||
|
const messageListener = (message: string) => {
|
||||||
|
const message1: BackendMessage = JSON.parse(message);
|
||||||
|
console.log(message)
|
||||||
|
if (message1.message_type === MessageType.GAME_START_READY) {
|
||||||
|
// Resolve the Promise when the success message is received
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
backendConnection.addMessageListener(messageListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
await successMessageReceived;
|
||||||
|
|
||||||
|
await router.push("/dedicatedgame");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<button class="btn btn-primary" @click="leaveGame()">Leave Game
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-2 max-w-xl mx-auto">
|
||||||
|
<div class="flex flex-col gap-4 p-6 bg-base-200 rounded-box">
|
||||||
|
<h1 class="font-bold text-xl">{{ gameSession.serverName }}</h1>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit In odit
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="font-medium text-2xl">{{ gameSession.playerCount }}/4</span>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
Spieler:
|
||||||
|
<div v-for="player in gameSession.players" class="flex flex-row gap-4 p-6 bg-base-200 rounded-box">
|
||||||
|
<p class="grow">{{ player.playerName }}</p>
|
||||||
|
<p v-if="player.isBot" class="text-warning"><i class="bi bi-robot"></i></p>
|
||||||
|
<p v-else-if="player.isReady" class="text-success">Bereit</p>
|
||||||
|
<p v-else class="text-error">Nicht bereit</p>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" @click="setStatusReady()">Toggle Ready
|
||||||
|
</button>
|
||||||
|
<button :disabled="!allPlayersReady" class="btn btn-primary max-w-xl" @click="startDedicated()">Starten</button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
222
Frontend/src/pages/LocalGame.vue
Normal 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>
|
||||||
186
Frontend/src/pages/MainMenu.vue
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
<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("10.6.9.57:8085")
|
||||||
|
const isConnected = ref<boolean>(false);
|
||||||
|
const isPingInProgress = ref<boolean>(false);
|
||||||
|
const secondsRemaining = ref<number>(10);
|
||||||
|
|
||||||
|
const checkInterval: number = 5;
|
||||||
|
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("http://" + 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);
|
||||||
|
if (message1.message_type === MessageType.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>
|
||||||
|
|
||||||
|
<button disabled class="btn btn-primary">Bald verfügbar</button>
|
||||||
|
</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>
|
||||||
|
|
||||||
|
<button disabled class="btn btn-primary">Bald verfügbar</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
94
Frontend/src/pages/OnlineGameList.vue
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import {scg} from "ioc-service-container";
|
||||||
|
import {BackendMessage, GameSession, MessageType} from "../BackendMessage.ts";
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
import {useRouter} from "vue-router";
|
||||||
|
|
||||||
|
const backendConnection = scg("BackendConnection");
|
||||||
|
const socket = ref<WebSocket | null>();
|
||||||
|
|
||||||
|
const gameList = ref<GameSession[]>([]);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
// Load playerName from localStorage or set default value if not present
|
||||||
|
const storedPlayerName = localStorage.getItem("playerName");
|
||||||
|
const playerName = ref<string>(storedPlayerName || "SchafkopfPlayer");
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
backendConnection.sendMessage(MessageType.SET_PLAYER_NAME, {playerName: playerName.value});
|
||||||
|
refreshGameList();
|
||||||
|
socket.value = backendConnection.getWebSocket();
|
||||||
|
|
||||||
|
const messageListener = (message: string) => {
|
||||||
|
const message1: BackendMessage = JSON.parse(message);
|
||||||
|
if (message1.message_type === MessageType.LIST_ONLINE_GAMES && "games" in message1.content) {
|
||||||
|
console.log(message1)
|
||||||
|
gameList.value = message1.content.games;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
backendConnection.addMessageListener(messageListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
function refreshGameList() {
|
||||||
|
backendConnection.sendMessage(MessageType.LIST_ONLINE_GAMES);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createOnlineGame() {
|
||||||
|
const serverName = `Schafkopf_${new Date().getTime()}`; // Append timestamp to server name
|
||||||
|
backendConnection.sendMessage(MessageType.CREATE_ONLINE_GAME, {serverName: serverName});
|
||||||
|
await router.push("/gamesession");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function joinGame(serverName: string) {
|
||||||
|
backendConnection.sendMessage(MessageType.JOIN_ONLINE_GAME, {serverName: serverName});
|
||||||
|
await router.push("/gamesession");
|
||||||
|
}
|
||||||
|
|
||||||
|
// function getServerByName(serverName: string): GameSession | undefined {
|
||||||
|
// return gameList.value.find(session => session.serverName === serverName);
|
||||||
|
// }
|
||||||
|
|
||||||
|
function sendPlayerName() {
|
||||||
|
backendConnection.sendMessage(MessageType.SET_PLAYER_NAME, {playerName: playerName.value});
|
||||||
|
localStorage.setItem("playerName", playerName.value);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<router-link to="/">
|
||||||
|
<button class="btn btn-primary">zurück</button>
|
||||||
|
</router-link>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" @click="refreshGameList()">Refresh Game
|
||||||
|
List
|
||||||
|
</button>
|
||||||
|
<input
|
||||||
|
v-model="playerName" type="text" placeholder="Type here" class="input input-bordered w-full max-w-xs"
|
||||||
|
@change="sendPlayerName()"/>
|
||||||
|
<div class="flex flex-col max-w-xl mx-auto gap-2">
|
||||||
|
<button class="btn btn-primary" @click="createOnlineGame()">Create Game</button>
|
||||||
|
<div v-for="game in gameList" :key="game.serverName" class="flex max-w-lg">
|
||||||
|
|
||||||
|
<div class="flex flex-col gap-4 p-6 bg-base-200 rounded-box">
|
||||||
|
<h1 class="font-bold text-xl">{{ game.serverName }}</h1>
|
||||||
|
|
||||||
|
<span>
|
||||||
|
Lorem ipsum dolor sit amet consectetur adipisicing elit In odit
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<span class="font-medium text-2xl">{{ game.playerCount }}/4</span>
|
||||||
|
|
||||||
|
<a class="btn btn-primary btn-sm" @click="joinGame(game.serverName)">Join</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
||||||
68
Frontend/src/services/BackendConnection.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import {MessageType} from "../BackendMessage.ts";
|
||||||
|
|
||||||
|
export class BackendConnection {
|
||||||
|
|
||||||
|
|
||||||
|
private readonly webSocket: WebSocket;
|
||||||
|
private messageListeners: ((message: string) => void)[] = [];
|
||||||
|
|
||||||
|
|
||||||
|
constructor(backendUri: string) {
|
||||||
|
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, content?: any): void {
|
||||||
|
let jsonMessage;
|
||||||
|
if (content === undefined) {
|
||||||
|
jsonMessage = {
|
||||||
|
origin: "FRONTEND",
|
||||||
|
message: {
|
||||||
|
message_type: messageType,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
jsonMessage = {
|
||||||
|
origin: "FRONTEND",
|
||||||
|
message: {
|
||||||
|
message_type: messageType,
|
||||||
|
content: content
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
15
Frontend/src/services/DependencyInjection.ts
Normal 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);
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
export default {
|
export default {
|
||||||
content: [
|
content: [
|
||||||
"./index.html",
|
"./index.html",
|
||||||
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
"./src/**/*.{vue,js,ts,jsx,tsx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {},
|
extend: {},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [require("daisyui")],
|
||||||
}
|
}
|
||||||