13 Commits

Author SHA1 Message Date
27f04ec085 added cmd playing mode 2024-07-22 16:41:41 +02:00
05300c1153 added cmd playing mode 2024-07-22 00:48:10 +02:00
4a167bf3b4 init kotlin backend 2024-07-20 20:04:37 +02:00
Valentin Heiserer
ea5df95228 edited a lot of stuff (#52) 2024-04-26 18:54:39 +00:00
Valentin Heiserer
539e29dc56 Make fit for beta release (#51)
* removed

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

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

* deleted web-content and fixed bug

* edited main page
2024-04-23 21:54:31 +02:00
Valentin Heiserer
cab2d36f48 refactored a lot and added possibility to play games on DedicatedServer (#43) 2024-04-19 16:15:51 +02:00
Valentin Heiserer
6259d0bef3 added arduino nfcReader files (#42) 2024-04-17 22:49:50 +00:00
107 changed files with 2827 additions and 2269 deletions

View File

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

View File

@@ -26,10 +26,6 @@ jobs:
run: npm install
working-directory: Frontend
- name: Create .env file
run: echo "VITE_APP_WEBSOCKET_IP=localhost" > .env
working-directory: Frontend
- name: Build Frontend
run: npm run build
working-directory: Frontend
@@ -52,12 +48,14 @@ jobs:
- name: Archive dist folder
# run: mv Backend/target/schafkopf-backend-build-jar-with-dependencies.jar schafkopf-bot.jar && zip -r dist.zip schafkopf-bot.jar
run: mv Backend/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
uses: softprops/action-gh-release@v2
if: startsWith(github.ref, 'refs/tags/')
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 }}

View File

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

View File

@@ -1,139 +1,142 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.heiserer</groupId>
<artifactId>schafkopf-2</artifactId>
<version>0.0.1</version>
<packaging>pom</packaging>
<name>schafkopf-2</name>
<description>schafkopf-2</description>
<modules>
<module>schafkopf-shared</module>
</modules>
<properties>
<ktor_version>2.3.12</ktor_version>
<kotlin.code.style>official</kotlin.code.style>
<kotlin_version>2.0.0</kotlin_version>
<logback_version>1.4.14</logback_version>
<slf4j_version>2.0.9</slf4j_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<main.class>de.heiserer.ApplicationKt</main.class>
</properties>
<repositories>
</repositories>
<dependencies>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-core-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-netty-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback_version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-tests-jvm</artifactId>
<version>${ktor_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-debug</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/main/resources</directory>
</resource>
</resources>
<artifactId>schafkopf-backend-java</artifactId>
<packaging>pom</packaging>
<modules>
<module>schafkopf-client</module>
<module>schafkopf-server</module>
<module>schafkopf-shared</module>
</modules>
<groupId>org.schafkopf</groupId>
<modelVersion>4.0.0</modelVersion>
<version>1.0-SNAPSHOT</version>
<properties>
<maven-compiler.version>3.5.1</maven-compiler.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<pi4j.version>2.4.0</pi4j.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<slf4j.version>1.7.32</slf4j.version>
</properties>
<build>
<finalName>schafkopf-backend-build</finalName>
<plugins>
<!-- Maven Clean Plugin -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>clean-initial</id>
<phase>pre-clean</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
<executions>
<execution>
<configuration>
<configLocation>google_checks.xml</configLocation>
<consoleOutput>true</consoleOutput>
<failOnViolation>true</failOnViolation>
<violationSeverity>warning</violationSeverity>
</configuration>
<goals>
<goal>check</goal>
</goals>
<id>validate</id>
<phase>validate</phase>
</execution>
</executions>
<groupId>org.apache.maven.plugins</groupId>
<version>3.3.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
</manifest>
</archive>
</configuration>
<version>3.3.0</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<verbose>false</verbose>
</configuration>
<version>3.11.0</version>
</plugin>
<!-- Maven Assembly Plugin -->
</plugins>
</build>
<dependencies>
<dependency>
<groupId>com.fazecast</groupId>
<artifactId>jSerialComm</artifactId>
<version>2.6.0</version> <!-- Check for the latest version on the official repository -->
</dependency>
<dependency>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<artifactId>slf4j-simple</artifactId>
<groupId>org.slf4j</groupId>
<version>${slf4j.version}</version>
</dependency>
<!-- To write basic websockets against -->
<dependency>
<artifactId>websocket-jetty-api</artifactId>
<groupId>org.eclipse.jetty.websocket</groupId>
<version>11.0.15</version>
</dependency>
<!-- To run websockets in embedded server -->
<dependency>
<artifactId>websocket-jetty-server</artifactId>
<groupId>org.eclipse.jetty.websocket</groupId>
<version>11.0.20</version>
</dependency>
<dependency>
<artifactId>jetty-servlets</artifactId>
<groupId>org.eclipse.jetty</groupId>
<version>11.0.19</version>
</dependency>
<!-- To run websockets client -->
<dependency>
<artifactId>websocket-jetty-client</artifactId>
<groupId>org.eclipse.jetty.websocket</groupId>
<version>11.0.15</version>
</dependency>
<dependency>
<artifactId>gson</artifactId>
<groupId>com.google.code.gson</groupId>
<version>2.10.1</version>
</dependency>
<dependency>
<artifactId>dotenv-java</artifactId>
<groupId>io.github.cdimascio</groupId>
<version>3.0.0</version>
</dependency>
</dependencies>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin_version}</version>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

38
Backend/schafkopf-client/.gitignore vendored Normal file
View File

@@ -0,0 +1,38 @@
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
src/main/resources/web-content

View File

@@ -1,60 +1,144 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.heiserer</groupId>
<artifactId>schafkopf-client</artifactId>
<version>0.0.1</version>
<name>schafkopf-client</name>
<description>schafkopf-client</description>
<properties>
<ktor_version>2.3.12</ktor_version>
<kotlin.code.style>official</kotlin.code.style>
<kotlin_version>2.0.0</kotlin_version>
<logback_version>1.4.14</logback_version>
<slf4j_version>2.0.9</slf4j_version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.compiler.incremental>true</kotlin.compiler.incremental>
<main.class>de.heiserer.SchafkopfClientKt</main.class>
</properties>
<repositories>
</repositories>
<dependencies>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-core-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-netty-jvm</artifactId>
<version>${ktor_version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback_version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j_version}</version>
</dependency>
<dependency>
<groupId>io.ktor</groupId>
<artifactId>ktor-server-tests-jvm</artifactId>
<version>${ktor_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-debug</artifactId>
<version>1.6.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>de.heiserer</groupId>
<artifactId>schafkopf-shared</artifactId>
<version>0.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<artifactId>schafkopf-client</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-shared</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>schafkopf-client-build</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.BackendServer</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin_version}</version>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>${main.class}</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>${main.class}</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>assemble-all</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

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

View File

@@ -1,77 +0,0 @@
package org.schafkopf;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketAdapter;
/** Class that represents one Frontend Connection. */
public class FrontendEndpoint extends WebSocketAdapter {
private final CountDownLatch closureLatch = new CountDownLatch(1);
private BackendServer backendServer;
public FrontendEndpoint(BackendServer backendServer) {
this.backendServer = backendServer;
System.out.println("new FrontendEndpoint");
}
@Override
public void onWebSocketConnect(Session session) {
super.onWebSocketConnect(session);
String clientIp = session.getRemoteAddress().toString();
System.out.println("Endpoint connected from ip: " + clientIp);
backendServer.addFrontendEndpoint(this);
}
@Override
public void onWebSocketText(String message) {
super.onWebSocketText(message);
System.out.println("Received TEXT message:" + message);
if (message.contains("startsimulation")) {
backendServer.schafkopfGame.startGame();
}
if (message.contains("stopsimulation")) {
backendServer.schafkopfGame.stopGame();
}
if (message.contains("showtrumpf")) {
backendServer.schafkopfGame.showTrumpf();
}
if (message.contains("showfarben")) {
backendServer.schafkopfGame.showFarbe();
}
if (message.contains("setgame")) {
backendServer.schafkopfGame.setGame(message);
}
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
super.onWebSocketClose(statusCode, reason);
backendServer.removeFrontendEndpoint(this);
System.out.println("Socket Closed: [" + statusCode + "] " + reason);
closureLatch.countDown();
}
@Override
public void onWebSocketError(Throwable cause) {
super.onWebSocketError(cause);
cause.printStackTrace(System.err);
}
/** send a Message to the connected FrontEnd. */
public void sendMessage(String message) {
try {
getRemote().sendString(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,23 +0,0 @@
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 FrontendEndpointCreator implements JettyWebSocketCreator {
private BackendServer backendServer;
public FrontendEndpointCreator(BackendServer backendServer) {
this.backendServer = backendServer;
}
@Override
public Object createWebSocket(
JettyServerUpgradeRequest jettyServerUpgradeRequest,
JettyServerUpgradeResponse jettyServerUpgradeResponse) {
return new FrontendEndpoint(this.backendServer);
}
}

View File

@@ -1,80 +0,0 @@
package org.schafkopf;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
/** GameState. */
public class GameState {
public GamePhase getGamePhase() {
return this.gamePhase;
}
/** GamePhase. */
public enum GamePhase {
CHOOSE_GAME("Spiel muss gewählt werden"),
GAME_START("Warten auf das Legen einer Karte"),
TRICK_START("Warten auf das Legen einer Karte"),
WAIT_FOR_CARD("Warten auf das Legen einer Karte"),
PLAYER_CARD("Warten auf das Legen einer Karte"),
PLAYER_TRICK("Spieler sticht"),
GAME_STOP("Spieler sticht");
// Add more phases as needed
private final String description;
GamePhase(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
private GamePhase gamePhase;
private Integer currentPlayer; // Using Integer to allow for null
private Karte card;
private KartenFarbe color;
private boolean trumpf;
// Constructors, getters, and setters
public GameState(GamePhase phase) {
this.gamePhase = phase;
}
public GameState(GamePhase phase, Integer player) {
this.gamePhase = phase;
this.currentPlayer = player;
}
/** GameState. */
public GameState(GamePhase phase, Integer player, Karte card, KartenFarbe color, boolean trumpf) {
this.gamePhase = phase;
this.currentPlayer = player;
this.card = card;
this.color = color;
this.trumpf = trumpf;
}
/** GameState. */
public GameState(GamePhase phase, Integer player, Karte card) {
this.gamePhase = phase;
this.currentPlayer = player;
this.card = card;
}
/** GameState. */
public JsonObject getJson() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.add("gamestate", gson.toJsonTree(this));
return jsonObject;
}
}

View File

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

View File

@@ -1,86 +0,0 @@
package org.schafkopf;
import org.schafkopf.GameState.GamePhase;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.player.Player;
import org.schafkopf.spielcontroller.SpielController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** The main class that controlls the game flow. */
public class Spielablauf {
private static final Logger logger = LoggerFactory.getLogger(Spielablauf.class);
private final KartenListe gespielteKarten = new KartenListe();
private final KartenListe tischKarten = new KartenListe();
private final SpielController spiel;
private final Player[] players;
private final Schafkopf schafkopf;
Spielablauf(Schafkopf schafkopf, SpielController spiel) {
this.schafkopf = schafkopf;
this.spiel = spiel;
this.players = schafkopf.getPlayer();
playRound();
}
private void playRound() {
int startingPlayer = 0;
logger.info("Starte Stiche");
for (int i = 0; i < 8; i++) {
logger.info("Stich: {}", i);
startingPlayer = playTrick(startingPlayer);
}
schafkopf.stopGame();
}
private int playTrick(int startingPlayer) {
schafkopf.setAndSendGameState(new GameState(GamePhase.TRICK_START));
for (int i = 0; i < 4; i++) {
int currentPlayer = (i + startingPlayer) % 4;
logger.info("Spieler ist dran: {}", currentPlayer);
schafkopf.setAndSendGameState(new GameState(GamePhase.WAIT_FOR_CARD, currentPlayer));
Karte playedCard = players[currentPlayer].play(spiel, tischKarten, gespielteKarten);
tischKarten.addKarten(playedCard);
schafkopf.setAndSendGameState(
new GameState(
GamePhase.PLAYER_CARD,
currentPlayer,
playedCard,
tischKarten.getByIndex(0).getFarbe(),
spiel.isTrumpf(tischKarten.getByIndex(0))));
}
int stichSpieler = SpielController.welcheKarteSticht(tischKarten);
logger.info("Stiche ende");
int winningPlayerIndex = (startingPlayer + stichSpieler) % 4;
logger.warn("Karte sticht: {}", winningPlayerIndex);
schafkopf.setAndSendGameState(
new GameState(
GamePhase.PLAYER_TRICK, winningPlayerIndex, tischKarten.getByIndex(stichSpieler)));
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
logger.error("error sleep");
}
gespielteKarten.addKarten(tischKarten);
tischKarten.clear();
return winningPlayerIndex;
}
}

View File

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

View File

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

View File

@@ -1,43 +0,0 @@
package org.schafkopf.player;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
import org.schafkopf.spielcontroller.SpielController;
/** Player that represents the Bot. */
public class BotPlayer extends Player {
private KartenListe eigeneKarten;
private KartenListe unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
public BotPlayer() {
// TODO document why this constructor is empty
}
@Override
public Karte play(SpielController spiel, KartenListe tischKarten, KartenListe gespielteKarten) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
Karte card = spiel.welcheKarteSpielIch(true, gespielteKarten, eigeneKarten, tischKarten);
eigeneKarten.removeKarten(card);
System.out.println("Eigene Karte legen");
return card;
}
/** Set the Cards of the Player. */
public void setCards(KartenListe cards) {
System.out.println("Eigene Karte setzen");
this.eigeneKarten = cards;
this.unbekannteKarten = KartenUtil.initializeSchafKopfCardDeck();
this.unbekannteKarten.removeKarten(eigeneKarten);
System.out.println("Eigene Karte fertig");
}
}

View File

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

View File

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

View File

@@ -1,36 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/** SpielController that implements Logic of a Farb Geier. */
public class FarbGeierController extends SoloController {
/**
* Create instance of SpielController.
*
* @param farbe Trumpffarbe of the Farb Geier.
*/
public FarbGeierController(int activePlayer, KartenFarbe farbe) {
super(activePlayer);
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe oberKarten = kartenList.getKarten(KartenSymbol.OBER);
KartenListe farbTrumpfKarten = kartenList.getKarten(farbe);
farbTrumpfKarten.removeKarten(KartenSymbol.OBER);
farbTrumpfKarten.addKarten(oberKarten);
kartenList.removeKarten(farbTrumpfKarten);
this.trumpfKarten = new KartenListe(farbTrumpfKarten);
this.farbKarten = new KartenListe(kartenList);
}
public Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten) {
return null;
}
}

View File

@@ -1,40 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/** SpielController that implements Logic of a Farb Solo. */
public class FarbSoloController extends SoloController {
/**
* Create instance of SpielController.
*
* @param farbe Trumpffarbe of the Farb Solo.
*/
public FarbSoloController(int activePlayer, KartenFarbe farbe) {
super(activePlayer);
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe unterKarten = kartenList.getKarten(KartenSymbol.UNTER);
KartenListe farbTrumpfKarten = kartenList.getKarten(farbe);
farbTrumpfKarten.removeKarten(KartenSymbol.UNTER);
farbTrumpfKarten.removeKarten(KartenSymbol.OBER);
farbTrumpfKarten.addKarten(kartenList.getKarten(KartenSymbol.UNTER));
farbTrumpfKarten.addKarten(kartenList.getKarten(KartenSymbol.OBER));
kartenList.removeKarten(farbTrumpfKarten);
this.trumpfKarten = new KartenListe(farbTrumpfKarten);
this.farbKarten = new KartenListe(kartenList);
}
public Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten) {
return null;
}
}

View File

@@ -1,36 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/** SpielController that implements Logic of a Farb Wenz. */
public class FarbWenzController extends SoloController {
/**
* Create instance of SpielController.
*
* @param farbe Trumpffarbe of the Farb Wenz.
*/
public FarbWenzController(int activePlayer, KartenFarbe farbe) {
super(activePlayer);
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe unterKarten = kartenList.getKarten(KartenSymbol.UNTER);
KartenListe farbTrumpfKarten = kartenList.getKarten(farbe);
farbTrumpfKarten.removeKarten(KartenSymbol.UNTER);
farbTrumpfKarten.addKarten(unterKarten);
kartenList.removeKarten(farbTrumpfKarten);
this.trumpfKarten = new KartenListe(farbTrumpfKarten);
this.farbKarten = new KartenListe(kartenList);
}
public Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten) {
return null;
}
}

View File

@@ -1,24 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/**
* SpielController that implements Logic of a Geier Game.
*/
public class GeierController extends GeierWenzController {
/**
* Create instance of Geier Game.
*/
public GeierController(int activePlayer) {
super(activePlayer);
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe oberKarten = kartenList.getKarten(KartenSymbol.OBER);
kartenList.removeKarten(oberKarten);
this.trumpfKarten = new KartenListe(oberKarten);
this.farbKarten = new KartenListe(kartenList);
}
}

View File

@@ -1,20 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
/**
* SpielController that implements Logic of a Geier/Wenz Game.
*/
public class GeierWenzController extends SoloController {
public GeierWenzController(int activePlayer) {
super(activePlayer);
}
@Override
public Karte welcheKarteSpielIch(boolean istSpieler, KartenListe gespielteKarten,
KartenListe meineHand, KartenListe tischKarten) {
return null;
}
}

View File

@@ -1,58 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
/** SpielController that implements Logic of a Sau Spiel Game. */
public class SauSpielController extends StandardController {
KartenFarbe suchFarbe;
/** Class that represents one Card of the game. */
public SauSpielController(int activePlayer, KartenFarbe farbe) {
super(activePlayer);
this.suchFarbe = farbe;
}
/** choose witch Card should be played with the right Game logic. */
public Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten) {
System.out.println("Ich spiele eine Karte Sauspiel");
int spielerNummer = tischKarten.size();
switch (spielerNummer) {
case 0:
if (istSpieler) {
return meineHand.getLast();
} else {
return meineHand.getByIndex(0);
}
case 1:
if (istSpieler) {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 2);
} else {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 0);
}
case 2:
if (istSpieler) {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 2);
} else {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 0);
}
case 3:
if (istSpieler) {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 2);
} else {
return farbeZugeben(meineHand, tischKarten.getByIndex(0), 0);
}
default:
System.out.println("Ungültige SpielerNummer");
}
return null;
}
}

View File

@@ -1,19 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenListe;
/**
* abstract Class that represents Logic of a Solo like Game.
*/
public abstract class SoloController extends SpielController {
SoloController(int activePlayer) {
super(activePlayer);
}
public Karte welcheKarteSpielIch(
KartenListe gespielteKarten, KartenListe meineHand, KartenListe tischKarten) {
return null;
}
}

View File

@@ -1,133 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenUtil;
/** Base Class of Game Controllers. */
public abstract class SpielController {
protected static KartenListe trumpfKarten;
protected static KartenListe farbKarten;
protected static int activePlayer;
public SpielController(int activePlayer) {
this.activePlayer = activePlayer;
}
/**
* Create instance of SpielController.
*
* @param meineHand Cards one Player holds.
* @param ersteKarte color the Player has to play.
* @param mode Mode the player chooses a Card if multiple are available.
*/
public static Karte farbeZugeben(KartenListe meineHand, Karte ersteKarte, int mode) {
KartenListe hand = new KartenListe(meineHand);
sortiereKarten(hand);
boolean trumpfGespielt = trumpfKarten.containsKarte(ersteKarte);
KartenListe handTrumpfKarten = hand.removeKarten(trumpfKarten);
KartenListe handfarbKarten;
if (trumpfGespielt) {
handfarbKarten = handTrumpfKarten;
} else {
handfarbKarten = hand.getKarten(ersteKarte.getFarbe());
}
if (handfarbKarten.size() == 1) {
return handfarbKarten.getByIndex(0);
} else if (handfarbKarten.size() > 1) {
return switch (mode) {
case 0 -> // Abspatzen
handfarbKarten.getByIndex(0);
case 1, 2 -> // Stechen // Schmieren
handfarbKarten.getLast();
default -> null;
};
}
if (handfarbKarten.isEmpty()) {
switch (mode) {
case 0: // Abspatzen
return hand.getByIndex(0);
case 1: // Schmieren
return hand.getLast();
case 2: // Stechen
if (!handTrumpfKarten.isEmpty()) {
return handTrumpfKarten.getLast(); // trumpf reinspielen
} else {
return hand.getByIndex(0); // wenn kein Trumpf und farblos, abschpatzen
}
default:
return null;
}
}
return null;
}
/**
* sorts Cards, so they are in the right order for the active game.
*
* @param karten Trumpffarbe of the Farb Geier.
*/
public static void sortiereKarten(KartenListe karten) {
KartenListe kartenReihenfolge = new KartenListe(farbKarten);
kartenReihenfolge.addKarten(trumpfKarten);
KartenListe kartenListe = KartenUtil.initializeSchafKopfCardDeck();
kartenListe.removeKarten(karten);
kartenReihenfolge.removeKarten(kartenListe);
karten.clear();
karten.addKarten(kartenReihenfolge);
}
/**
* checks, which card has the highest strength and will win one Stich.
*
* @param karten Cards to check.
*/
public static int welcheKarteSticht(KartenListe karten) {
KartenListe kartenNew = new KartenListe(karten);
sortiereKarten(kartenNew);
KartenListe farbTischKarten = kartenNew.removeKarten(trumpfKarten);
System.out.println("trumpfKarten:");
System.out.println(trumpfKarten.getJson());
if (!farbTischKarten.isEmpty()) {
System.out.println("trumpfkarten:");
System.out.println(farbTischKarten.getJson());
return karten.indexOf(farbTischKarten.getLast());
} else {
KartenFarbe firstColor = karten.getByIndex(0).getFarbe();
KartenListe firstColorCards = kartenNew.removeKarten(firstColor);
System.out.println("firstcolor:");
System.out.println(firstColorCards.getJson());
return karten.indexOf(firstColorCards.getLast());
}
}
public abstract Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten);
public KartenListe getTrumpfKarten() {
return trumpfKarten;
}
public boolean isTrumpf(Karte card) {
return trumpfKarten.containsKarte(card);
}
public KartenListe getFarbKarten() {
return farbKarten;
}
}

View File

@@ -1,33 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.Karte;
import org.schafkopf.karte.KartenFarbe;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/** SpielController that has the standard Card Deck for Sauspiel, Bettel und Co. */
public abstract class StandardController extends SpielController {
StandardController(int activePlayer) {
super(activePlayer);
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe herzKarten = kartenList.getKarten(KartenFarbe.HERZ);
herzKarten.removeKarten(KartenSymbol.UNTER);
herzKarten.removeKarten(KartenSymbol.OBER);
herzKarten.addKarten(kartenList.getKarten(KartenSymbol.UNTER));
herzKarten.addKarten(kartenList.getKarten(KartenSymbol.OBER));
kartenList.removeKarten(herzKarten);
this.trumpfKarten = new KartenListe(herzKarten);
this.farbKarten = new KartenListe(kartenList);
}
public abstract Karte welcheKarteSpielIch(
boolean istSpieler,
KartenListe gespielteKarten,
KartenListe meineHand,
KartenListe tischKarten);
}

View File

@@ -1,25 +0,0 @@
package org.schafkopf.spielcontroller;
import org.schafkopf.karte.KartenListe;
import org.schafkopf.karte.KartenSymbol;
import org.schafkopf.karte.KartenUtil;
/**
* SpielController that implements Logic of a Wenz Game.
*/
public class WenzController extends GeierWenzController {
/**
* Create instance of Wenz Game.
*/
public WenzController(int activePlayer) {
super(activePlayer);
this.activePlayer = activePlayer;
KartenListe kartenList = KartenUtil.initializeSchafKopfCardDeck();
KartenListe unterKarten = kartenList.getKarten(KartenSymbol.UNTER);
kartenList.removeKarten(unterKarten);
this.trumpfKarten = new KartenListe(unterKarten);
this.farbKarten = new KartenListe(kartenList);
}
}

View File

@@ -0,0 +1,122 @@
import de.heiserer.CmdPlayer
import de.heiserer.SchafkopfMessager
import de.heiserer.cards.Card
import de.heiserer.cards.CardColor
import de.heiserer.cards.CardSymbol
import de.heiserer.player.Player
class CmdSchafkopfMessager: SchafkopfMessager {
override fun sendPlayerTurn(player: Player) {
if(player !is CmdPlayer){
clearConsole()
}
println("${player.getName()} ist am Zug")
Thread.sleep(1000)
}
override fun sendPlayerWonTrick(player: Player) {
println("${player.getName()} hat den Stich gewonnen.")
println()
}
override fun sendPlayerWonGame(player: Player) {
TODO("Not yet implemented")
}
override fun sendCardPlayed(player: Player, card: Card) {
println("${player.getName()} hat ${card.displayName} gespielt.")
}
override fun sendTableCards(cards: List<Card>) {
println("Tischkarten:")
CmdCard.printCards(cards)
Thread.sleep(3000)
}
class CmdCard(card: Card){
private val symbolMap = mapOf(
CardSymbol.SIEBEN to "7",
CardSymbol.ACHT to "8",
CardSymbol.NEUN to "9",
CardSymbol.ZEHN to "10",
CardSymbol.OBER to "O",
CardSymbol.UNTER to "U",
CardSymbol.KOENIG to "K",
CardSymbol.ASS to "A"
)
private val colorMap = mapOf(
CardColor.HERZ to "",
CardColor.SCHELL to "",
CardColor.EICHEL to "",
CardColor.BLATT to ""
)
private val symbol = symbolMap[card.symbol]?: "?"
private val color = colorMap[card.color]?: "?"
private val colorName = card.color.displayName
private val symbolName = card.symbol.displayName
private val cardWidth = 13
private val symbolLine = "${symbol.padEnd(cardWidth-5)}$symbol"
private val colorLine = "${color.padEnd(cardWidth-5)}$color"
private val cardLines = listOf(
"┌───────────┐",
symbolLine,
colorLine,
"${colorName.padCenter(cardWidth-2)}",
"${symbolName.padCenter(cardWidth-2)}",
colorLine,
symbolLine,
"└───────────┘"
)
fun print(){
cardLines.forEach {
println(it)
}
}
fun getCardLines(): List<String> {
return cardLines
}
private fun String.padCenter(totalWidth: Int, padChar: Char = ' '): String {
if (this.length >= totalWidth) return this
val padding = totalWidth - this.length
val padStart = this.length + padding / 2
return this.padStart(padStart, padChar).padEnd(totalWidth, padChar)
}
companion object {
fun printCards(cards: List<Card>) {
val lastCardLines = CmdCard(cards.last()).getCardLines()
val cardLines = cards.map { CmdCard(it).getCardLines() }
// Loop through the indices of the lines that are present in all cards
for (i in lastCardLines.indices) {
// For other lines, print up to the first 5 characters
var line = cardLines.joinToString(" ") { it[i].take(5) }
line += lastCardLines[i].drop(5)
println(line)
}
}
}
}
fun clearConsole() {
for (i in 1..100) {
println()
}
}
}

View File

@@ -0,0 +1,60 @@
package de.heiserer
import de.heiserer.cards.*
import de.heiserer.player.Player
import java.util.*
class CmdPlayer(name: String) : Player(name) {
private val scanner = Scanner(System.`in`)
override fun playCard(tableCards: UnsortedCardList, gameType: GameType): Card {
val cardsCopy = cards.getCopyOfCards()
println("Available cards:")
CmdSchafkopfMessager.CmdCard.printCards(cardsCopy)
// Print cards with their respective index
cardsCopy.forEachIndexed { index, card ->
println("(${index + 1}) ${card.name}") // Prints card with index starting from 1
}
// Prompt the user to enter a card number
println("Please enter the number of the card you want to play:")
val userInput = scanner.nextLine()
val cardToPlay = getCardByUserInput(userInput, tableCards, gameType)
return cards.remove(cardToPlay)
}
private fun getCardByUserInput(
userInput: String,
tableCards: UnsortedCardList,
gameType: GameType
): Card {
val cardsCopy = cards.getCopyOfCards()
return try {
// Convert user input to an integer and adjust for zero-based index
val cardIndex = userInput.toInt() - 1
// Ensure the index is within bounds
if (cardIndex in cardsCopy.indices) {
cardsCopy[cardIndex] // Return the selected card
if(validateCard(cardsCopy[cardIndex], tableCards, gameType)){
cardsCopy[cardIndex]
} else {
println("Invalid card. Please try again.")
playCard(tableCards, gameType) // Retry if the card is invalid
}
} else {
println("Invalid card number. Please try again.")
playCard(tableCards, gameType) // Retry if the input was invalid
}
} catch (e: NumberFormatException) {
println("Invalid input. Please enter a number.")
playCard(tableCards, gameType) // Retry on invalid input
}
}
}

View File

@@ -0,0 +1,21 @@
package de.heiserer
import CmdSchafkopfMessager
import de.heiserer.player.NPCPlayer
import de.heiserer.plugins.*
import io.ktor.server.application.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
fun main() {
val test = SchafkopfGameController(listOf(NPCPlayer("NPC 1"), NPCPlayer("NPC 2"), CmdPlayer("Dev"), NPCPlayer("NPC 4")), CmdSchafkopfMessager())
test.playRound()
embeddedServer(Netty, port = 8080, host = "0.0.0.0", module = Application::module)
.start(wait = true)
}
fun Application.module() {
configureRouting()
}

View File

@@ -0,0 +1,13 @@
package de.heiserer.plugins
import io.ktor.server.application.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
fun Application.configureRouting() {
routing {
get("/") {
call.respondText("Hello World!")
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 815 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 859 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 971 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 797 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 603 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 646 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 730 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 905 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 870 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 412 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 498 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 626 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 957 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1010 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 959 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 686 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 600 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 704 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 741 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1009 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 958 KiB

View File

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

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>schafkopf-server</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-shared</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<finalName>schafkopf-server-build</finalName>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.3.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>org.schafkopf.Main</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -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());
}
}
}

View File

@@ -1,20 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.schafkopf</groupId>
<artifactId>schafkopf-backend-java</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>de.heiserer</groupId>
<artifactId>schafkopf-2</artifactId>
<version>0.0.1</version>
</parent>
<artifactId>schafkopf-shared</artifactId>
<artifactId>schafkopf-shared</artifactId>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.code.style>official</kotlin.code.style>
<kotlin.compiler.jvmTarget>1.8</kotlin.compiler.jvmTarget>
</properties>
<repositories>
<repository>
<id>mavenCentral</id>
<url>https://repo1.maven.org/maven2/</url>
</repository>
</repositories>
<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>2.22.2</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>MainKt</mainClass>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit5</artifactId>
<version>2.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -1,87 +0,0 @@
package org.schafkopf.karte;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
/** enum to represent all cards in the game. */
public enum Karte {
SCHELL_7(KartenFarbe.SCHELL, KartenSymbol.SEVEN),
SCHELL_8(KartenFarbe.SCHELL, KartenSymbol.EIGHT),
SCHELL_9(KartenFarbe.SCHELL, KartenSymbol.NINE),
SCHELL_U(KartenFarbe.SCHELL, KartenSymbol.UNTER),
SCHELL_O(KartenFarbe.SCHELL, KartenSymbol.OBER),
SCHELL_K(KartenFarbe.SCHELL, KartenSymbol.KOENIG),
SCHELL_X(KartenFarbe.SCHELL, KartenSymbol.TEN),
SCHELL_A(KartenFarbe.SCHELL, KartenSymbol.ASS),
HERZ_7(KartenFarbe.HERZ, KartenSymbol.SEVEN),
HERZ_8(KartenFarbe.HERZ, KartenSymbol.EIGHT),
HERZ_9(KartenFarbe.HERZ, KartenSymbol.NINE),
HERZ_U(KartenFarbe.HERZ, KartenSymbol.UNTER),
HERZ_O(KartenFarbe.HERZ, KartenSymbol.OBER),
HERZ_K(KartenFarbe.HERZ, KartenSymbol.KOENIG),
HERZ_X(KartenFarbe.HERZ, KartenSymbol.TEN),
HERZ_A(KartenFarbe.HERZ, KartenSymbol.ASS),
BLATT_7(KartenFarbe.BLATT, KartenSymbol.SEVEN),
BLATT_8(KartenFarbe.BLATT, KartenSymbol.EIGHT),
BLATT_9(KartenFarbe.BLATT, KartenSymbol.NINE),
BLATT_U(KartenFarbe.BLATT, KartenSymbol.UNTER),
BLATT_O(KartenFarbe.BLATT, KartenSymbol.OBER),
BLATT_K(KartenFarbe.BLATT, KartenSymbol.KOENIG),
BLATT_X(KartenFarbe.BLATT, KartenSymbol.TEN),
BLATT_A(KartenFarbe.BLATT, KartenSymbol.ASS),
EICHEL_7(KartenFarbe.EICHEL, KartenSymbol.SEVEN),
EICHEL_8(KartenFarbe.EICHEL, KartenSymbol.EIGHT),
EICHEL_9(KartenFarbe.EICHEL, KartenSymbol.NINE),
EICHEL_U(KartenFarbe.EICHEL, KartenSymbol.UNTER),
EICHEL_O(KartenFarbe.EICHEL, KartenSymbol.OBER),
EICHEL_K(KartenFarbe.EICHEL, KartenSymbol.KOENIG),
EICHEL_X(KartenFarbe.EICHEL, KartenSymbol.TEN),
EICHEL_A(KartenFarbe.EICHEL, KartenSymbol.ASS);
private final String id;
private final KartenFarbe farbe;
private final KartenSymbol symbol;
private final String displayName;
private final int punkte;
Karte(KartenFarbe farbe, KartenSymbol symbol) {
this.farbe = farbe;
this.symbol = symbol;
this.id = this.name().toLowerCase();
this.displayName = farbe.getDisplayName() + " " + symbol.getDisplayName();
this.punkte = symbol.getValue();
}
public String getId() {
return this.id;
}
public String getName() {
return this.displayName;
}
public KartenFarbe getFarbe() {
return this.farbe;
}
public KartenSymbol getSymbol() {
return this.symbol;
}
public int getPunkte() {
return this.punkte;
}
/** get the Card as a Json Object. */
public JsonObject getJson() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.add("card", gson.toJsonTree(this));
return jsonObject;
}
}

View File

@@ -1,21 +0,0 @@
package org.schafkopf.karte;
/**
* Enum for all possible Card Colors.
*/
public enum KartenFarbe {
EICHEL("Eichel"),
BLATT("Blatt"),
HERZ("Herz"),
SCHELL("Schell");
private final String displayName;
KartenFarbe(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
}

View File

@@ -1,203 +0,0 @@
package org.schafkopf.karte;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.List;
/**
* A Class that represents a list of Cards.
*/
public class KartenListe {
private List<Karte> kartenListe;
public KartenListe() {
this.kartenListe = new ArrayList<>();
}
public KartenListe(KartenListe liste) {
this.kartenListe = new ArrayList<>(liste.getKartenListe());
}
public List<Karte> getKartenListe() {
return this.kartenListe;
}
/**
* A Class that represents a list of Cards.
*/
public void addKarten(Karte karte) {
if (!this.containsKarte(karte)) {
this.kartenListe.add(karte);
return;
}
throw new RuntimeException("Karte bereits vorhanden: " + karte.getName());
}
// methoden zum hinzufügen von karten
/**
* A Class that represents a list of Cards.
*/
public void addKarten(KartenListe karten) {
for (Karte karte : karten.getKartenListe()) {
this.addKarten(karte);
}
}
/**
* A Class that represents a list of Cards.
*/
public KartenListe removeKarten(KartenListe karten) {
KartenListe result = new KartenListe();
for (Karte karteWeg : karten.getKartenListe()) {
for (Karte karte : this.kartenListe) {
if (karte.getId().equals(karteWeg.getId())) {
result.addKarten(karte);
break;
}
}
}
this.kartenListe.removeAll(result.getKartenListe());
return result;
}
// methoden zum entfernen von karten
/**
* A Class that represents a list of Cards.
*/
public KartenListe removeKarten(KartenFarbe farbe) {
KartenListe result = new KartenListe();
for (Karte karte : this.kartenListe) {
if (karte.getFarbe().equals(farbe)) {
result.addKarten(karte);
}
}
this.kartenListe.removeAll(result.getKartenListe());
return result;
}
/**
* A Class that represents a list of Cards.
*/
public KartenListe removeKarten(KartenSymbol symbol) {
KartenListe result = new KartenListe();
for (Karte karte : this.kartenListe) {
if (karte.getSymbol().equals(symbol)) {
result.addKarten(karte);
}
}
this.kartenListe.removeAll(result.getKartenListe());
return result;
}
/**
* A Class that represents a list of Cards.
*/
public Karte removeKarten(Karte karteToRemove) {
for (Karte karte : this.kartenListe) {
if (karte.getId().equals(karteToRemove.getId())) {
this.kartenListe.remove(karte);
return karte;
}
}
return null;
}
/**
* A Class that represents a list of Cards.
*/
public Karte removeKarten(String idToRemove) {
for (Karte karte : this.kartenListe) {
if (karte.getId().equals(idToRemove)) {
this.kartenListe.remove(karte);
return karte;
}
}
return null;
}
/**
* A Class that represents a list of Cards.
*/
public boolean containsKarte(Karte karte) {
for (Karte karteInListe : this.kartenListe) {
if (karteInListe.getId().equals(karte.getId())) {
return true;
}
}
return false;
}
// get Karten
/**
* A Class that represents a list of Cards.
*/
public KartenListe getKarten(KartenFarbe farbe) {
KartenListe result = new KartenListe();
for (Karte karte : this.kartenListe) {
if (karte.getFarbe().equals(farbe)) {
result.addKarten(karte);
}
}
return result;
}
/**
* A Class that represents a list of Cards.
*/
public KartenListe getKarten(KartenSymbol symbol) {
KartenListe result = new KartenListe();
for (Karte karte : this.kartenListe) {
if (karte.getSymbol().equals(symbol)) {
result.addKarten(karte);
}
}
return result;
}
/**
* A Class that represents a list of Cards.
*/
public JsonObject getJson() {
Gson gson = new Gson();
JsonObject jsonObject = new JsonObject();
jsonObject.add("cards", gson.toJsonTree(this.kartenListe));
return jsonObject;
}
public boolean isEmpty() {
return this.kartenListe.isEmpty();
}
public Karte getLast() {
return this.kartenListe.getLast();
}
public Karte getByIndex(int index) {
return this.kartenListe.get(index);
}
public int size() {
return this.kartenListe.size();
}
/**
* A Class that represents a list of Cards.
*/
public int indexOf(Karte karte) {
for (Karte karteInListe : this.kartenListe) {
if (karteInListe.getId().equals(karte.getId())) {
return this.kartenListe.indexOf(karteInListe);
}
}
return -1;
}
public void clear() {
this.kartenListe.clear();
}
}

View File

@@ -1,38 +0,0 @@
package org.schafkopf.karte;
/**
* Enum for all possible Card Symbols.
*/
public enum KartenSymbol {
SIX("6", "6", 0),
SEVEN("7", "7", 0),
EIGHT("8", "8", 0),
NINE("9", "9", 0),
UNTER("u", "Unter", 2),
OBER("o", "Ober", 3),
KOENIG("k", "König", 4),
TEN("x", "10", 10),
ASS("a", "Ass", 11);
private final String displayName;
private final String id;
private final int value;
KartenSymbol(String id, String displayName, int value) {
this.displayName = displayName;
this.value = value;
this.id = id;
}
public String getDisplayName() {
return displayName;
}
public int getValue() {
return value;
}
public String getId() {
return id;
}
}

View File

@@ -1,82 +0,0 @@
package org.schafkopf.karte;
import java.util.Random;
/** Class that brings usefully functions for Card/s. */
public class KartenUtil {
/** initialize a normal Card Deck. It will be in the standard order. */
public static KartenListe initializeSchafKopfCardDeck() {
KartenListe deck = new KartenListe();
for (Karte karte : Karte.values()) {
deck.addKarten(karte);
}
deck.removeKarten(KartenSymbol.SIX);
return deck;
}
/**
* Create a List of Random Cards.
*
* @param anzahl count of random cards.
*/
public static KartenListe zieheZufallsHand(int anzahl) {
KartenListe karten = initializeSchafKopfCardDeck();
KartenListe gezogeneKarten = new KartenListe();
Random random = new Random();
// Ziehe zufällige Karten
for (int i = 0; i < anzahl; i++) {
int zufallsIndex = random.nextInt(karten.size());
Karte gezogeneKarte = karten.getByIndex(zufallsIndex);
gezogeneKarten.addKarten(gezogeneKarte);
karten.removeKarten(gezogeneKarte);
}
return gezogeneKarten;
}
/**
* converts Uid from a NFC Card to a card ID.
*
* @param uid uId to get the Card ID from.
*/
public static Karte getIdOfUid(String uid) {
return switch (uid) {
case "04E7A9C2126F80" -> Karte.EICHEL_7;
case "04A46BB4780000" -> Karte.EICHEL_8;
case "04A26BB4780000" -> Karte.EICHEL_9;
case "04A16BB4780000" -> Karte.EICHEL_X;
case "049E6BB4780000" -> Karte.EICHEL_K;
case "04A86BB4780000" -> Karte.EICHEL_A;
case "04A06BB4780000" -> Karte.EICHEL_U;
case "049F6BB4780000" -> Karte.EICHEL_O;
case "04F26BB4780000" -> Karte.BLATT_7;
case "04A76BB4780000" -> Karte.BLATT_8;
case "049B6BB4780000" -> Karte.BLATT_9;
case "04996BB4780000" -> Karte.BLATT_X;
case "041CD2C2126F81" -> Karte.BLATT_K;
case "04A96BB4780000" -> Karte.BLATT_A;
case "049A6BB4780000" -> Karte.BLATT_U;
case "049D6BB4780000" -> Karte.BLATT_O;
case "04936BB4780000" -> Karte.SCHELL_7;
case "04F697C2126F80" -> Karte.SCHELL_8;
case "04946BB4780000" -> Karte.SCHELL_9;
case "04956BB4780000" -> Karte.SCHELL_X;
case "04986BB4780000" -> Karte.SCHELL_K;
case "04AA6BB4780000" -> Karte.SCHELL_A;
case "04966BB4780000" -> Karte.SCHELL_U;
case "04976BB4780000" -> Karte.SCHELL_O;
case "04F36BB4780000" -> Karte.HERZ_7;
case "04B06BB4780000" -> Karte.HERZ_8;
case "04AF6BB4780000" -> Karte.HERZ_9;
case "04AE6BB4780000" -> Karte.HERZ_X;
case "04AB6BB4780000" -> Karte.HERZ_K;
case "049C6BB4780000" -> Karte.HERZ_A;
case "04AD6BB4780000" -> Karte.HERZ_U;
case "04AC6BB4780000" -> Karte.HERZ_O;
default -> null;
};
}
}

View File

@@ -0,0 +1,62 @@
package de.heiserer
import card.CardList
import card.CardToolkit
import de.heiserer.cards.*
import de.heiserer.player.Player
class SchafkopfGameController(palyers: List<Player>, private val messager: SchafkopfMessager) {
private val players = palyers
private lateinit var gameType: GameType
private val playedCards: CardList = UnsortedCardList()
fun playRound(startingOffset: Int = 0){
serveCards()
gameType = GameType.SAU_SPIEL
players.forEach { it.sortCards(gameType) }
var startingPlayer: Player = players[startingOffset % 4]
for(i in 0 until 8){
startingPlayer = playTrick(startingPlayer)
messager.sendPlayerWonTrick(startingPlayer)
}
}
private fun playTrick(startingPlayer: Player): Player{
val tableCards = UnsortedCardList()
for(i in 0 until 4){
val currentPlayer = calculatePlayerOffset(startingPlayer, i)
messager.sendPlayerTurn(currentPlayer)
val card = currentPlayer.playCard(tableCards, gameType)
messager.sendCardPlayed(currentPlayer, card)
tableCards.add(card)
messager.sendTableCards(tableCards.getCopyOfCards())
}
playedCards.add(tableCards)
val trickOffset = CardToolkit.whoTricks(gameType, tableCards)
return calculatePlayerOffset(startingPlayer, trickOffset)
}
private fun calculatePlayerOffset(startingPlayer: Player, i: Int): Player = players[(players.indexOf(startingPlayer) + i) % 4]
private fun serveCards(){
val deck = UnsortedCardList(true)
deck.shuffle()
for(i in 0 until 4){
val playerCards = UnsortedCardList()
for(j in 0 until 8){
playerCards.add(deck.removeLast())
}
players[i % 4].serveCards(playerCards)
}
}
}

View File

@@ -0,0 +1,12 @@
package de.heiserer
import de.heiserer.cards.Card
import de.heiserer.player.Player
interface SchafkopfMessager {
fun sendPlayerTurn(player: Player)
fun sendPlayerWonTrick(player: Player)
fun sendPlayerWonGame(player: Player)
fun sendCardPlayed(player: Player, card: Card)
fun sendTableCards(cards: List<Card>)
}

View File

@@ -0,0 +1,6 @@
package de.heiserer.cards
class CardAlreadyAddedException(message: String, val card: Card) : RuntimeException(message) {
// You can add additional constructors or methods if needed, but for now, this is sufficient to handle the scenario of adding a card that's already in the list.
}

View File

@@ -0,0 +1,25 @@
package card
import de.heiserer.cards.Card
import de.heiserer.cards.GameType
import de.heiserer.cards.SortedCardList
interface CardList {
fun add(card: Card)
fun add(cards: CardList)
fun remove(card: Card): Card
fun remove(cards: CardList)
fun removeLast(): Card
fun removeFirst(): Card
fun get(index: Int): Card
fun getLast(): Card
fun getCopyOfCards(): List<Card>
fun indexOf(card: Card): Int
operator fun contains(card: Card): Boolean
fun size(): Int
fun print()
fun asSortedCardList(type: GameType): SortedCardList
}

View File

@@ -0,0 +1,40 @@
package card
import de.heiserer.cards.Card
import de.heiserer.cards.GameType
import de.heiserer.cards.UnsortedCardList
class CardToolkit private constructor(private val gameType: GameType) {
private val sortedCardList = UnsortedCardList(true).asSortedCardList(gameType)
fun isTrumpf(card: Card): Boolean = card in sortedCardList.getTrumpf()
fun whoTricks(cards: CardList): Int {
if(cards.size() != 4){
throw IllegalArgumentException("Es müssen 4 Karten auf dem Tisch liegen.")
}
val sortedCards = cards.asSortedCardList(gameType)
if(sortedCards.getTrumpf().size() > 0){
return cards.indexOf(sortedCards.getTrumpf().getLast())
} else {
val firstColor = cards.get(0).color
val colorCards = sortedCards.getCardsWithoutTrumpf(firstColor)
return cards.indexOf(colorCards.getLast())
}
}
companion object {
fun isTrumpf(gameType: GameType, card: Card): Boolean {
val toolkit = CardToolkit(gameType)
return toolkit.isTrumpf(card)
}
fun whoTricks(gameType: GameType, cards: CardList): Int {
val toolkit = CardToolkit(gameType)
return toolkit.whoTricks(cards)
}
}
}

View File

@@ -0,0 +1,82 @@
package de.heiserer.cards
enum class Card(val color: CardColor, val symbol: CardSymbol) {
SCHELL_7(CardColor.SCHELL, CardSymbol.SIEBEN),
SCHELL_8(CardColor.SCHELL, CardSymbol.ACHT),
SCHELL_9(CardColor.SCHELL, CardSymbol.NEUN),
SCHELL_U(CardColor.SCHELL, CardSymbol.UNTER),
SCHELL_O(CardColor.SCHELL, CardSymbol.OBER),
SCHELL_K(CardColor.SCHELL, CardSymbol.KOENIG),
SCHELL_X(CardColor.SCHELL, CardSymbol.ZEHN),
SCHELL_A(CardColor.SCHELL, CardSymbol.ASS),
HERZ_7(CardColor.HERZ, CardSymbol.SIEBEN),
HERZ_8(CardColor.HERZ, CardSymbol.ACHT),
HERZ_9(CardColor.HERZ, CardSymbol.NEUN),
HERZ_U(CardColor.HERZ, CardSymbol.UNTER),
HERZ_O(CardColor.HERZ, CardSymbol.OBER),
HERZ_K(CardColor.HERZ, CardSymbol.KOENIG),
HERZ_X(CardColor.HERZ, CardSymbol.ZEHN),
HERZ_A(CardColor.HERZ, CardSymbol.ASS),
BLATT_7(CardColor.BLATT, CardSymbol.SIEBEN),
BLATT_8(CardColor.BLATT, CardSymbol.ACHT),
BLATT_9(CardColor.BLATT, CardSymbol.NEUN),
BLATT_U(CardColor.BLATT, CardSymbol.UNTER),
BLATT_O(CardColor.BLATT, CardSymbol.OBER),
BLATT_K(CardColor.BLATT, CardSymbol.KOENIG),
BLATT_X(CardColor.BLATT, CardSymbol.ZEHN),
BLATT_A(CardColor.BLATT, CardSymbol.ASS),
EICHEL_7(CardColor.EICHEL, CardSymbol.SIEBEN),
EICHEL_8(CardColor.EICHEL, CardSymbol.ACHT),
EICHEL_9(CardColor.EICHEL, CardSymbol.NEUN),
EICHEL_U(CardColor.EICHEL, CardSymbol.UNTER),
EICHEL_O(CardColor.EICHEL, CardSymbol.OBER),
EICHEL_K(CardColor.EICHEL, CardSymbol.KOENIG),
EICHEL_X(CardColor.EICHEL, CardSymbol.ZEHN),
EICHEL_A(CardColor.EICHEL, CardSymbol.ASS);
val id = name.lowercase()
val displayName = "${color.displayName} ${symbol.displayName}"
val points = symbol.value
}
enum class CardColor(val order: Int, val displayName: String) {
SCHELL(0,"Schell"),
HERZ(1,"Herz"),
BLATT(2,"Blatt"),
EICHEL(3,"Eichel")
}
enum class CardSymbol(val order: Int, val displayName: String, val value: Int) {
SIEBEN(0,"7", 0),
ACHT(1,"8", 0),
NEUN(2,"9", 0),
UNTER(3,"Unter", 2),
OBER(4,"Ober", 3),
KOENIG(5,"König", 4),
ZEHN(6,"10", 10),
ASS(7,"Ass", 11)
}
enum class GameType(val color: CardColor?, val symbol: CardSymbol?){
RAMSCH(CardColor.HERZ, null),
SAU_SPIEL(CardColor.HERZ, null),
BETTEL(CardColor.HERZ, null),
SCHELL_GEIER(CardColor.SCHELL, CardSymbol.OBER),
HERZ_GEIER(CardColor.HERZ, CardSymbol.OBER),
BLATT_GEIER(CardColor.BLATT, CardSymbol.OBER),
EICHEL_GEIER(CardColor.EICHEL, CardSymbol.OBER),
SCHELL_WENZ(CardColor.SCHELL, CardSymbol.UNTER),
HERZ_WENZ(CardColor.HERZ, CardSymbol.UNTER),
BLATT_WENZ(CardColor.BLATT, CardSymbol.UNTER),
EICHEL_WENZ(CardColor.EICHEL, CardSymbol.UNTER),
GEIER(null, CardSymbol.OBER),
WENZ(null, CardSymbol.UNTER),
SCHELL_SOLO(CardColor.SCHELL, null),
BLATT_SOLO(CardColor.BLATT, null),
EICHEL_SOLO(CardColor.EICHEL, null),
HERZ_SOLO(CardColor.HERZ, null),
}

View File

@@ -0,0 +1,41 @@
package de.heiserer.cards
class SortedCardList(private val gameType: GameType, withAllCards: Boolean = false) : UnsortedCardList(withAllCards) {
override fun add(card: Card) {
super.add(card)
sort()
}
fun getCardsWithoutTrumpf(color: CardColor? = null): SortedCardList {
val cardsWithoutTrumpf = SortedCardList(gameType)
color?.let { cardsWithoutTrumpf.add(get(it)) }?: cardsWithoutTrumpf.add(this)
try {
cardsWithoutTrumpf.remove(getTrumpf())
} catch (_: IllegalArgumentException) {
}
return cardsWithoutTrumpf
}
fun getTrumpf(): SortedCardList {
val trumpf = SortedCardList(gameType)
gameType.symbol?.let {
trumpf.add(get(it))
} ?: run {
trumpf.add(get(CardSymbol.OBER))
trumpf.add(get(CardSymbol.UNTER))
}
gameType.color?.let {
try{ trumpf.add(get(it)) } catch (_: CardAlreadyAddedException) {}
}
return trumpf
}
private fun sort() {
super.sortInternal(gameType.symbol, gameType.color)
}
}

View File

@@ -0,0 +1,131 @@
package de.heiserer.cards
import card.CardList
open class UnsortedCardList(withAllCards: Boolean = false): CardList {
private val cards: MutableList<Card> = if(withAllCards){
enumValues<Card>().toMutableList()
} else {
mutableListOf()
}
override operator fun contains(card: Card): Boolean {
return cards.contains(card)
}
override fun add(card: Card) {
if (card !in cards) {
cards.add(card)
} else {
throw CardAlreadyAddedException("Karte $card is already in the deck.", card)
}
}
override fun add(cards: CardList) {
cards.getCopyOfCards().forEach { card -> add(card) }
}
override fun remove(card: Card) : Card {
if (card !in cards) {
throw IllegalArgumentException("Karte $card is not in the deck.")
} else {
cards.remove(card)
return card
}
}
override fun remove(cards: CardList) {
cards.getCopyOfCards().forEach { card -> remove(card) }
}
override fun removeFirst(): Card {
if (cards.isEmpty()) {
throw IllegalArgumentException("Deck is empty.")
}
return cards.removeFirst()
}
override fun removeLast(): Card {
if (cards.isEmpty()) {
throw IllegalArgumentException("Deck is empty.")
}
return cards.removeLast()
}
override fun indexOf(card: Card): Int {
return cards.indexOf(card)
}
override fun get(index: Int): Card {
if(index < 0 || index >= cards.size){
throw IllegalArgumentException("Index $index is out of bounds.")
}
return cards[index]
}
override fun getLast(): Card {
return cards[cards.size - 1]
}
protected fun get(color: CardColor): CardList {
val list = UnsortedCardList()
cards.forEach { card ->
if (card.color == color) {
list.add(card)
}
}
return list
}
protected fun get(symbol: CardSymbol): CardList {
val list = UnsortedCardList()
cards.forEach { card ->
if (card.symbol == symbol) {
list.add(card)
}
}
return list
}
override fun getCopyOfCards(): List<Card> {
return cards.toList()
}
override fun size(): Int {
return cards.size
}
override fun print() {
println("KartenDeck:")
cards.forEach { karte ->
println(karte.displayName)
}
println()
}
fun shuffle() {
cards.shuffle()
}
override fun asSortedCardList(type: GameType): SortedCardList {
val sortedList = SortedCardList(type)
cards.forEach { card -> sortedList.add(card) }
return sortedList
}
protected fun sortInternal(symbol: CardSymbol? = null, color: CardColor? = null) {
if (symbol != null && symbol != CardSymbol.OBER && symbol != CardSymbol.UNTER) {
throw IllegalArgumentException("Symbol $symbol is not accepted. Only OBER and UNTER are allowed.")
}
cards.sortWith(compareBy<Card> {
when {
symbol == null && it.symbol == CardSymbol.OBER -> 3
symbol == null && it.symbol == CardSymbol.UNTER -> 2
symbol != null && it.symbol == symbol -> 2
color != null && it.color == color -> 1
else -> 0
}
}.thenComparing(compareBy({ it.color.order }, { it.symbol.order })))
}
}

View File

@@ -0,0 +1,39 @@
package de.heiserer.player
import card.CardToolkit
import de.heiserer.cards.*
class NPCPlayer(name: String) : Player(name){
override fun playCard(tableCards: UnsortedCardList, gameType: GameType): Card {
if(tableCards.size() == 0){
return playTrumpf()
}
val firstCard = tableCards.get(0)
return if(CardToolkit.isTrumpf(gameType, firstCard)){
playTrumpf()
} else {
playColor(firstCard.color)
}
}
private fun playTrumpf(): Card {
val trumpfCards = cards.getTrumpf()
if (trumpfCards.size() > 0) {
val card = trumpfCards.removeLast()
return cards.remove(card)
} else {
return cards.removeFirst()
}
}
private fun playColor(color: CardColor): Card {
val colorCards = cards.getCardsWithoutTrumpf(color)
if (colorCards.size() > 0) {
val card = colorCards.removeLast()
return cards.remove(card)
} else {
return cards.removeFirst()
}
}
}

View File

@@ -0,0 +1,36 @@
package de.heiserer.player
import card.CardToolkit
import de.heiserer.cards.*
abstract class Player(private var name: String){
protected var cards: SortedCardList = SortedCardList(GameType.SAU_SPIEL)
fun serveCards(cards: UnsortedCardList){
this.cards = cards.asSortedCardList(GameType.SAU_SPIEL)
}
fun sortCards(gameType: GameType){
cards = cards.asSortedCardList(gameType)
}
abstract fun playCard(tableCards: UnsortedCardList, gameType: GameType): Card
fun getName(): String{
return name
}
protected fun validateCard(card: Card, tableCards: UnsortedCardList, gameType: GameType): Boolean{
if(tableCards.size() == 0){
return true
}
val firstCard = tableCards.get(0)
return if(CardToolkit.isTrumpf(gameType, firstCard)){
CardToolkit.isTrumpf(gameType, card) || cards.getTrumpf().size() == 0
} else {
card.color == firstCard.color || cards.getCardsWithoutTrumpf(firstCard.color).size() == 0
}
}
}

View File

@@ -0,0 +1,479 @@
import de.heiserer.cards.Card
import de.heiserer.cards.CardColor
import de.heiserer.cards.GameType
import de.heiserer.cards.UnsortedCardList
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource
class UnsortedListTest {
@Test
fun `init with all cards`() {
assertEquals(32, UnsortedCardList(true).size())
}
}
class SortingCardsTest {
private val defaultList = listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.HERZ_X,
Card.EICHEL_U,
Card.HERZ_O
)
companion object {
@JvmStatic
fun gameTypeProvider() = GameType.values().asList()
}
private fun testCardSorting(expectedCards: List<Card>, gameType: GameType) {
// ARRANGE
var cards = UnsortedCardList()
defaultList.shuffled().forEach {
cards.add(it)
}
// ACT
cards = cards.asSortedCardList(gameType)
// ASSERT
assertEquals(expectedCards.size, cards.size())
expectedCards.forEachIndexed { index, card ->
assertEquals(card, cards.get(index))
}
}
private fun expectedCardsForGameType(gameType: GameType): List<Card> {
return when(gameType) {
GameType.RAMSCH -> defaultList
GameType.SAU_SPIEL -> defaultList
GameType.BETTEL -> defaultList
GameType.SCHELL_GEIER -> listOf(
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O
)
GameType.HERZ_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
Card.HERZ_X,
Card.HERZ_O
)
GameType.BLATT_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.EICHEL_U,
Card.EICHEL_A,
Card.BLATT_8,
Card.BLATT_9,
Card.HERZ_O
)
GameType.EICHEL_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
Card.HERZ_O
)
GameType.SCHELL_WENZ -> listOf(
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.SCHELL_7,
Card.SCHELL_K,
Card.EICHEL_U,
)
GameType.HERZ_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.HERZ_O,
Card.HERZ_X,
Card.EICHEL_U
)
GameType.BLATT_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.EICHEL_A,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
)
GameType.EICHEL_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.EICHEL_U
)
GameType.GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
Card.HERZ_O
)
GameType.WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.EICHEL_U,
)
GameType.SCHELL_SOLO -> listOf(
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.SCHELL_7,
Card.SCHELL_K,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.BLATT_SOLO -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.EICHEL_A,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.EICHEL_SOLO -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.HERZ_SOLO -> defaultList
else -> throw IllegalArgumentException("No expected cards defined for gameType: $gameType")
}
}
@ParameterizedTest(name = "test sorting for {0}") // This gives a clear name in the test output
@MethodSource("gameTypeProvider")
fun `test sorting for all game types`(gameType: GameType) {
// ARRANGE
val expectedCards = expectedCardsForGameType(gameType)
// ACT & ASSERT
testCardSorting(expectedCards, gameType)
}
private fun testGetTrumpfCard(expectedCards: List<Card>, gameType: GameType) {
// ARRANGE
var cards = UnsortedCardList()
defaultList.shuffled().forEach {
cards.add(it)
}
// ACT
cards = cards.asSortedCardList(gameType).getTrumpf()
// ASSERT
expectedCards.forEachIndexed { index, card ->
assertEquals(card, cards.get(index))
}
}
private fun expectedTrumpfCardsForGameType(gameType: GameType): List<Card> {
val herzList = listOf(
Card.HERZ_X,
Card.EICHEL_U,
Card.HERZ_O
)
return when(gameType) {
GameType.RAMSCH -> herzList
GameType.SAU_SPIEL -> herzList
GameType.BETTEL -> herzList
GameType.SCHELL_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O
)
GameType.HERZ_GEIER -> listOf(
Card.HERZ_X,
Card.HERZ_O
)
GameType.BLATT_GEIER -> listOf(
Card.BLATT_8,
Card.BLATT_9,
Card.HERZ_O
)
GameType.EICHEL_GEIER -> listOf(
Card.EICHEL_U,
Card.EICHEL_A,
Card.HERZ_O
)
GameType.SCHELL_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.EICHEL_U,
)
GameType.HERZ_WENZ -> listOf(
Card.HERZ_O,
Card.HERZ_X,
Card.EICHEL_U
)
GameType.BLATT_WENZ -> listOf(
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
)
GameType.EICHEL_WENZ -> listOf(
Card.EICHEL_A,
Card.EICHEL_U
)
GameType.GEIER -> listOf(
Card.HERZ_O
)
GameType.WENZ -> listOf(
Card.EICHEL_U,
)
GameType.SCHELL_SOLO -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.BLATT_SOLO -> listOf(
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.EICHEL_SOLO -> listOf(
Card.EICHEL_A,
Card.EICHEL_U,
Card.HERZ_O
)
GameType.HERZ_SOLO -> herzList
else -> throw IllegalArgumentException("No expected cards defined for gameType: $gameType")
}
}
@ParameterizedTest(name = "test get Trumpfcards for {0}") // This gives a clear name in the test output
@MethodSource("gameTypeProvider")
fun `test get Trumpfcards for all game types`(gameType: GameType) {
// ARRANGE
val expectedCards = expectedTrumpfCardsForGameType(gameType)
// ACT & ASSERT
testGetTrumpfCard(expectedCards, gameType)
}
private fun testCardsWithoutTrumpf(expectedCards: List<Card>, gameType: GameType) {
// ARRANGE
var cards = UnsortedCardList()
defaultList.shuffled().forEach {
cards.add(it)
}
// ACT
cards = cards.asSortedCardList(gameType).getCardsWithoutTrumpf()
// ASSERT
expectedCards.forEachIndexed { index, card ->
assertEquals(card, cards.get(index))
}
}
private fun expectedCardsWithoutTrumpfForGameType(gameType: GameType): List<Card> {
val herzList = listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
)
return when(gameType) {
GameType.RAMSCH -> herzList
GameType.SAU_SPIEL -> herzList
GameType.BETTEL -> herzList
GameType.SCHELL_GEIER -> listOf(
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
)
GameType.HERZ_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
)
GameType.BLATT_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.EICHEL_U,
Card.EICHEL_A,
)
GameType.EICHEL_GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
)
GameType.SCHELL_WENZ -> listOf(
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
)
GameType.HERZ_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
)
GameType.BLATT_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.EICHEL_A,
)
GameType.EICHEL_WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
)
GameType.GEIER -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_U,
Card.EICHEL_A,
)
GameType.WENZ -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_O,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
)
GameType.SCHELL_SOLO -> listOf(
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
Card.EICHEL_A,
)
GameType.BLATT_SOLO -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.EICHEL_A,
)
GameType.EICHEL_SOLO -> listOf(
Card.SCHELL_7,
Card.SCHELL_K,
Card.HERZ_X,
Card.BLATT_8,
Card.BLATT_9,
)
GameType.HERZ_SOLO -> herzList
else -> throw IllegalArgumentException("No expected cards defined for gameType: $gameType")
}
}
@ParameterizedTest(name = "test get Cards without Trumpf for {0}") // This gives a clear name in the test output
@MethodSource("gameTypeProvider")
fun `test get Cards without Trumpf for all game types`(gameType: GameType) {
// ARRANGE
val expectedCards = expectedCardsWithoutTrumpfForGameType(gameType)
// ACT & ASSERT
testCardsWithoutTrumpf(expectedCards, gameType)
}
@Test
fun `test get Cards without Trumpf with Color`() {
// ARRANGE
var cards = UnsortedCardList()
defaultList.shuffled().forEach {
cards.add(it)
}
val expectedCards = listOf(
Card.BLATT_8,
Card.BLATT_9,
)
// ACT
cards = cards.asSortedCardList(GameType.SAU_SPIEL).getCardsWithoutTrumpf(CardColor.BLATT)
// ASSERT
expectedCards.forEachIndexed { index, card ->
assertEquals(card, cards.get(index))
}
}
}

View File

@@ -0,0 +1,26 @@
import card.CardToolkit
import de.heiserer.cards.*
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
class CardToolkitTest {
@Test
fun `test isTrumpf`() {
// ASSERT AND ACT
assertEquals(true, CardToolkit.isTrumpf(GameType.SAU_SPIEL, Card.HERZ_O))
assertEquals(false, CardToolkit.isTrumpf(GameType.SAU_SPIEL, Card.SCHELL_7))
}
@Test
fun whoTricks() {
// ARRANGE
val cards = UnsortedCardList()
cards.add(Card.SCHELL_7)
cards.add(Card.SCHELL_8)
cards.add(Card.SCHELL_K)
cards.add(Card.SCHELL_O)
// ASSERT AND ACT
assertEquals(3, CardToolkit.whoTricks(GameType.SAU_SPIEL, cards))
}
}

View File

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

View File

@@ -9,12 +9,16 @@
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.4.21"
"bootstrap-icons": "^1.11.3",
"ioc-service-container": "^1.6.1",
"vue": "^3.4.21",
"vue-router": "^4.3.2"
},
"devDependencies": {
"@typescript-eslint/parser": "^7.7.0",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"daisyui": "^4.10.2",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-vue": "^9.25.0",

View File

@@ -1,228 +1,10 @@
<script lang="ts" setup>
import {onMounted, ref} from 'vue';
import CardComp from './components/CardComponent.vue';
import {BackendMessage, Card, GamePhase, GameState} from "./BackendMessage";
const messageFromServer = ref<string[]>([]);
const gameStateText = ref<string>("Schafkopf");
const gameInfoText = ref<string>("");
const socket = ref<WebSocket | null>();
const tableCards = ref<Card[]>([]);
const botCards = ref(0);
const trickCard = ref<Card>();
const showGameSelect = ref(true);
function startSimulation(): void {
sendMessageToServer("startsimulation");
}
function 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);
});
});
import MessageBoard from "./components/MessageBoard.vue";
</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/>
<MessageBoard></MessageBoard>
</template>
<style lang="scss">
$card-height: 24rem;
.card0 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card1 {
z-index: 1;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.card2 {
z-index: 2;
top: calc(50% + ($card-height * 0.10));
left: calc(50% - ($card-height * 0.3));
transform: rotate(50deg) translate(-50%, -50%);
}
.card3 {
z-index: 3;
top: calc(50% - ($card-height * 0.125));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-30deg) translate(-50%, -50%);
}
.card4 {
z-index: 4;
top: calc(50% - ($card-height * 0.4));
left: calc(50% + ($card-height * 0.35));
transform: rotate(-60deg) translate(-50%, -50%);
}
@import "bootstrap-icons/font/bootstrap-icons.css";
</style>

View File

@@ -71,29 +71,107 @@ export enum GamePhase {
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
export interface CardArray {
cards: Card[];
export interface CardArrayMessage {
message_type: MessageType.ONLINE_PLAYER_HAND;
content: { cards: Card[] };
}
export interface CardObject {
card: Card;
export interface CardMessage {
message_type: MessageType.PLAYER_CARD;
content: { card: Card };
}
// Define the interface for the game state
export interface GameState {
gamePhase: GamePhase;
currentPlayer?: number;
currentPlayer?: string;
card?: Card;
color?: KartenFarbe;
trumpf?: boolean;
}
export interface GameSession {
serverName: string;
playerCount: number;
players: OnlinePlayer[];
}
export interface GameStateJson {
gamestate: GameState
export interface OnlinePlayer {
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
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"
}

View File

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

View File

@@ -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>

View 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>

View File

@@ -1,5 +1,24 @@
import { createApp } from 'vue'
import {createApp} from 'vue'
import './style.css'
import App from './App.vue'
import {createRouter, createWebHistory} from "vue-router";
import {setupService} from "./services/DependencyInjection.ts";
createApp(App).mount('#app')
const routes = [
{path: '/', component: () => import('./pages/MainMenu.vue'),},
{path: '/online', component: () => import('./pages/OnlineGameList.vue'),},
{path: '/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')

View 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>

View 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>

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