Feat/game-ui #180
@@ -119,3 +119,6 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
## IntelliJ
|
## IntelliJ
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
|
## bin
|
||||||
|
bin/
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
stages:
|
||||||
|
- lint
|
||||||
|
- report
|
||||||
|
- build
|
||||||
|
- test
|
||||||
|
|
||||||
|
# Reusable definitions
|
||||||
|
.gradle-cache: &gradle-cache
|
||||||
|
cache:
|
||||||
|
key: '$CI_PROJECT_ID-gradle'
|
||||||
|
paths:
|
||||||
|
- .gradle/
|
||||||
|
- ~/.gradle/caches/
|
||||||
|
|
||||||
|
.on-commit: &on-commit
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
when: never
|
||||||
|
- if: '$CI_COMMIT_BRANCH'
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
|
.on-mr: &on-mr
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
allow_failure: true # deactivate after all issues (shown by checkstyle) have been resolved
|
||||||
|
|
||||||
|
# Jobs
|
||||||
|
checkstyle:
|
||||||
|
<<: [*gradle-cache, *on-commit]
|
||||||
|
stage: lint
|
||||||
|
image: gradle:9.3.1-jdk25
|
||||||
|
script:
|
||||||
|
- gradle checkstyleMain checkstyleTest
|
||||||
|
allow_failure: true
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- build/reports/checkstyle/main.html
|
||||||
|
- build/reports/checkstyle/main.xml
|
||||||
|
- build/reports/checkstyle/test.html
|
||||||
|
- build/reports/checkstyle/test.xml
|
||||||
|
expose_as: 'Checkstyle Report'
|
||||||
|
expire_in: 1 week
|
||||||
|
|
||||||
|
checkstyle-mr:
|
||||||
|
<<: [*gradle-cache, *on-mr]
|
||||||
|
stage: lint
|
||||||
|
image: gradle:9.3.1-jdk25
|
||||||
|
script:
|
||||||
|
- gradle checkstyleMain checkstyleTest
|
||||||
|
allow_failure: true
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
paths:
|
||||||
|
- build/reports/checkstyle/main.html
|
||||||
|
- build/reports/checkstyle/main.xml
|
||||||
|
- build/reports/checkstyle/test.html
|
||||||
|
- build/reports/checkstyle/test.xml
|
||||||
|
expose_as: 'Checkstyle Report'
|
||||||
|
expire_in: 1 week
|
||||||
|
|
||||||
|
checkstyle-report:
|
||||||
|
stage: report
|
||||||
|
image: python:3.14
|
||||||
|
needs:
|
||||||
|
- job: checkstyle-mr
|
||||||
|
artifacts: true
|
||||||
|
optional: true
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
script:
|
||||||
|
- python3 scripts/convert-checkstyle-gl-report.py
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
reports:
|
||||||
|
codequality: gl-code-quality-report.json
|
||||||
|
expire_in: 1 week
|
||||||
|
|
||||||
|
compile-check:
|
||||||
|
<<: *gradle-cache
|
||||||
|
stage: build
|
||||||
|
image: gradle:9.3.1-jdk25
|
||||||
|
script:
|
||||||
|
- gradle compileTestJava
|
||||||
|
needs: []
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
allow_failure: false
|
||||||
|
- if: '$CI_COMMIT_BRANCH'
|
||||||
|
allow_failure: false
|
||||||
|
|
||||||
|
test:
|
||||||
|
<<: *gradle-cache
|
||||||
|
stage: test
|
||||||
|
image: gradle:9.3.1-jdk25
|
||||||
|
script:
|
||||||
|
- gradle test
|
||||||
|
artifacts:
|
||||||
|
when: always
|
||||||
|
reports:
|
||||||
|
junit: build/test-results/test/*.xml
|
||||||
|
paths:
|
||||||
|
- build/reports/tests/test/
|
||||||
|
expose_as: 'Test Report'
|
||||||
|
expire_in: 1 week
|
||||||
|
rules:
|
||||||
|
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
|
||||||
|
allow_failure: false
|
||||||
|
- if: '$CI_COMMIT_BRANCH'
|
||||||
|
allow_failure: true
|
||||||
Vendored
+3
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"java.configuration.updateBuildConfiguration": "automatic"
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 194 KiB |
@@ -2,6 +2,8 @@ plugins {
|
|||||||
id 'application'
|
id 'application'
|
||||||
id 'java'
|
id 'java'
|
||||||
id 'org.openjfx.javafxplugin' version '0.1.0'
|
id 'org.openjfx.javafxplugin' version '0.1.0'
|
||||||
|
id 'checkstyle'
|
||||||
|
id 'com.diffplug.spotless' version '7.0.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
group = 'ch.unibas.dmi.dbis'
|
group = 'ch.unibas.dmi.dbis'
|
||||||
@@ -27,14 +29,40 @@ javafx {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// Source: https://mvnrepository.com/artifact/org.apache.logging.log4j
|
||||||
|
implementation("org.apache.logging.log4j:log4j-api:2.25.3")
|
||||||
|
runtimeOnly("org.apache.logging.log4j:log4j-core:2.25.3")
|
||||||
|
|
||||||
|
// Source: https://mvnrepository.com/artifact/org.fusesource.jansi/jansi
|
||||||
|
runtimeOnly("org.fusesource.jansi:jansi:2.4.2")
|
||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
|
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
|
||||||
|
implementation 'org.json:json:20240303'
|
||||||
}
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
checkstyle {
|
||||||
|
toolVersion = '10.21.0'
|
||||||
|
configFile = file('config/checkstyle/checkstyle.xml')
|
||||||
|
configProperties = [
|
||||||
|
'suppressionFile': file('config/checkstyle/suppressions.xml').absolutePath
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
spotless {
|
||||||
|
java {
|
||||||
|
googleJavaFormat('1.25.2').aosp()
|
||||||
|
importOrder()
|
||||||
|
removeUnusedImports()
|
||||||
|
trimTrailingWhitespace()
|
||||||
|
endWithNewline()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tasks.named('jar', Jar) {
|
tasks.named('jar', Jar) {
|
||||||
manifest {
|
manifest {
|
||||||
attributes('Main-Class': application.mainClass.get())
|
attributes('Main-Class': application.mainClass.get())
|
||||||
@@ -42,6 +70,22 @@ tasks.named('jar', Jar) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Dieses JVM-Argument erlaubt JavaFX und anderen Libraries den Zugriff auf native Methoden.
|
||||||
|
// Ohne diese Einstellung erscheinen Warnungen und zukünftige Java-Versionen könnten den Zugriff blockieren.
|
||||||
|
// Siehe: https://openjdk.org/jeps/472
|
||||||
|
tasks.withType(JavaExec) {
|
||||||
|
jvmArgs += '--enable-native-access=ALL-UNNAMED'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
tasks.register('cruntest', JavaExec) {
|
||||||
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
|
mainClass = 'ch.unibas.dmi.dbis.cs108.casono.Main'
|
||||||
|
jvmArgs '--enable-native-access=ALL-UNNAMED'
|
||||||
|
args 'client', '0.0.0.0:1234'
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register('fatJar', Jar) {
|
tasks.register('fatJar', Jar) {
|
||||||
group = 'build'
|
group = 'build'
|
||||||
description = 'Assembles a runnable fat JAR including runtime dependencies.'
|
description = 'Assembles a runnable fat JAR including runtime dependencies.'
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE module PUBLIC
|
||||||
|
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
|
||||||
|
"https://checkstyle.org/dtds/configuration_1_3.dtd">
|
||||||
|
|
||||||
|
<module name="Checker">
|
||||||
|
<!-- Fail build if checkstyle finds violations -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/config.html#Properties_1 -->
|
||||||
|
<property name="severity" value="error"/>
|
||||||
|
|
||||||
|
<!-- Only check java files -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/config.html#Properties_1 -->
|
||||||
|
<property name="fileExtensions" value="java"/>
|
||||||
|
|
||||||
|
<module name="SuppressionFilter">
|
||||||
|
<property name="file" value="${suppressionFile}"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Only allow spaces -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/whitespace/filetabcharacter.html -->
|
||||||
|
<module name="FileTabCharacter"/>
|
||||||
|
|
||||||
|
<!-- Maximale Zeilenlänge -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/sizes/linelength.html -->
|
||||||
|
<module name="LineLength">
|
||||||
|
<property name="max" value="100"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<module name="TreeWalker">
|
||||||
|
<!-- Allows for emptylines BETWEEN methods and classes but DISALLOWS newlines after an opening bracket -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/whitespace/emptylineseparator.html -->
|
||||||
|
<module name="EmptyLineSeparator">
|
||||||
|
<property name="allowNoEmptyLineBetweenFields" value="true"/>
|
||||||
|
<property name="tokens" value="
|
||||||
|
METHOD_DEF,
|
||||||
|
CTOR_DEF,
|
||||||
|
STATIC_INIT,
|
||||||
|
INSTANCE_INIT,
|
||||||
|
CLASS_DEF,
|
||||||
|
INTERFACE_DEF,
|
||||||
|
ENUM_DEF
|
||||||
|
"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Enforce indentation of four whitespaces -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/misc/indentation.html -->
|
||||||
|
<module name="Indentation">
|
||||||
|
<property name="basicOffset" value="4"/>
|
||||||
|
<property name="caseIndent" value="4"/>
|
||||||
|
<property name="throwsIndent" value="4"/>
|
||||||
|
<property name="arrayInitIndent" value="4"/>
|
||||||
|
<property name="lineWrappingIndentation" value="8"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- No wildcard imports (import x.*) -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/imports/avoidstarimport.html -->
|
||||||
|
<module name="AvoidStarImport"/>
|
||||||
|
|
||||||
|
<!-- No unused imports -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/imports/unusedimports.html -->
|
||||||
|
<module name="UnusedImports"/>
|
||||||
|
|
||||||
|
<!-- No unordered / ungrouped imports -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/imports/importorder.html -->
|
||||||
|
<module name="ImportOrder"/>
|
||||||
|
|
||||||
|
<!-- Classes, enums, records, ... as PascalCase -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/typename.html -->
|
||||||
|
<module name="TypeName"/>
|
||||||
|
|
||||||
|
<!-- Method names as camelCase -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/methodname.html -->
|
||||||
|
<module name="MethodName"/>
|
||||||
|
|
||||||
|
<!-- Parameter names as camelCase -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/parametername.html -->
|
||||||
|
<module name="ParameterName"/>
|
||||||
|
|
||||||
|
<!-- Lokal variables as camelCase -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/localvariablename.html -->
|
||||||
|
<module name="LocalVariableName"/>
|
||||||
|
|
||||||
|
<!-- Constants as UPPER_SNAKE_CASE -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/constantname.html -->
|
||||||
|
<module name="ConstantName"/>
|
||||||
|
|
||||||
|
<!-- Field names as camelCase -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/naming/membername.html -->
|
||||||
|
<module name="MemberName">
|
||||||
|
<property name="format" value="^[a-z][a-zA-Z0-9]*$"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Restrict length of method name -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/sizes/methodlength.html -->
|
||||||
|
<module name="MethodLength">
|
||||||
|
<property name="max" value="40"/>
|
||||||
|
<property name="countEmpty" value="false"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Restrict number of parameters -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/sizes/parameternumber.html -->
|
||||||
|
<module name="ParameterNumber">
|
||||||
|
<property name="max" value="5"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Modifier-Reihenfolge: public static final ... -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/modifier/modifierorder.html -->
|
||||||
|
<module name="ModifierOrder"/>
|
||||||
|
|
||||||
|
<!-- Require braces arround code block (no single instruction after if/else/while/...)-->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/needbraces.html -->
|
||||||
|
<module name="NeedBraces"/>
|
||||||
|
|
||||||
|
<!-- Require the left curly braces at the same line -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/leftcurly.html -->
|
||||||
|
<module name="LeftCurly"/>
|
||||||
|
|
||||||
|
<!-- Require right curly braces at the same line as following instructions -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/rightcurly.html -->
|
||||||
|
<module name="RightCurly"/>
|
||||||
|
|
||||||
|
<!-- Disallow empty code blocks -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/emptyblock.html -->
|
||||||
|
<module name="EmptyBlock">
|
||||||
|
<property name="option" value="text"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Disallow empty code blocks after catch -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/emptycatchblock.html -->
|
||||||
|
<module name="EmptyCatchBlock">
|
||||||
|
<property name="exceptionVariableName" value="expected|ignored"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Require string literals to be compared with .equals and not == -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/coding/stringliteralequality.html -->
|
||||||
|
<module name="StringLiteralEquality"/>
|
||||||
|
|
||||||
|
<!-- Disallow standalone numbers in code -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/coding/magicnumber.html -->
|
||||||
|
<module name="MagicNumber">
|
||||||
|
<property name="ignoreNumbers" value="-1, 0, 1, 2"/>
|
||||||
|
<property name="ignoreAnnotation" value="true"/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Disallow nested code blocks dangling in code -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/blocks/avoidnestedblocks.html -->
|
||||||
|
<module name="AvoidNestedBlocks"/>
|
||||||
|
|
||||||
|
<!-- Disallow System.out.println -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html -->
|
||||||
|
<module name="Regexp">
|
||||||
|
<property name="id" value="SystemOutErr"/>
|
||||||
|
<property name="format" value="System\.(out|err)\.print"/>
|
||||||
|
<property name="illegalPattern" value="true"/>
|
||||||
|
<property name="message" value="No `System.out.print(err)` allowed — use a logger."/>
|
||||||
|
</module>
|
||||||
|
|
||||||
|
<!-- Disallow whitespace before semicolon or bracket -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/whitespace/nowhitespacebefore.html -->
|
||||||
|
<module name="NoWhitespaceBefore"/>
|
||||||
|
|
||||||
|
<!-- Disallow whitespace after code -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/whitespace/whitespaceafter.html -->
|
||||||
|
<module name="WhitespaceAfter"/>
|
||||||
|
|
||||||
|
<!-- No trailing whitespace at line ending -->
|
||||||
|
<!-- https://checkstyle.sourceforge.io/checks/regexp/regexp.html -->
|
||||||
|
<module name="Regexp">
|
||||||
|
<property name="format" value=" +$"/>
|
||||||
|
<property name="illegalPattern" value="true"/>
|
||||||
|
<property name="message" value="No trailing whitespace."/>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
|
</module>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE suppressions PUBLIC
|
||||||
|
"-//Checkstyle//DTD SuppressionFilter Configuration 1.2//EN"
|
||||||
|
"https://checkstyle.org/dtds/suppressions_1_2.dtd">
|
||||||
|
|
||||||
|
<suppressions>
|
||||||
|
<!-- Allow literal, for example assertEquals(42, life.answer()) -->
|
||||||
|
<suppress checks="MagicNumber" files=".*Test\.java"/>
|
||||||
|
|
||||||
|
<!-- Allow longer method names as the are usually descriptive of the test -->
|
||||||
|
<suppress checks="MethodLength" files=".*Test\.java"/>
|
||||||
|
|
||||||
|
<!-- Allow methods with more parameters -->
|
||||||
|
<suppress checks="ParameterNumber" files=".*Test\.java"/>
|
||||||
|
|
||||||
|
<!-- Allow for System.out.println in tests -->
|
||||||
|
<suppress id="SystemOutErr" files=".*Test\.java"/>
|
||||||
|
|
||||||
|
<!-- Allow wildcard imports (import x.*) -->
|
||||||
|
<suppress checks="AvoidStarImport" files=".*Test\.java"/>
|
||||||
|
|
||||||
|
<!-- Allow for compacter line seperators -->
|
||||||
|
<suppress checks="EmptyLineSeparator" files=".*Test\.java"/>
|
||||||
|
</suppressions>
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Server-Side Networking — How It All Fits Together
|
||||||
|
|
||||||
|
This doc is meant to give a solid mixed-level understanding of how our server-side networking works. No deep dives — just a clear picture of what's happening under the hood when a client connects and sends a command.
|
||||||
|
|
||||||
|
> **Note:** This is v1. Annotations and Reflection-based dispatching are on the roadmap but not covered here yet.
|
||||||
|
|
||||||
|
> **Disclamer:** This document has been written by Claude. I modified certain parts and verified its contents for correctness.
|
||||||
|
|
||||||
|
## Our Protocol at a Glance
|
||||||
|
|
||||||
|
Our protocol is inspired by **POP3** - a classic, text-based protocol that communicates over a raw TCP connection. The idea is simple: the client sends a command as a plain-text string, and the server responds with either a success or an error.
|
||||||
|
|
||||||
|
A typical exchange looks something like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
Client → GET_DELTA SINCE=42
|
||||||
|
Server → +OK
|
||||||
|
PLAYER_FOLDED playerId=3
|
||||||
|
POT 240
|
||||||
|
NEXT_TURN playerId=1
|
||||||
|
.
|
||||||
|
```
|
||||||
|
|
||||||
|
Responses start with `+OK` on success or `-ERR` when something goes wrong. Commands are short, uppercase strings - sometimes followed by arguments.
|
||||||
|
|
||||||
|
We don't use HTTP, there's no JSON body, no headers. Just a raw socket, a text stream, and a clearly defined set of commands.
|
||||||
|
|
||||||
|
## Core Components & Their Roles
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Here's a quick rundown of the main building blocks:
|
||||||
|
|
||||||
|
### `NetworkManager`
|
||||||
|
This is the entry point. It binds to a specific port and listens for incoming TCP connections.
|
||||||
|
Once a client connects, it creates a `Session` adds it to the `SessionManager` and goes back to waiting. Its not the responsibility of the `NetworkManager` to recieve and send data from and to each connected client.
|
||||||
|
|
||||||
|
### `Session`
|
||||||
|
Every client that connects has its own `Session` instance running on its own thread. This component owns the lifecycle of that connection: it reads incoming data, passes it along for processing, and writes responses back.
|
||||||
|
|
||||||
|
### `SessionId`
|
||||||
|
The `SessionId` is used to uniquely identify each session. Essentially its a wrapper arround the UUID type.
|
||||||
|
|
||||||
|
### `SessionManager`
|
||||||
|
The `SessionManager` stores all currently active sessions. It allows for the retrieval of a specific session by its id.
|
||||||
|
|
||||||
|
### `ProtocolParser`
|
||||||
|
Raw text (encapsulated in a `RawRequest`) coming off the socket isn't immediately useful — the `ProtocolParser` turns it with the help of the `Tokenizer` into a `PrimitiveRequest`.
|
||||||
|
|
||||||
|
### `RawRequest`
|
||||||
|
The `RawRequest` object is created after a message has been recieved.
|
||||||
|
It holds both a reference to the `Session` that recieved the message and the message itself.
|
||||||
|
|
||||||
|
### `PrimitiveRequest`
|
||||||
|
The `PrimitiveRequest` object is created by the `ProtocolParser` after the content of the `RawRequest` has been tokenized by the `Tokenizer`.
|
||||||
|
The command is stored in its own attribute and the arguments are accessible as a dictionary.
|
||||||
|
|
||||||
|
### `Tokenizer`
|
||||||
|
The job of the `tokenizer` is it, to take the raw string and turn it into tokens.
|
||||||
|
For example, the command `CHAT_LOBBY GAME=12 MESSAGE='All-in?'` will be decoded as `"CHAT_LOBBY", "GAME", "=", "12", "MESSAGE", "=", "All-in?"`.
|
||||||
|
It is unaware about the meaning.
|
||||||
|
|
||||||
|
### `CommandParser` (per command)
|
||||||
|
Takes in the `PrimitiveRequest` and checks if all required fields are provided with the correct value.
|
||||||
|
Creates a command specific `Request` object containing command arguments in a structured manner.
|
||||||
|
|
||||||
|
### `Request` (per command)
|
||||||
|
Has fields common along each implementation such as a reference to the `Session` that recieved the request.
|
||||||
|
Furthermore each implementation has fields unique to each command. For example, the `ChatLobbyRequest` has fields for the game and the message.
|
||||||
|
|
||||||
|
### `CommandRouter`
|
||||||
|
By identifying the type of class of the `Request`, the `CommandRouter` routes the request to the matching `CommandHandler`.
|
||||||
|
|
||||||
|
### `CommandHandler` (per command)
|
||||||
|
Each supported command has its own handler — a small, focused class that contains the logic for that specific command. They have access to the domain, containing inner parts of the game itself.
|
||||||
|
|
||||||
|
### `Response` (per response)
|
||||||
|
The interface has two sub-interfaces for either failed `ErrorResponse` or successfull `SuccessResponse` execution of commands.
|
||||||
|
|
||||||
|
Each step has exactly one responsibility. Increasing the ability to test and extend different components later on.
|
||||||
|
|
||||||
|
## Concurrency - Handling Multiple Clients
|
||||||
|
|
||||||
|
Every incoming connection spawns a new **Thread**. This means multiple clients can be served simultaneously without blocking each other.
|
||||||
|
|
||||||
|
As each `Session` runs entirely on its own thread, there's no shared mutable state between sessions, with exception of the `SessionManager` and other key components explained later.
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
@startuml
|
||||||
|
skinparam classAttributeIconSize 0
|
||||||
|
skinparam packageStyle rectangle
|
||||||
|
skinparam linetype ortho
|
||||||
|
skinparam backgroundColor transparent
|
||||||
|
|
||||||
|
' Network Layer
|
||||||
|
class NetworkManager {
|
||||||
|
+ start(port: int)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Session {
|
||||||
|
- id: SessionId
|
||||||
|
+ Session(id: SessionId)
|
||||||
|
+ getId(): SessionId
|
||||||
|
+ run()
|
||||||
|
+ send(response: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionId {
|
||||||
|
- value: UUID
|
||||||
|
+ SessionId()
|
||||||
|
+ SessionId(value: UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SessionManager {
|
||||||
|
- sessions: Map<UUID, Session>
|
||||||
|
+ add(session: Session)
|
||||||
|
+ getById(id: UUID): Session
|
||||||
|
+ remove(id: UUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
NetworkManager --> Session : creates
|
||||||
|
NetworkManager --> SessionManager : adds session to
|
||||||
|
Session --> SessionId : identified by
|
||||||
|
Session --> RawRequest : creates
|
||||||
|
Session ..> Response : sends
|
||||||
|
SessionManager o-- Session : manages
|
||||||
|
|
||||||
|
' Protocol Layer
|
||||||
|
class RawRequest {
|
||||||
|
- session: Session
|
||||||
|
- rawMessage: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class Tokenizer {
|
||||||
|
+ tokenize(input: String): List<String>
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProtocolParser {
|
||||||
|
+ parse(raw: RawRequest): PrimitiveRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrimitiveRequest {
|
||||||
|
- command: String
|
||||||
|
- arguments: Map<String, String>
|
||||||
|
}
|
||||||
|
|
||||||
|
RawRequest --> ProtocolParser : passed to
|
||||||
|
ProtocolParser --> Tokenizer : uses
|
||||||
|
ProtocolParser --> PrimitiveRequest : produces
|
||||||
|
|
||||||
|
' Command Layer
|
||||||
|
interface CommandParser <<per command>> {
|
||||||
|
+ parse(primitive: PrimitiveRequest): Request
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Request <<per command>> {
|
||||||
|
- session: Session
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandRouter {
|
||||||
|
+ route(request: Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CommandHandler <<per command>> {
|
||||||
|
+ handle(request: Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimitiveRequest --> CommandParser : passed to
|
||||||
|
CommandParser --> Request : produces
|
||||||
|
CommandRouter --> CommandHandler : dispatches to
|
||||||
|
CommandRouter ..> Request : receives
|
||||||
|
|
||||||
|
' Response Layer
|
||||||
|
interface Response {
|
||||||
|
+ encode(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SuccessResponse
|
||||||
|
|
||||||
|
interface ErrorResponse
|
||||||
|
|
||||||
|
Response <|-- SuccessResponse
|
||||||
|
Response <|-- ErrorResponse
|
||||||
|
@enduml
|
||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
|
|||||||
|
# Skript to convert findings of checkstyle into code quality report for gitlab
|
||||||
|
import xml.etree.ElementTree as ET, json, hashlib
|
||||||
|
|
||||||
|
findings = []
|
||||||
|
for source in ["main", "test"]:
|
||||||
|
try:
|
||||||
|
tree = ET.parse(f"build/reports/checkstyle/{source}.xml")
|
||||||
|
for file in tree.findall("file"):
|
||||||
|
path = file.get("name")
|
||||||
|
for error in file.findall("error"):
|
||||||
|
line = int(error.get("line", 1))
|
||||||
|
msg = error.get("message", "")
|
||||||
|
fingerprint = hashlib.md5(f"{path}{line}{msg}".encode()).hexdigest()
|
||||||
|
findings.append({
|
||||||
|
"type": "issue",
|
||||||
|
"check_name": error.get("source", "checkstyle"),
|
||||||
|
"description": msg,
|
||||||
|
"severity": "minor",
|
||||||
|
"fingerprint": fingerprint,
|
||||||
|
"location": {
|
||||||
|
"path": "src/" + path.split("/src/")[1] if "/src/" in path else path,
|
||||||
|
"lines": {"begin": line}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
with open("gl-code-quality-report.json", "w") as f:
|
||||||
|
json.dump(findings, f)
|
||||||
@@ -3,8 +3,25 @@ package ch.unibas.dmi.dbis.cs108.casono;
|
|||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ClientApp;
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
|
import ch.unibas.dmi.dbis.cs108.casono.server.ServerApp;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for Casono application.
|
||||||
|
* Handles client and server startup.
|
||||||
|
* <p>
|
||||||
|
* Standardkonstruktor für die Anwendung.
|
||||||
|
*/
|
||||||
public final class Main {
|
public final class Main {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardkonstruktor.
|
||||||
|
*/
|
||||||
|
public Main() {
|
||||||
|
// Standardkonstruktor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for Casono.
|
||||||
|
* @param args Command line arguments
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
if (!isValid(args)) {
|
if (!isValid(args)) {
|
||||||
printUsage();
|
printUsage();
|
||||||
|
|||||||
@@ -1,6 +1,29 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client;
|
package ch.unibas.dmi.dbis.cs108.casono.client;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry point for the Casono client application.
|
||||||
|
* Handles client startup and connection parameters.
|
||||||
|
*/
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher;
|
import ch.unibas.dmi.dbis.cs108.casono.client.ui.Launcher;
|
||||||
|
/**
|
||||||
|
* Entry point for the Casono client application.
|
||||||
|
* Handles client startup and connection parameters.
|
||||||
|
* <p>
|
||||||
|
* Standardkonstruktor für die Anwendung.
|
||||||
|
*/
|
||||||
public class ClientApp {
|
public class ClientApp {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardkonstruktor.
|
||||||
|
*/
|
||||||
|
public ClientApp() {
|
||||||
|
// Standardkonstruktor
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Starts the client application with the given address.
|
||||||
|
* @param arg Address in the format "ip:port".
|
||||||
|
* @throws IllegalArgumentException if the address format is invalid.
|
||||||
|
*/
|
||||||
public static void start(String arg) {
|
public static void start(String arg) {
|
||||||
String[] parts = arg.split(":", 2);
|
String[] parts = arg.split(":", 2);
|
||||||
if (parts.length != 2) {
|
if (parts.length != 2) {
|
||||||
|
|||||||
@@ -1,22 +1,26 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui;
|
||||||
|
|
||||||
import ch.unibas.dmi.dbis.cs108.casono.client.ui.gameui.CasinoGameUI;
|
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Launcher-Klasse für die Casino-Anwendung.
|
* Launcher for the Casono main UI.
|
||||||
*
|
* <p>
|
||||||
* Startet die JavaFX-Anwendung. Während der Entwicklung kann hier direkt
|
* Standardkonstruktor für die Anwendung.
|
||||||
* `Casinogameui` für Tests aufgerufen werden. Im fertigen Spiel erfolgt
|
|
||||||
* der Aufruf von `Casinogameui` über die `Casinomainui`.
|
|
||||||
*
|
|
||||||
* Starte den Client über das Terminal mit:
|
|
||||||
* {@code ./gradlew run --args="client 0.0.0.0:1234"}
|
|
||||||
*/
|
*/
|
||||||
public class Launcher {
|
public class Launcher {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardkonstruktor.
|
||||||
|
*/
|
||||||
|
public Launcher() {
|
||||||
|
// Standardkonstruktor
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Main entry point for launching the UI.
|
||||||
|
* @param args Command line arguments
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
// Application.launch(ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui.class, args);
|
Application.launch(ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui.Casinomainui.class, args);
|
||||||
Application.launch(CasinoGameUI.class, args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main UI application for Casono.
|
||||||
|
* Loads the main FXML layout and sets up the stage.
|
||||||
|
*/
|
||||||
import javafx.application.Application;
|
import javafx.application.Application;
|
||||||
import javafx.fxml.FXMLLoader;
|
import javafx.fxml.FXMLLoader;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
@@ -7,8 +11,25 @@ import javafx.stage.Stage;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JavaFX Application class for the Casono main UI.
|
||||||
|
* <p>
|
||||||
|
* Standardkonstruktor für die Anwendung.
|
||||||
|
*/
|
||||||
public class Casinomainui extends Application {
|
public class Casinomainui extends Application {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standardkonstruktor.
|
||||||
|
*/
|
||||||
|
public Casinomainui() {
|
||||||
|
// Standardkonstruktor
|
||||||
|
}
|
||||||
@Override
|
@Override
|
||||||
|
/**
|
||||||
|
* Starts the JavaFX application and loads the main UI.
|
||||||
|
* @param stage The primary stage for this application.
|
||||||
|
* @throws IOException If loading the FXML fails.
|
||||||
|
*/
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml"));
|
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/ui-structure/Casinomainui.fxml"));
|
||||||
Scene scene = new Scene(fxmlLoader.load(), 1200, 800);
|
Scene scene = new Scene(fxmlLoader.load(), 1200, 800);
|
||||||
@@ -20,6 +41,10 @@ public class Casinomainui extends Application {
|
|||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point for launching the application.
|
||||||
|
* @param args Command line arguments
|
||||||
|
*/
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch();
|
launch();
|
||||||
}
|
}
|
||||||
|
|||||||
+58
-22
@@ -1,22 +1,21 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.image.ImageView;
|
|
||||||
import javafx.scene.shape.Rectangle;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.scene.layout.AnchorPane;
|
|
||||||
|
|
||||||
|
import javafx.application.Platform;
|
||||||
|
import javafx.fxml.FXML;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.AnchorPane;
|
||||||
|
import javafx.scene.layout.VBox;
|
||||||
|
import javafx.scene.shape.Rectangle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controller for the Casono main UI lobby.
|
||||||
|
* Handles UI initialization and user actions.
|
||||||
|
*/
|
||||||
public class CasinomainuiController {
|
public class CasinomainuiController {
|
||||||
|
|
||||||
@FXML
|
@FXML
|
||||||
private AnchorPane rootPane;
|
private AnchorPane rootPane;
|
||||||
@FXML
|
@FXML
|
||||||
@@ -24,26 +23,63 @@ public class CasinomainuiController {
|
|||||||
@FXML
|
@FXML
|
||||||
private Label subtitleLabel;
|
private Label subtitleLabel;
|
||||||
@FXML
|
@FXML
|
||||||
private javafx.scene.image.ImageView logoView;
|
private ImageView logoView;
|
||||||
@FXML
|
@FXML
|
||||||
private javafx.scene.shape.Rectangle greenBox;
|
private Rectangle greenBox;
|
||||||
@FXML
|
@FXML
|
||||||
private Button exitbutton;
|
private Button exitbutton;
|
||||||
|
@FXML
|
||||||
|
private VBox casinoTable;
|
||||||
|
|
||||||
|
private LobbyButtonTranslationManager translationManager;
|
||||||
|
private LobbyButtonGridManager gridManager;
|
||||||
|
private int nextButtonId = 1;
|
||||||
|
|
||||||
|
public CasinomainuiController() {
|
||||||
|
// Default constructor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the UI components and sets default values.
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
public void initialize() {
|
public void initialize() {
|
||||||
titleLabel.setText("Casono");
|
titleLabel.setText("Casono");
|
||||||
subtitleLabel.setText("Texas Hold'em Poker");
|
subtitleLabel.setText("Texas Hold'em Poker");
|
||||||
// Logo laden
|
logoView.setImage(new Image(getClass().getResource("/images/logo.png").toExternalForm()));
|
||||||
logoView.setImage(new javafx.scene.image.Image(getClass().getResource("/images/logo.png").toExternalForm()));
|
|
||||||
|
translationManager = new LobbyButtonTranslationManager();
|
||||||
|
gridManager = new LobbyButtonGridManager(new javafx.scene.layout.GridPane(), translationManager);
|
||||||
|
casinoTable.getChildren().clear();
|
||||||
|
casinoTable.getChildren().add(gridManager.getGridPane());
|
||||||
|
gridManager.renderLobbyButtons();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the exit button action to close the application.
|
||||||
|
*/
|
||||||
@FXML
|
@FXML
|
||||||
public void handleexitbutton() {
|
public void handleexitbutton() {
|
||||||
Platform.exit();
|
Platform.exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles creation of a new lobby button.
|
||||||
|
*/
|
||||||
|
@FXML
|
||||||
|
public void handleCreateLobbyButton() {
|
||||||
|
if (translationManager.isFull()) {
|
||||||
|
System.out.println("Grid voll! Keine weiteren Lobbys moeglich.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int buttonId = nextButtonId++;
|
||||||
|
int lobbyId = gridManager.createLobby();
|
||||||
|
try {
|
||||||
|
translationManager.addLobbyButton(buttonId, lobbyId);
|
||||||
|
System.out.println("ButtonID: " + buttonId + ", LobbyID: " + lobbyId);
|
||||||
|
gridManager.renderLobbyButtons();
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.out.println("Fehler beim Hinzufügen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the grid for lobby buttons and rendering.
|
||||||
|
* Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.image.ImageView;
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the grid for lobby buttons and rendering.
|
||||||
|
* Uses LobbyButtonTranslationManager for mapping ButtonID to LobbyID.
|
||||||
|
*/
|
||||||
|
public class LobbyButtonGridManager {
|
||||||
|
|
||||||
|
/** GridPane for the button grid. */
|
||||||
|
private final GridPane gridPane;
|
||||||
|
/** Manager for mapping ButtonID to LobbyID. */
|
||||||
|
private final LobbyButtonTranslationManager translationManager;
|
||||||
|
/** Number of rows in the grid. */
|
||||||
|
private final int rows = 2;
|
||||||
|
/** Number of columns in the grid. */
|
||||||
|
private final int cols = 4;
|
||||||
|
/** Path to the button image. */
|
||||||
|
private final String buttonImagePath = "/images/logo.png";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for the GridManager.
|
||||||
|
*
|
||||||
|
* @param gridPane the GridPane for rendering
|
||||||
|
* @param translationManager the manager for mapping ButtonID to LobbyID
|
||||||
|
*/
|
||||||
|
public LobbyButtonGridManager(GridPane gridPane, LobbyButtonTranslationManager translationManager) {
|
||||||
|
this.gridPane = gridPane;
|
||||||
|
this.translationManager = translationManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders all lobby buttons in the grid.
|
||||||
|
* Creates a button for each mapping with image and event handler.
|
||||||
|
*/
|
||||||
|
public void renderLobbyButtons() {
|
||||||
|
gridPane.getChildren().clear();
|
||||||
|
int index = 0;
|
||||||
|
Map<Integer, Integer> mapping = translationManager.getButtonIdToLobbyId();
|
||||||
|
if (mapping.isEmpty()) {
|
||||||
|
// No buttons to render
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<Integer, Integer> entry : mapping.entrySet()) {
|
||||||
|
int buttonId = entry.getKey();
|
||||||
|
Button btn = new Button();
|
||||||
|
btn.setId("lobbyBtn-" + buttonId);
|
||||||
|
btn.setGraphic(new ImageView(new Image(getClass().getResourceAsStream(buttonImagePath))));
|
||||||
|
btn.setOnAction(e -> {
|
||||||
|
Integer lobbyId = translationManager.getLobbyIdForButton(buttonId);
|
||||||
|
if (lobbyId != null)
|
||||||
|
joinLobby(lobbyId);
|
||||||
|
});
|
||||||
|
int row = index / cols;
|
||||||
|
int col = index % cols;
|
||||||
|
gridPane.add(btn, col, row);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for lobby creation logic. Returns a generated lobbyId.
|
||||||
|
*
|
||||||
|
* @return The generated lobbyId
|
||||||
|
*/
|
||||||
|
public int createLobby() {
|
||||||
|
// TODO: Replace with actual lobby creation logic
|
||||||
|
int lobbyId = (int) (Math.random() * 10000 + 1);
|
||||||
|
System.out.println("Lobby created: " + lobbyId);
|
||||||
|
return lobbyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for joining a lobby.
|
||||||
|
*
|
||||||
|
* @param lobbyId The lobbyId to join
|
||||||
|
*/
|
||||||
|
public void joinLobby(int lobbyId) {
|
||||||
|
// TODO: Replace with actual join logic
|
||||||
|
System.out.println("Joining lobby: " + lobbyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Getter for the GridPane.
|
||||||
|
*
|
||||||
|
* @return The GridPane for the button grid
|
||||||
|
*/
|
||||||
|
public javafx.scene.layout.GridPane getGridPane() {
|
||||||
|
return gridPane;
|
||||||
|
}
|
||||||
|
}
|
||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verwaltet das Mapping zwischen Button-IDs und Lobby-IDs rein im Speicher.
|
||||||
|
* Keine Dateioperationen, nur Laufzeitdatenstruktur.
|
||||||
|
*/
|
||||||
|
public class LobbyButtonTranslationManager {
|
||||||
|
/** Maximale Anzahl an Buttons/Lobbys */
|
||||||
|
private static final int MAX_BUTTONS = 8;
|
||||||
|
/** Zuordnung ButtonID → LobbyID */
|
||||||
|
private final Map<Integer, Integer> buttonIdToLobbyId = new HashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Konstruktor: initialisiert die Zuordnung leer.
|
||||||
|
*/
|
||||||
|
public LobbyButtonTranslationManager() {
|
||||||
|
// Zuordnung bleibt leer beim Start
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft, ob das Grid voll ist (MAX_BUTTONS erreicht).
|
||||||
|
* @return true, wenn Grid voll; sonst false
|
||||||
|
*/
|
||||||
|
public boolean isFull() {
|
||||||
|
return buttonIdToLobbyId.size() >= MAX_BUTTONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fügt eine Zuordnung ButtonID → LobbyID hinzu.
|
||||||
|
* @param buttonId Die ID des Buttons
|
||||||
|
* @param lobbyId Die ID der Lobby
|
||||||
|
* @throws Exception wenn das Grid voll ist
|
||||||
|
*/
|
||||||
|
public void addLobbyButton(int buttonId, int lobbyId) throws Exception {
|
||||||
|
if (isFull()) throw new Exception("Grid is full!");
|
||||||
|
buttonIdToLobbyId.put(buttonId, lobbyId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entfernt eine Zuordnung für die gegebene ButtonID.
|
||||||
|
* @param buttonId Die ID des zu entfernenden Buttons
|
||||||
|
*/
|
||||||
|
public void removeLobbyButton(int buttonId) {
|
||||||
|
buttonIdToLobbyId.remove(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die LobbyID für eine gegebene ButtonID zurück.
|
||||||
|
* @param buttonId Die ButtonID
|
||||||
|
* @return Die zugehoerige LobbyID oder null, falls nicht vorhanden
|
||||||
|
*/
|
||||||
|
public Integer getLobbyIdForButton(int buttonId) {
|
||||||
|
return buttonIdToLobbyId.get(buttonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gibt die gesamte Zuordnung ButtonID → LobbyID zurück.
|
||||||
|
* @return Map aller Zuordnungen
|
||||||
|
*/
|
||||||
|
public Map<Integer, Integer> getButtonIdToLobbyId() {
|
||||||
|
return buttonIdToLobbyId;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,22 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.casono.server;
|
package ch.unibas.dmi.dbis.cs108.casono.server;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.NetworkManager;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application class for starting the server.
|
||||||
|
*/
|
||||||
public class ServerApp {
|
public class ServerApp {
|
||||||
public static void start(String arg) {
|
public static void start(String arg) {
|
||||||
int port = Integer.parseInt(arg);
|
int port = Integer.parseInt(arg);
|
||||||
System.out.println("You've selected the server. It will accept connections at port " + port);
|
System.out.println("You've selected the server. It will accept connections at port " + port);
|
||||||
|
|
||||||
|
EventBus eventBus = new EventBus();
|
||||||
|
SessionManager sessionManager = new SessionManager();
|
||||||
|
eventBus.subscribe(DisconnectEvent.class, event -> sessionManager.removeSession(event.sessionId()));
|
||||||
|
NetworkManager networkManager = new NetworkManager(port, sessionManager, eventBus);
|
||||||
|
networkManager.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ServerSocket;
|
||||||
|
import java.net.Socket;
|
||||||
|
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.Session;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionManager;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TcpTransport;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and manages the server socket. Accepts new incoming connections and creates sessions.
|
||||||
|
*/
|
||||||
|
public class NetworkManager implements Runnable {
|
||||||
|
private Integer port;
|
||||||
|
private Logger logger;
|
||||||
|
private Thread thread;
|
||||||
|
private Boolean running;
|
||||||
|
private SessionManager sessionManager;
|
||||||
|
private EventBus eventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new NetworkManager with the given port, session manager, and event bus.
|
||||||
|
*
|
||||||
|
* @param port the port to listen on
|
||||||
|
* @param sessionManager the session manager to use
|
||||||
|
* @param eventBus the event bus for events
|
||||||
|
*/
|
||||||
|
public NetworkManager(Integer port, SessionManager sessionManager, EventBus eventBus) {
|
||||||
|
this.port = port;
|
||||||
|
this.logger = LogManager.getLogger(NetworkManager.class);
|
||||||
|
this.thread = new Thread(this, "networkManager");
|
||||||
|
this.running = true;
|
||||||
|
this.sessionManager = sessionManager;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
this.eventBus.subscribe(DisconnectEvent.class, event -> clientDisconnected(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the internal thread to accept new connections.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
logger.debug("Starting server at port " + port);
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles client disconnection events.
|
||||||
|
*
|
||||||
|
* @param event the disconnect event
|
||||||
|
*/
|
||||||
|
public void clientDisconnected(DisconnectEvent event) {
|
||||||
|
logger.info("Session " + event.sessionId().value() + " disconnected adhasghd");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the network manager loop, accepting connections.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try (ServerSocket serverSocket = new ServerSocket(port)) {
|
||||||
|
while (running) {
|
||||||
|
Socket clientSocket = serverSocket.accept();
|
||||||
|
|
||||||
|
System.out.println("Accepted connection from " + clientSocket.getRemoteSocketAddress());
|
||||||
|
|
||||||
|
Session session = new Session(new TcpTransport(clientSocket), eventBus);
|
||||||
|
sessionManager.addSession(session);
|
||||||
|
session.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.fatal(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.events;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.sessions.SessionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a disconnect event for a session.
|
||||||
|
*/
|
||||||
|
public record DisconnectEvent(SessionId sessionId) implements Event {}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Marker interface for events in the event bus system.
|
||||||
|
*/
|
||||||
|
interface Event {}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.events;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple event bus for publishing and subscribing to events.
|
||||||
|
*/
|
||||||
|
public class EventBus {
|
||||||
|
private final Map<Class<?>, List<Consumer<Object>>> handlers = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscribes a handler to a specific event type.
|
||||||
|
*
|
||||||
|
* @param eventType the class of the event to subscribe to
|
||||||
|
* @param handler the consumer to handle the event
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked") // This cast is safe, because handlers only get passed the type they subscribed to
|
||||||
|
public <T extends Event> void subscribe(Class<T> eventType, Consumer<T> handler) {
|
||||||
|
handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
|
||||||
|
.add((Consumer<Object>) (Consumer<?>) handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Publishes an event to all subscribed handlers.
|
||||||
|
*
|
||||||
|
* @param event the event to publish
|
||||||
|
*/
|
||||||
|
public <T extends Event> void publish(T event) {
|
||||||
|
Objects.requireNonNull(event, "event must not be null");
|
||||||
|
List<Consumer<Object>> subscribers = handlers.get(event.getClass());
|
||||||
|
if (subscribers != null) {
|
||||||
|
subscribers.forEach(h -> h.accept(event));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.DisconnectEvent;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.events.EventBus;
|
||||||
|
import ch.unibas.dmi.dbis.cs108.casono.server.network.transport.TransportLayer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a client session in the network server.
|
||||||
|
*/
|
||||||
|
public class Session implements Runnable {
|
||||||
|
private SessionId id;
|
||||||
|
private Thread thread;
|
||||||
|
private TransportLayer transport;
|
||||||
|
private Logger logger;
|
||||||
|
private Boolean running;
|
||||||
|
private EventBus eventBus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new Session with the given transport and event bus.
|
||||||
|
*
|
||||||
|
* @param transport the transport layer for communication
|
||||||
|
* @param eventBus the event bus for publishing events
|
||||||
|
* @throws IOException if an I/O error occurs during initialization
|
||||||
|
*/
|
||||||
|
public Session(TransportLayer transport, EventBus eventBus) throws IOException {
|
||||||
|
this.id = new SessionId();
|
||||||
|
this.thread = new Thread(this, "session-" + this.id.value());
|
||||||
|
this.transport = transport;
|
||||||
|
this.running = true;
|
||||||
|
this.eventBus = eventBus;
|
||||||
|
|
||||||
|
this.logger = LogManager.getLogger(Session.class.toString() + id.value());
|
||||||
|
this.logger.info("Created new session");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the ID of this session.
|
||||||
|
*
|
||||||
|
* @return the session ID
|
||||||
|
*/
|
||||||
|
public SessionId getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts the session thread.
|
||||||
|
*/
|
||||||
|
public void start() {
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the session and its transport.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void close() throws IOException {
|
||||||
|
transport.close();
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the session loop, reading from the transport.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (running) {
|
||||||
|
try {
|
||||||
|
System.out.println("Recieved: " + transport.read());
|
||||||
|
} catch (EOFException e) {
|
||||||
|
logger.info("Client disconnected");
|
||||||
|
eventBus.publish(new DisconnectEvent(id));
|
||||||
|
break;
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a unique identifier for a session.
|
||||||
|
*/
|
||||||
|
public class SessionId {
|
||||||
|
private final UUID value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SessionId with a randomly generated UUID.
|
||||||
|
*/
|
||||||
|
public SessionId() {
|
||||||
|
this.value = UUID.randomUUID();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new SessionId with the specified UUID.
|
||||||
|
*
|
||||||
|
* @param UUID to use for this SessionId
|
||||||
|
*/
|
||||||
|
public SessionId(UUID value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the UUID value of this SessionId.
|
||||||
|
*
|
||||||
|
* @return the UUID value
|
||||||
|
*/
|
||||||
|
public UUID value() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
+59
@@ -0,0 +1,59 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.sessions;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages active sessions in the server.
|
||||||
|
*/
|
||||||
|
public class SessionManager {
|
||||||
|
private Map<SessionId, Session> sessions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new SessionManager.
|
||||||
|
*/
|
||||||
|
public SessionManager() {
|
||||||
|
this.sessions = new ConcurrentHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a session to the manager.
|
||||||
|
*
|
||||||
|
* @param session the session to add
|
||||||
|
*/
|
||||||
|
public void addSession(Session session) {
|
||||||
|
sessions.put(session.getId(), session);
|
||||||
|
System.out.println("Added session " + session.getId().value() + " to session manager");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a session by its ID.
|
||||||
|
*
|
||||||
|
* @param id the ID of the session to remove
|
||||||
|
* @return the removed session, or null if not found
|
||||||
|
*/
|
||||||
|
public Session removeSession(SessionId id) {
|
||||||
|
System.out.println("Removed session " + id.value() + " from session manager");
|
||||||
|
return sessions.remove(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes the specified session.
|
||||||
|
*
|
||||||
|
* @param session the session to remove
|
||||||
|
* @return the removed session, or null if not found
|
||||||
|
*/
|
||||||
|
public Session removeSession(Session session) {
|
||||||
|
return sessions.remove(session.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a session by its ID.
|
||||||
|
*
|
||||||
|
* @param id the ID of the session to retrieve
|
||||||
|
* @return the session with the specified ID, or null if not found
|
||||||
|
*/
|
||||||
|
public Session getSessionById(SessionId id) {
|
||||||
|
return sessions.get(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.transport;
|
||||||
|
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.DataOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements TCP-based transport layer for network communication.
|
||||||
|
*/
|
||||||
|
public class TcpTransport implements TransportLayer {
|
||||||
|
private Socket socket;
|
||||||
|
private DataInputStream in;
|
||||||
|
private DataOutputStream out;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new TcpTransport with the given socket.
|
||||||
|
*
|
||||||
|
* @param socket the socket to use for communication
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public TcpTransport(Socket socket) throws IOException {
|
||||||
|
this.socket = socket;
|
||||||
|
this.in = new DataInputStream(socket.getInputStream());
|
||||||
|
this.out = new DataOutputStream(socket.getOutputStream());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a string from the socket.
|
||||||
|
*
|
||||||
|
* @return the read string
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public String read() throws IOException {
|
||||||
|
int length = in.readInt();
|
||||||
|
byte[] payload = new byte[length];
|
||||||
|
in.readFully(payload);
|
||||||
|
return new String(payload, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a string to the socket.
|
||||||
|
*
|
||||||
|
* @param payload the string to write
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void write(String payload) throws IOException {
|
||||||
|
byte[] rawPayload = payload.getBytes(StandardCharsets.UTF_8);
|
||||||
|
out.writeInt(rawPayload.length);
|
||||||
|
out.write(rawPayload);
|
||||||
|
out.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the socket.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
public void close() throws IOException {
|
||||||
|
socket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.server.network.transport;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for transport layer implementations.
|
||||||
|
*/
|
||||||
|
public interface TransportLayer {
|
||||||
|
/**
|
||||||
|
* Reads data from the transport layer.
|
||||||
|
*
|
||||||
|
* @return the read data as a string
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
String read() throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes data to the transport layer.
|
||||||
|
*
|
||||||
|
* @param data the data to write
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
void write(String data) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes the transport layer.
|
||||||
|
*
|
||||||
|
* @throws IOException if an I/O error occurs
|
||||||
|
*/
|
||||||
|
void close() throws IOException;
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.example;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple HelloWorld class.
|
|
||||||
*/
|
|
||||||
public class HelloWorld {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
System.out.println("Hello World");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.example.gui.javafx;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
import javafx.scene.Scene;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.layout.StackPane;
|
|
||||||
import javafx.stage.Stage;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is an example JavaFX-Application.
|
|
||||||
*/
|
|
||||||
public class GUI extends Application {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Launching this method will not work on some platforms.
|
|
||||||
* What you should do is to create a separate main class and launch the GUI class from there as is done in {@link Main}
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
|
||||||
launch(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void start(Stage stage) {
|
|
||||||
String javaVersion = System.getProperty("java.version");
|
|
||||||
String javafxVersion = System.getProperty("javafx.version");
|
|
||||||
Label l = new Label("Hello, JavaFX " + javafxVersion + ", running on Java " + javaVersion + ".");
|
|
||||||
Scene scene = new Scene(new StackPane(l), 640, 480);
|
|
||||||
stage.setScene(scene);
|
|
||||||
stage.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.example.gui.javafx;
|
|
||||||
|
|
||||||
import javafx.application.Application;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is simply a wrapper to launch the {@link GUI} class.
|
|
||||||
* The reason this class exists is documented in {@link GUI#main(String[])}
|
|
||||||
*/
|
|
||||||
public static void main(String[] args) {
|
|
||||||
Application.launch(GUI.class, args);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Configuration status="warn">
|
||||||
|
|
||||||
|
<Appenders>
|
||||||
|
<Console name="Console" target="SYSTEM_OUT">
|
||||||
|
<PatternLayout disableAnsi="false" pattern=
|
||||||
|
"%style{%d{HH:mm:ss.SSS}}{bright,black} %highlight{%-8level}{
|
||||||
|
FATAL=bright red,
|
||||||
|
ERROR=red,
|
||||||
|
WARN=yellow,
|
||||||
|
INFO=blue,
|
||||||
|
DEBUG=cyan,
|
||||||
|
TRACE=white
|
||||||
|
} %style{%-20logger{20}}{magenta} %msg%n"
|
||||||
|
/>
|
||||||
|
</Console>
|
||||||
|
</Appenders>
|
||||||
|
|
||||||
|
<Loggers>
|
||||||
|
<Root level="debug">
|
||||||
|
<AppenderRef ref="Console"/>
|
||||||
|
</Root>
|
||||||
|
</Loggers>
|
||||||
|
|
||||||
|
</Configuration>
|
||||||
@@ -120,6 +120,7 @@
|
|||||||
-fx-padding: 0 0 40 0;
|
-fx-padding: 0 0 40 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* EXIT BUTTON */
|
||||||
.button-exit {
|
.button-exit {
|
||||||
-fx-background-radius: 12;
|
-fx-background-radius: 12;
|
||||||
-fx-border-radius: 12;
|
-fx-border-radius: 12;
|
||||||
@@ -128,17 +129,17 @@
|
|||||||
-fx-border-width: 2;
|
-fx-border-width: 2;
|
||||||
-fx-padding: 6 15;
|
-fx-padding: 6 15;
|
||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
|
-fx-background-color: #333333;
|
||||||
|
-fx-border-color: #666666;
|
||||||
|
-fx-text-fill: #CCCCCC;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-exit:hover {
|
.button-exit:hover {
|
||||||
-fx-translate-y: -3;
|
-fx-translate-y: -3;
|
||||||
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-exit {
|
/* LOBBY ERSTELLEN BUTTON */
|
||||||
-fx-background-color: #333333;
|
.button-create-lobby {
|
||||||
-fx-border-color: #666666;
|
|
||||||
-fx-text-fill: #CCCCCC;
|
|
||||||
-fx-background-radius: 12;
|
-fx-background-radius: 12;
|
||||||
-fx-border-radius: 12;
|
-fx-border-radius: 12;
|
||||||
-fx-font-family: "Courier New";
|
-fx-font-family: "Courier New";
|
||||||
@@ -146,6 +147,14 @@
|
|||||||
-fx-border-width: 2;
|
-fx-border-width: 2;
|
||||||
-fx-padding: 6 15;
|
-fx-padding: 6 15;
|
||||||
-fx-cursor: hand;
|
-fx-cursor: hand;
|
||||||
|
-fx-background-color: #1a7f2e;
|
||||||
|
-fx-border-color: #2ecc71;
|
||||||
|
-fx-text-fill: #ffffff;
|
||||||
|
}
|
||||||
|
.button-create-lobby:hover {
|
||||||
|
-fx-translate-y: -3;
|
||||||
|
-fx-effect: dropshadow(one-pass-box, rgba(0, 0, 0, 0.8), 0, 0, 3, 3);
|
||||||
|
-fx-background-color: #27ae60;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,18 @@
|
|||||||
<Insets top="20" left="20" />
|
<Insets top="20" left="20" />
|
||||||
</GridPane.margin>
|
</GridPane.margin>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button text="Lobby erstellen"
|
||||||
|
styleClass="button-create-lobby"
|
||||||
|
fx:id="createLobbyButton"
|
||||||
|
onAction="#handleCreateLobbyButton"
|
||||||
|
GridPane.rowIndex="0"
|
||||||
|
GridPane.columnIndex="0"
|
||||||
|
GridPane.halignment="LEFT"
|
||||||
|
GridPane.valignment="TOP">
|
||||||
|
<GridPane.margin>
|
||||||
|
<Insets top="60" left="20" />
|
||||||
|
</GridPane.margin>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<VBox fx:id="casinoTable"
|
<VBox fx:id="casinoTable"
|
||||||
alignment="CENTER"
|
alignment="CENTER"
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"1": 1240,
|
||||||
|
"2": 3640,
|
||||||
|
"3": 7589,
|
||||||
|
"4": 3982,
|
||||||
|
"5": 5655,
|
||||||
|
"6": 6418,
|
||||||
|
"7": 8586,
|
||||||
|
"8": 7378
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
|
import javafx.scene.layout.GridPane;
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class LobbyButtonGridManagerTest {
|
||||||
|
LobbyButtonGridManager gridManager;
|
||||||
|
LobbyButtonTranslationManager translationManager;
|
||||||
|
GridPane gridPane;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
gridPane = new GridPane();
|
||||||
|
translationManager = new LobbyButtonTranslationManager();
|
||||||
|
translationManager.getButtonIdToLobbyId().clear();
|
||||||
|
gridManager = new LobbyButtonGridManager(gridPane, translationManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateLobbyReturnsId() {
|
||||||
|
int lobbyId = gridManager.createLobby();
|
||||||
|
assertTrue(lobbyId > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJoinLobbyPlaceholder() {
|
||||||
|
// check if exception is thrown
|
||||||
|
assertDoesNotThrow(() -> gridManager.joinLobby(123));
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package ch.unibas.dmi.dbis.cs108.casono.client.ui.lobbyui;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.*;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
class LobbyButtonTranslationManagerTest {
|
||||||
|
LobbyButtonTranslationManager manager;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
manager = new LobbyButtonTranslationManager();
|
||||||
|
manager.getButtonIdToLobbyId().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddLobbyButton() throws Exception {
|
||||||
|
manager.addLobbyButton(1, 100);
|
||||||
|
assertEquals(100, manager.getLobbyIdForButton(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRemoveLobbyButton() throws Exception {
|
||||||
|
manager.addLobbyButton(2, 200);
|
||||||
|
manager.removeLobbyButton(2);
|
||||||
|
assertNull(manager.getLobbyIdForButton(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testIsFull() throws Exception {
|
||||||
|
for (int i = 1; i <= 8; i++) {
|
||||||
|
manager.addLobbyButton(i, 100 + i);
|
||||||
|
}
|
||||||
|
assertTrue(manager.isFull());
|
||||||
|
Exception ex = assertThrows(Exception.class, () -> manager.addLobbyButton(9, 109));
|
||||||
|
assertEquals("Grid is full!", ex.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetButtonIdToLobbyId() throws Exception {
|
||||||
|
manager.addLobbyButton(4, 400);
|
||||||
|
Map<Integer, Integer> map = manager.getButtonIdToLobbyId();
|
||||||
|
assertTrue(map.containsKey(4));
|
||||||
|
assertEquals(400, map.get(4));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package ch.unibas.dmi.dbis.cs108.example;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An example test class.
|
|
||||||
* Checks the output of the {@link HelloWorld} class and makes sure it contains "Hello World"
|
|
||||||
*/
|
|
||||||
public class HelloWorldTest {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Streams to store system.out and system.err content
|
|
||||||
*/
|
|
||||||
private ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
|
||||||
private ByteArrayOutputStream errStream = new ByteArrayOutputStream();
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Here we store the previous pointers to system.out / system.err
|
|
||||||
*/
|
|
||||||
private PrintStream outBackup;
|
|
||||||
private PrintStream errBackup;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is executed before each test.
|
|
||||||
* It redirects System.out and System.err to our variables {@link #outStream} and {@link #errStream}.
|
|
||||||
* This allows us to test their content later.
|
|
||||||
*/
|
|
||||||
@BeforeEach
|
|
||||||
public void redirectStdOutStdErr() {
|
|
||||||
outBackup = System.out;
|
|
||||||
errBackup = System.err;
|
|
||||||
System.setOut(new PrintStream(outStream));
|
|
||||||
System.setErr(new PrintStream(errStream));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method is run after each test.
|
|
||||||
* It redirects System.out / System.err back to the normal streams.
|
|
||||||
*/
|
|
||||||
@AfterEach
|
|
||||||
public void reestablishStdOutStdErr() {
|
|
||||||
System.setOut(outBackup);
|
|
||||||
System.setErr(errBackup);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a normal JUnit-Test. It executes the HelloWorld-Method and verifies that it actually wrote "Hello World" to stdout
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void testMain() {
|
|
||||||
HelloWorld.main(new String[0]);
|
|
||||||
String toTest = outStream.toString();
|
|
||||||
toTest = removeNewline(toTest);
|
|
||||||
assertTrue(toTest.contains("Hello World"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String removeNewline(String str) {
|
|
||||||
return str.replace("\n", "").replace("\r", "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user