Compare commits
	
		
			1 Commits
		
	
	
		
			96a78ee61b
			...
			revert-71a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9abf7350e4 | 
							
								
								
									
										26
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,26 +0,0 @@
 | 
			
		||||
# Gradle
 | 
			
		||||
.gradle
 | 
			
		||||
build
 | 
			
		||||
 | 
			
		||||
# VSC
 | 
			
		||||
bin
 | 
			
		||||
 | 
			
		||||
# IntelliJ
 | 
			
		||||
*.iml
 | 
			
		||||
.idea
 | 
			
		||||
out
 | 
			
		||||
 | 
			
		||||
# Eclipse
 | 
			
		||||
.classpath
 | 
			
		||||
.project
 | 
			
		||||
 | 
			
		||||
# Libraries
 | 
			
		||||
*.so
 | 
			
		||||
*.dylib
 | 
			
		||||
*.dll
 | 
			
		||||
*.jar
 | 
			
		||||
*.class
 | 
			
		||||
 | 
			
		||||
.DS_Store
 | 
			
		||||
!Projekte/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										1
									
								
								Dokumente/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1 +0,0 @@
 | 
			
		||||
#
 | 
			
		||||
							
								
								
									
										3
									
								
								Projekte/.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +0,0 @@
 | 
			
		||||
*.bat           text eol=crlf
 | 
			
		||||
gradlew         text eol=lf
 | 
			
		||||
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
    <configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application"
 | 
			
		||||
                   singleton="false">
 | 
			
		||||
        <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/>
 | 
			
		||||
        <module name="Projekte.battleship.client.main"/>
 | 
			
		||||
        <option name="VM_PARAMETERS" value="-XstartOnFirstThread"/>
 | 
			
		||||
        <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
			
		||||
        <extension name="coverage">
 | 
			
		||||
            <pattern>
 | 
			
		||||
                <option name="PATTERN" value="pp.battleship.client.*"/>
 | 
			
		||||
                <option name="ENABLED" value="true"/>
 | 
			
		||||
            </pattern>
 | 
			
		||||
        </extension>
 | 
			
		||||
        <method v="2">
 | 
			
		||||
            <option name="Make" enabled="true"/>
 | 
			
		||||
        </method>
 | 
			
		||||
    </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,18 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false"
 | 
			
		||||
                 nameIsGenerated="true">
 | 
			
		||||
    <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/>
 | 
			
		||||
    <module name="Projekte.battleship.client.main"/>
 | 
			
		||||
    <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
			
		||||
    <extension name="coverage">
 | 
			
		||||
      <pattern>
 | 
			
		||||
        <option name="PATTERN" value="pp.battleship.client.*"/>
 | 
			
		||||
        <option name="ENABLED" value="true"/>
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true"/>
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="BattleshipServer" type="Application" factoryName="Application"
 | 
			
		||||
                 nameIsGenerated="true">
 | 
			
		||||
    <option name="MAIN_CLASS_NAME" value="pp.battleship.server.BattleshipServer"/>
 | 
			
		||||
    <module name="Projekte.battleship.server.main"/>
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
			
		||||
    <extension name="coverage">
 | 
			
		||||
      <pattern>
 | 
			
		||||
        <option name="PATTERN" value="pp.battleship.server.*"/>
 | 
			
		||||
        <option name="ENABLED" value="true"/>
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true"/>
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,24 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="Projekte [test]" type="GradleRunConfiguration" factoryName="Gradle">
 | 
			
		||||
    <ExternalSystemSettings>
 | 
			
		||||
      <option name="executionName" />
 | 
			
		||||
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
			
		||||
      <option name="externalSystemIdString" value="GRADLE" />
 | 
			
		||||
      <option name="scriptParameters" value="--continue" />
 | 
			
		||||
      <option name="taskDescriptions">
 | 
			
		||||
        <list />
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="taskNames">
 | 
			
		||||
        <list>
 | 
			
		||||
          <option value="test" />
 | 
			
		||||
        </list>
 | 
			
		||||
      </option>
 | 
			
		||||
      <option name="vmOptions" />
 | 
			
		||||
    </ExternalSystemSettings>
 | 
			
		||||
    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
 | 
			
		||||
    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
 | 
			
		||||
    <DebugAllEnabled>false</DebugAllEnabled>
 | 
			
		||||
    <RunAsTest>false</RunAsTest>
 | 
			
		||||
    <method v="2" />
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
@@ -1,189 +0,0 @@
 | 
			
		||||
# Beispielprogramme des Programmierprojekts
 | 
			
		||||
 | 
			
		||||
Hier ist der Quellcode für das in der Einarbeitungsphase genutzte Spiel
 | 
			
		||||
_Battleships_ zu finden. Die Quellen bestehen aus den folgenden
 | 
			
		||||
Gradle-Unterprojekten:
 | 
			
		||||
 | 
			
		||||
* _:battleship:server_
 | 
			
		||||
* _:battleship:client_
 | 
			
		||||
* _:battleship:model_
 | 
			
		||||
* _:battleship:converter_
 | 
			
		||||
* _:common_
 | 
			
		||||
* _:jme-common_
 | 
			
		||||
 | 
			
		||||
_Battleships_ ist ein netzwerkbasiertes Spiel und besteht aus einem Server- und
 | 
			
		||||
einem Clientanteil, die in den Unterprojekten _:battleship:server_ und
 | 
			
		||||
_:battleship:client_ realisiert sind. Beide nutzen das Unterprojekt
 | 
			
		||||
_:battleship:model_, das den gemeinsamen Modellanteil enthält.
 | 
			
		||||
 | 
			
		||||
Die Unterprojekte _:common_ und _:jme-common_ enthalten Hilfsklassen.
 | 
			
		||||
 | 
			
		||||
Das Unterprojekt _:battleship:converter_ wird für _Battleships_ selbst nicht
 | 
			
		||||
benötigt, sondern enthält lediglich den Code, um ein im Spiel verwendetes
 | 
			
		||||
3d-Modell eines Schlachtschiffs in eine _J3O_-Datei umzuwandeln, die von jME
 | 
			
		||||
einfacher geladen werden kann.
 | 
			
		||||
 | 
			
		||||
## 1 Vorbereitung
 | 
			
		||||
 | 
			
		||||
Für das Programmierprojekt empfehlen wir die Verwendung von Java 20. Unter Linux
 | 
			
		||||
sollte [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20)
 | 
			
		||||
als JDK verwendet werden, andere JDKs können unter Linux Probleme verursachen.
 | 
			
		||||
Auf anderen Betriebssystemen empfehlen wir aber ebenfalls Temurin. Im Folgenden
 | 
			
		||||
ist beschrieben, wie Sie Temurin installieren und die Umgebungsvariable
 | 
			
		||||
**JAVA_HOME** richtig setzen, damit Sie Gradle (siehe unten) verwenden können.
 | 
			
		||||
 | 
			
		||||
### 1.1 Installation von Temurin
 | 
			
		||||
 | 
			
		||||
Laden Sie [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20)
 | 
			
		||||
entsprechend Ihrem Betriebssystem und Ihrer Prozessorarchitektur herunter und
 | 
			
		||||
entpacken Sie das Archiv in einem Verzeichnis Ihrer Wahl auf Ihrem Rechner.
 | 
			
		||||
 | 
			
		||||
### 1.2 Setzen von JAVA_HOME
 | 
			
		||||
 | 
			
		||||
Zur Verwendung mit Gradle muss die Umgebungsvariable **JAVA_HOME** richtig
 | 
			
		||||
gesetzt werden. Folgen Sie dazu den nachfolgenden Anweisungen entsprechend Ihrem
 | 
			
		||||
Betriebssystem:
 | 
			
		||||
 | 
			
		||||
* **Windows:**
 | 
			
		||||
 | 
			
		||||
  Öffnen Sie ihre Powershell (Core) bzw. ihr Windows Terminal mit Powershell
 | 
			
		||||
  (Core). Überprüfen Sie, ob die Umgebungsvariable korrekt gesetzt ist:  
 | 
			
		||||
  `Get-ChildItem -Path Env:JAVA_HOME`  
 | 
			
		||||
  Falls kein oder ein falscher Pfad gesetzt ist, setzen Sie diesen mit dem
 | 
			
		||||
  folgenden Kommando (in einer Zeile):  
 | 
			
		||||
  `[System.Environment]::SetEnvironmentVariable('JAVA_HOME','<Pfad zum SDK>',[System.EnvironmentVariableTarget]::User)`
 | 
			
		||||
 | 
			
		||||
  Alternativ können Sie die GUI verwenden. Unter Windows 10 klicken Sie die
 | 
			
		||||
  Windows-Taste und dann das Zahnrad um die Einstellungen zu öffnen. Dort wählen
 | 
			
		||||
  Sie "System", dann "Info" (links unten) und nun
 | 
			
		||||
  "Erweiterte Systemeinstellungen" (rechts) um den Dialog "Systemeigenschaften"
 | 
			
		||||
  zu starten. Im Reiter "Erweitert" klicken Sie
 | 
			
		||||
  "Umgebungsvariablen..." und klicken dann unter "Benutzervariablen" den Knopf
 | 
			
		||||
  "Neu..." um JAVA_HOME anzulegen oder "Bearbeiten" um ihn zu ändern. Geben Sie
 | 
			
		||||
  als Name `JAVA_HOME` und als Wert den Pfad ein. Schließen Sie mit "OK".
 | 
			
		||||
 | 
			
		||||
  > **(!) Beachten Sie, dass Sie die jeweilige Applikation neu starten müssen**,
 | 
			
		||||
  > um von der gesetzten Umgebungsvariablen Notiz zu nehmen.
 | 
			
		||||
  > Dies betrifft auch die Shell, die Sie gerade verwenden.
 | 
			
		||||
 | 
			
		||||
* **UNIX (Linux/MacOS):**
 | 
			
		||||
 | 
			
		||||
  Öffnen oder erstellen Sie die Datei `~/.profile` (wenn Sie die Bash verwenden;
 | 
			
		||||
  bei anderen Shells sind es andere Dateien) und ergänzen Sie am Ende der Datei
 | 
			
		||||
  die Zeile:
 | 
			
		||||
 | 
			
		||||
  `export JAVA_HOME="<Pfad zum entpackten Archiv>"`
 | 
			
		||||
 | 
			
		||||
  Ersetzen Sie dabei `<Pfad zum entpackten Archiv>` mit dem entsprechenden Pfad.
 | 
			
		||||
  Zum Beispiel:
 | 
			
		||||
 | 
			
		||||
  `export JAVA_HOME="/home/user/jdk-20.0.2"`
 | 
			
		||||
 | 
			
		||||
  Fügen Sie dann die folgende Zeile hinzu:
 | 
			
		||||
 | 
			
		||||
  `export PATH="$JAVA_HOME/bin:$PATH"`
 | 
			
		||||
 | 
			
		||||
## 2 Programmstart
 | 
			
		||||
 | 
			
		||||
Grundsätzlich kann man das gesamte Projekt einfach in IntelliJ öffnen. Details
 | 
			
		||||
dazu sind im Aufgabenblatt zur Einarbeitungsaufgabe zu finden. Im Folgenden ist
 | 
			
		||||
beschrieben, wie die _Batttleships_ unmittelbar von der Kommandozeile gestartet
 | 
			
		||||
werden können.
 | 
			
		||||
 | 
			
		||||
Um _Battleships_ spielen zu können, muss man zuerst das Server-Programm auf
 | 
			
		||||
einem Rechner und dann zweimal das Client-Programm auf beliebigen Rechnern
 | 
			
		||||
starten, die TCP/IP-Verbindungen zum Server erlauben. Natürlich ist es auch
 | 
			
		||||
möglich, alle drei Programme auf demselben Rechner zu starten.
 | 
			
		||||
 | 
			
		||||
Es empfiehlt sich der Start von der Kommandozeile. Will man alle drei Programme
 | 
			
		||||
auf demselben Rechner starten, sollte man dazu drei Shell-Instanzen öffnen und
 | 
			
		||||
in jeder eines der Programme starten. Auf diese Weise können die
 | 
			
		||||
Logging-Ausgaben der drei Programme voneinander unterschieden werden.
 | 
			
		||||
 | 
			
		||||
Das Server-Programm startet man unmittelbar mit Gradle mit
 | 
			
		||||
 | 
			
		||||
`./gradlew :battleship:server:run`
 | 
			
		||||
 | 
			
		||||
Unter Windows kann es je nach Shell (Eingabeaufforderung cmd) erforderlich sein,
 | 
			
		||||
`/` jeweils durch `\ ` zu ersetzen.
 | 
			
		||||
 | 
			
		||||
Im Verzeichnis `battleship/server` befindet sich die Datei `config.propeties`,
 | 
			
		||||
worüber sich der Server konfigurieren lässt. Mit der Zeile `port=1234` lässt
 | 
			
		||||
sich der verwendete Server-Port (hier 1234) einstellen. Außerdem befindet sich
 | 
			
		||||
dort die Datei `logging.properties`, womit das Logging des Servers konfiguriert
 | 
			
		||||
wird.
 | 
			
		||||
 | 
			
		||||
Das Client-Programm startet man unmittelbar mit Gradle mit
 | 
			
		||||
 | 
			
		||||
`./gradlew :battleship:client:run`
 | 
			
		||||
 | 
			
		||||
Die Datei `logging.properties` im Verzeichnis `battleship/client` konfiguriert
 | 
			
		||||
das Logging des Clients.
 | 
			
		||||
 | 
			
		||||
Alternativ kann man auch die Start-Skripte
 | 
			
		||||
 | 
			
		||||
* `./battleship/server/build/install/battleship-server/bin/battleship-server`
 | 
			
		||||
* `./battleship/client/build/install/battleship/bin/battleship`
 | 
			
		||||
 | 
			
		||||
direkt in der Kommandozeile starten. Allerdings müssen sie zuvor mittels
 | 
			
		||||
 | 
			
		||||
`./gradlew installDist`
 | 
			
		||||
 | 
			
		||||
erzeugt worden sein. Beachten Sie aber, dass nur im **aktuellen
 | 
			
		||||
Arbeitsverzeichnis** nach den Dateien `config.properties` und
 | 
			
		||||
`logging.properties` gesucht wird und diese geladen werden. Das heißt, dass die
 | 
			
		||||
vordefinierten Dateien in den Verzeichnissen `battleship/server` und
 | 
			
		||||
`battleship/client` nur dann gelesen werden, wenn Sie diese Verzeichnisse als
 | 
			
		||||
aktuelle Arbeitsverzeichnisse nutzen. Wie üblich müssen Sie dazu in der
 | 
			
		||||
Kommandozeile
 | 
			
		||||
 | 
			
		||||
`cd battleship/server`
 | 
			
		||||
 | 
			
		||||
bzw.
 | 
			
		||||
 | 
			
		||||
`cd battleship/client`
 | 
			
		||||
 | 
			
		||||
eingeben.
 | 
			
		||||
 | 
			
		||||
## 3 Hinweise zu _Battleships_
 | 
			
		||||
 | 
			
		||||
Der _Battleships_-Client hat ein Menü, in das man immer mit der
 | 
			
		||||
Esc-Taste kommt. Aus dem Menü heraus lässt sich das Programm auch schließen.
 | 
			
		||||
Beachte, dass sich beim Laden und Speichern eines Spiels kein Dateidialog
 | 
			
		||||
öffnet. Vielmehr muss man den Dateipfad in das Dialogfeld eingeben. Da
 | 
			
		||||
JSON-Dateien geschrieben werden, empfiehlt sich das Datei-Suffix _.json_.
 | 
			
		||||
 | 
			
		||||
## 4 Allgemeine Gradle-Tasks:
 | 
			
		||||
 | 
			
		||||
- `./gradlew clean`
 | 
			
		||||
 | 
			
		||||
  Entfernt alle `build`-Verzeichnisse und alle erzeugten Dateien.
 | 
			
		||||
 | 
			
		||||
- `./gradlew classes`
 | 
			
		||||
 | 
			
		||||
  Übersetzt den Quellcode und legt unter build den Bytecode sowie
 | 
			
		||||
  Ressourcen ab.
 | 
			
		||||
 | 
			
		||||
- `./gradlew javadoc`
 | 
			
		||||
 | 
			
		||||
  Erzeugt die Dokumentation aus den JavaDoc-Kommentaren im Verzeichnis
 | 
			
		||||
  `build/docs/javadoc` des jeweiligen Unterprojekts.
 | 
			
		||||
 | 
			
		||||
- `./gradlew test`
 | 
			
		||||
 | 
			
		||||
  Führt die JUnit-Tests durch. Ergebnisse sind im Verzeichnis
 | 
			
		||||
  `build/reports/tests` des jeweiligen Unterprojekts zu finden.
 | 
			
		||||
 | 
			
		||||
- `./gradlew build`
 | 
			
		||||
 | 
			
		||||
  Führt die JUnit-Tests durch und erstellt in `build/distributions`
 | 
			
		||||
  gepackte Distributionsdateien
 | 
			
		||||
 | 
			
		||||
- `./gradlew installDist`
 | 
			
		||||
 | 
			
		||||
  Erstellt unter `battleship/client/build/install` und
 | 
			
		||||
  `battleship/server/build/install` Verzeichnisse, die jeweils eine ausführbare
 | 
			
		||||
  Distribution samt Start-Skripten enthält (siehe oben).
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
Juli 2024
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'buildlogic.jme-application-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'Battleship Client'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(":jme-common")
 | 
			
		||||
    implementation project(":battleship:model")
 | 
			
		||||
 | 
			
		||||
    implementation libs.jme3.desktop
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.awt.dialogs
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
    runtimeOnly libs.jme3.jogg
 | 
			
		||||
    runtimeOnly libs.jme3.testdata
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass = 'pp.battleship.client.BattleshipApp'
 | 
			
		||||
    applicationName = 'battleship'
 | 
			
		||||
}
 | 
			
		||||
@@ -1,73 +0,0 @@
 | 
			
		||||
########################################
 | 
			
		||||
## Programming project code
 | 
			
		||||
## UniBw M, 2022, 2023, 2024
 | 
			
		||||
## www.unibw.de/inf2
 | 
			
		||||
## (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
########################################
 | 
			
		||||
#
 | 
			
		||||
# Battleship client configuration
 | 
			
		||||
#
 | 
			
		||||
# Specifies the map used by the opponent in single mode.
 | 
			
		||||
# Single mode is activated if this property is set.
 | 
			
		||||
#map.opponent=maps/map2.json
 | 
			
		||||
#
 | 
			
		||||
# Specifies the map used by the player in single mode.
 | 
			
		||||
# The player must define their own map if this property is not set.
 | 
			
		||||
map.own=maps/map1.json
 | 
			
		||||
#
 | 
			
		||||
# Coordinates of the shots fired by the RobotClient in the order listed.
 | 
			
		||||
# Example:
 | 
			
		||||
#   2, 0,\
 | 
			
		||||
#   2, 1,\
 | 
			
		||||
#   2, 2,\
 | 
			
		||||
#   2, 3
 | 
			
		||||
#  defines four shots, namely at the coordinates
 | 
			
		||||
#  (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
 | 
			
		||||
robot.targets=2, 0,\
 | 
			
		||||
              2, 1,\
 | 
			
		||||
              2, 2,\
 | 
			
		||||
              2, 3
 | 
			
		||||
#
 | 
			
		||||
# Delay in milliseconds between each shot fired by the RobotClient.
 | 
			
		||||
robot.delay=500
 | 
			
		||||
#
 | 
			
		||||
# The dimensions of the game map used in single mode.
 | 
			
		||||
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows.
 | 
			
		||||
map.width=10
 | 
			
		||||
map.height=10
 | 
			
		||||
#
 | 
			
		||||
# The number of ships of each length available in single mode.
 | 
			
		||||
# The value is a comma-separated list where each element corresponds to the number of ships
 | 
			
		||||
# with a specific length. For example:
 | 
			
		||||
# ship.nums=4, 3, 2, 1
 | 
			
		||||
# This configuration means:
 | 
			
		||||
#   - 4 ships of length 1
 | 
			
		||||
#   - 3 ships of length 2
 | 
			
		||||
#   - 2 ships of length 3
 | 
			
		||||
#   - 1 ship of length 4
 | 
			
		||||
ship.nums=4, 3, 2, 1
 | 
			
		||||
#
 | 
			
		||||
# Screen settings
 | 
			
		||||
#
 | 
			
		||||
# Color of the text displayed at the top of the overlay.
 | 
			
		||||
# The format is (red, green, blue, alpha) where each value ranges from 0 to 1.
 | 
			
		||||
overlay.top.color=1, 1, 1, 1
 | 
			
		||||
#
 | 
			
		||||
# Application settings configuration
 | 
			
		||||
# Determines whether the settings window is shown at startup.
 | 
			
		||||
settings.show=false
 | 
			
		||||
#
 | 
			
		||||
# Specifies the width of the application window in pixels.
 | 
			
		||||
settings.resolution.width=1200
 | 
			
		||||
#
 | 
			
		||||
# Specifies the height of the application window in pixels.
 | 
			
		||||
settings.resolution.height=800
 | 
			
		||||
#
 | 
			
		||||
# Determines whether the application runs in full-screen mode.
 | 
			
		||||
settings.full-screen=false
 | 
			
		||||
#
 | 
			
		||||
# Enables or disables gamma correction to improve color accuracy.
 | 
			
		||||
settings.use-gamma-correction=true
 | 
			
		||||
#
 | 
			
		||||
# Indicates whether the statistics window is displayed during gameplay.
 | 
			
		||||
statistics.show=false
 | 
			
		||||
@@ -1,8 +0,0 @@
 | 
			
		||||
handlers=java.util.logging.ConsoleHandler
 | 
			
		||||
.level=INFO
 | 
			
		||||
pp.level=FINE
 | 
			
		||||
com.jme3.network.level=INFO
 | 
			
		||||
;com.jme3.util.TangentBinormalGenerator.level=SEVERE
 | 
			
		||||
java.util.logging.ConsoleHandler.level=FINER
 | 
			
		||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
 | 
			
		||||
;java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%n
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "width": 10,
 | 
			
		||||
  "height": 10,
 | 
			
		||||
  "ships": [
 | 
			
		||||
    {
 | 
			
		||||
      "length": 4,
 | 
			
		||||
      "x": 2,
 | 
			
		||||
      "y": 8,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 2,
 | 
			
		||||
      "y": 5,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 5,
 | 
			
		||||
      "y": 6,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 4,
 | 
			
		||||
      "y": 4,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 7,
 | 
			
		||||
      "y": 4,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 4,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
{
 | 
			
		||||
  "width": 10,
 | 
			
		||||
  "height": 10,
 | 
			
		||||
  "ships": [
 | 
			
		||||
    {
 | 
			
		||||
      "length": 4,
 | 
			
		||||
      "x": 0,
 | 
			
		||||
      "y": 5,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 0,
 | 
			
		||||
      "y": 9,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 2,
 | 
			
		||||
      "y": 6,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 4,
 | 
			
		||||
      "y": 8,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 2,
 | 
			
		||||
      "y": 4,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 2,
 | 
			
		||||
      "y": 1,
 | 
			
		||||
      "rot": "DOWN"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -1,429 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.DebugKeysAppState;
 | 
			
		||||
import com.jme3.app.SimpleApplication;
 | 
			
		||||
import com.jme3.app.StatsAppState;
 | 
			
		||||
import com.jme3.font.BitmapFont;
 | 
			
		||||
import com.jme3.font.BitmapText;
 | 
			
		||||
import com.jme3.input.KeyInput;
 | 
			
		||||
import com.jme3.input.MouseInput;
 | 
			
		||||
import com.jme3.input.controls.ActionListener;
 | 
			
		||||
import com.jme3.input.controls.KeyTrigger;
 | 
			
		||||
import com.jme3.input.controls.MouseButtonTrigger;
 | 
			
		||||
import com.jme3.system.AppSettings;
 | 
			
		||||
import com.simsilica.lemur.GuiGlobals;
 | 
			
		||||
import com.simsilica.lemur.style.BaseStyles;
 | 
			
		||||
import pp.battleship.client.gui.BattleAppState;
 | 
			
		||||
import pp.battleship.client.gui.EditorAppState;
 | 
			
		||||
import pp.battleship.client.gui.SeaAppState;
 | 
			
		||||
import pp.battleship.game.client.BattleshipClient;
 | 
			
		||||
import pp.battleship.game.client.ClientGameLogic;
 | 
			
		||||
import pp.battleship.game.client.ServerConnection;
 | 
			
		||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
			
		||||
import pp.battleship.game.singlemode.ServerConnectionMockup;
 | 
			
		||||
import pp.battleship.notification.ClientStateEvent;
 | 
			
		||||
import pp.battleship.notification.GameEventListener;
 | 
			
		||||
import pp.battleship.notification.InfoTextEvent;
 | 
			
		||||
import pp.dialog.DialogBuilder;
 | 
			
		||||
import pp.dialog.DialogManager;
 | 
			
		||||
import pp.graphics.Draw;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.concurrent.ExecutorService;
 | 
			
		||||
import java.util.concurrent.Executors;
 | 
			
		||||
import java.util.logging.LogManager;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The main class for the Battleship client application.
 | 
			
		||||
 * It manages the initialization, input setup, GUI setup, and game states for the client.
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipApp extends SimpleApplication implements BattleshipClient, GameEventListener {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logger for logging messages within the application.
 | 
			
		||||
     */
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleshipApp.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the styles script for GUI elements.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String STYLES_SCRIPT = "Interface/Lemur/pp-styles.groovy"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the font resource used in the GUI.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String FONT = "Interface/Fonts/Default.fnt"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the client configuration file, if one exists.
 | 
			
		||||
     */
 | 
			
		||||
    private static final File CONFIG_FILE = new File("client.properties");
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Input mapping name for mouse clicks.
 | 
			
		||||
     */
 | 
			
		||||
    public static final String CLICK = "CLICK";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Input mapping name for the Escape key.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String ESC = "ESC";
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Manager for handling dialogs within the application.
 | 
			
		||||
     */
 | 
			
		||||
    private final DialogManager dialogManager = new DialogManager(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The server connection instance, used for communicating with the game server.
 | 
			
		||||
     */
 | 
			
		||||
    private final ServerConnection serverConnection;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Instance of the {@link Draw} class for rendering graphics.
 | 
			
		||||
     */
 | 
			
		||||
    private Draw draw;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Text display at the top of the GUI for showing information to the user.
 | 
			
		||||
     */
 | 
			
		||||
    private BitmapText topText;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Executor service for handling asynchronous tasks within the application.
 | 
			
		||||
     */
 | 
			
		||||
    private ExecutorService executor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handler for managing the client's game logic.
 | 
			
		||||
     */
 | 
			
		||||
    private final ClientGameLogic logic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configuration settings for the Battleship client application.
 | 
			
		||||
     */
 | 
			
		||||
    private final BattleshipAppConfig config;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Listener for handling actions triggered by the Escape key.
 | 
			
		||||
     */
 | 
			
		||||
    private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        // Configure logging
 | 
			
		||||
        LogManager manager = LogManager.getLogManager();
 | 
			
		||||
        try {
 | 
			
		||||
            manager.readConfiguration(new FileInputStream("logging.properties"));
 | 
			
		||||
            LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.INFO, e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the Battleship application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param args Command-line arguments for launching the application.
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        new BattleshipApp().start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new {@code BattleshipApp} instance.
 | 
			
		||||
     * Initializes the configuration, server connection, and game logic listeners.
 | 
			
		||||
     */
 | 
			
		||||
    private BattleshipApp() {
 | 
			
		||||
        config = new BattleshipAppConfig();
 | 
			
		||||
        config.readFromIfExists(CONFIG_FILE);
 | 
			
		||||
        serverConnection = makeServerConnection();
 | 
			
		||||
        logic = new ClientGameLogic(serverConnection);
 | 
			
		||||
        logic.addListener(this);
 | 
			
		||||
        setShowSettings(config.getShowSettings());
 | 
			
		||||
        setSettings(makeSettings());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates and configures application settings from the client configuration.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A configured {@link AppSettings} object.
 | 
			
		||||
     */
 | 
			
		||||
    private AppSettings makeSettings() {
 | 
			
		||||
        final AppSettings settings = new AppSettings(true);
 | 
			
		||||
        settings.setTitle(lookup("battleship.name"));
 | 
			
		||||
        settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight());
 | 
			
		||||
        settings.setFullscreen(config.fullScreen());
 | 
			
		||||
        settings.setUseRetinaFrameBuffer(config.useRetinaFrameBuffer());
 | 
			
		||||
        settings.setGammaCorrection(config.useGammaCorrection());
 | 
			
		||||
        return settings;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Factory method for creating a server connection based on the current
 | 
			
		||||
     * client configuration.
 | 
			
		||||
     *
 | 
			
		||||
     * @return A {@link ServerConnection} instance, which could be a real or mock server.
 | 
			
		||||
     */
 | 
			
		||||
    private ServerConnection makeServerConnection() {
 | 
			
		||||
        if (config.isSingleMode())
 | 
			
		||||
            return new ServerConnectionMockup(this);
 | 
			
		||||
        return new NetworkSupport(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the dialog manager responsible for managing in-game dialogs.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link DialogManager} instance.
 | 
			
		||||
     */
 | 
			
		||||
    DialogManager getDialogManager() {
 | 
			
		||||
        return dialogManager;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the game logic handler for the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link ClientGameLogic} instance.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public ClientGameLogic getGameLogic() {
 | 
			
		||||
        return logic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current configuration settings for the Battleship client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link BattleshipClientConfig} instance.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public BattleshipAppConfig getConfig() {
 | 
			
		||||
        return config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the application.
 | 
			
		||||
     * Sets up input mappings, GUI, game states, and connects to the server.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void simpleInitApp() {
 | 
			
		||||
        setPauseOnLostFocus(false);
 | 
			
		||||
        draw = new Draw(assetManager);
 | 
			
		||||
        setupInput();
 | 
			
		||||
        setupStates();
 | 
			
		||||
        setupGui();
 | 
			
		||||
        serverConnection.connect();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up the graphical user interface (GUI) for the application.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupGui() {
 | 
			
		||||
        GuiGlobals.initialize(this);
 | 
			
		||||
        BaseStyles.loadStyleResources(STYLES_SCRIPT);
 | 
			
		||||
        GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS
 | 
			
		||||
        final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
 | 
			
		||||
        topText = new BitmapText(normalFont);
 | 
			
		||||
        final int height = context.getSettings().getHeight();
 | 
			
		||||
        topText.setLocalTranslation(10f, height - 10f, 0f);
 | 
			
		||||
        topText.setColor(config.getTopColor());
 | 
			
		||||
        guiNode.attachChild(topText);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Configures input mappings and sets up listeners for user interactions.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupInput() {
 | 
			
		||||
        inputManager.deleteMapping(INPUT_MAPPING_EXIT);
 | 
			
		||||
        inputManager.setCursorVisible(false);
 | 
			
		||||
        inputManager.addMapping(ESC, new KeyTrigger(KeyInput.KEY_ESCAPE));
 | 
			
		||||
        inputManager.addMapping(CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
 | 
			
		||||
        inputManager.addListener(escapeListener, ESC);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes and attaches the necessary application states for the game.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupStates() {
 | 
			
		||||
        if (config.getShowStatistics()) {
 | 
			
		||||
            final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
 | 
			
		||||
            final StatsAppState stats = new StatsAppState(guiNode, normalFont);
 | 
			
		||||
            stateManager.attach(stats);
 | 
			
		||||
        }
 | 
			
		||||
        flyCam.setEnabled(false);
 | 
			
		||||
        stateManager.detach(stateManager.getState(StatsAppState.class));
 | 
			
		||||
        stateManager.detach(stateManager.getState(DebugKeysAppState.class));
 | 
			
		||||
 | 
			
		||||
        attachGameSound();
 | 
			
		||||
        stateManager.attachAll(new EditorAppState(), new BattleAppState(), new SeaAppState());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attaches the game sound state and sets its initial enabled state.
 | 
			
		||||
     */
 | 
			
		||||
    private void attachGameSound() {
 | 
			
		||||
        final GameSound gameSound = new GameSound();
 | 
			
		||||
        logic.addListener(gameSound);
 | 
			
		||||
        gameSound.setEnabled(GameSound.enabledInPreferences());
 | 
			
		||||
        stateManager.attach(gameSound);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the application state every frame.
 | 
			
		||||
     * This method is called once per frame during the game loop.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf Time per frame in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void simpleUpdate(float tpf) {
 | 
			
		||||
        super.simpleUpdate(tpf);
 | 
			
		||||
        dialogManager.update(tpf);
 | 
			
		||||
        logic.update(tpf);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the Escape key action to either close the top dialog or show the main menu.
 | 
			
		||||
     *
 | 
			
		||||
     * @param isPressed Indicates whether the Escape key is pressed.
 | 
			
		||||
     */
 | 
			
		||||
    private void escape(boolean isPressed) {
 | 
			
		||||
        if (!isPressed) return;
 | 
			
		||||
        if (dialogManager.showsDialog())
 | 
			
		||||
            dialogManager.escape();
 | 
			
		||||
        else
 | 
			
		||||
            new Menu(this).open();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the {@link Draw} instance used for rendering graphical elements in the game.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link Draw} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public Draw getDraw() {
 | 
			
		||||
        return draw;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a request to close the application.
 | 
			
		||||
     * If the request is initiated by pressing ESC, this parameter is true.
 | 
			
		||||
     *
 | 
			
		||||
     * @param esc If true, the request is due to the ESC key being pressed.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void requestClose(boolean esc) { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the application, displaying a confirmation dialog if the client is connected to a server.
 | 
			
		||||
     */
 | 
			
		||||
    public void closeApp() {
 | 
			
		||||
        if (serverConnection.isConnected())
 | 
			
		||||
            confirmDialog(lookup("confirm.leaving"), this::close);
 | 
			
		||||
        else
 | 
			
		||||
            close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the application, disconnecting from the server and stopping the application.
 | 
			
		||||
     */
 | 
			
		||||
    private void close() {
 | 
			
		||||
        serverConnection.disconnect();
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the informational text displayed in the GUI.
 | 
			
		||||
     *
 | 
			
		||||
     * @param text The information text to display.
 | 
			
		||||
     */
 | 
			
		||||
    void setInfoText(String text) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "setInfoText {0}", text); //NON-NLS
 | 
			
		||||
        topText.setText(text);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the informational text in the GUI based on the key received in an {@link InfoTextEvent}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event The {@link InfoTextEvent} containing the key for the text to display.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(InfoTextEvent event) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "received info text {0}", event.key()); //NON-NLS
 | 
			
		||||
        setInfoText(lookup(event.key()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles client state events to update the game states accordingly.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event The {@link ClientStateEvent} representing the state change.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(ClientStateEvent event) {
 | 
			
		||||
        stateManager.getState(EditorAppState.class).setEnabled(logic.showEditor());
 | 
			
		||||
        stateManager.getState(BattleAppState.class).setEnabled(logic.showBattle());
 | 
			
		||||
        stateManager.getState(SeaAppState.class).setEnabled(logic.showBattle());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the executor service used for handling multithreaded tasks.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The {@link ExecutorService} instance.
 | 
			
		||||
     */
 | 
			
		||||
    public ExecutorService getExecutor() {
 | 
			
		||||
        if (executor == null)
 | 
			
		||||
            executor = Executors.newCachedThreadPool();
 | 
			
		||||
        return executor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Stops the application, shutting down the executor service and halting execution.
 | 
			
		||||
     *
 | 
			
		||||
     * @param waitFor If true, waits for the application to stop before returning.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void stop(boolean waitFor) {
 | 
			
		||||
        if (executor != null) executor.shutdownNow();
 | 
			
		||||
        super.stop(waitFor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays a confirmation dialog with a specified question and action for the "Yes" button.
 | 
			
		||||
     *
 | 
			
		||||
     * @param question  The question to display in the dialog.
 | 
			
		||||
     * @param yesAction The action to perform if "Yes" is selected.
 | 
			
		||||
     */
 | 
			
		||||
    void confirmDialog(String question, Runnable yesAction) {
 | 
			
		||||
        DialogBuilder.simple(dialogManager)
 | 
			
		||||
                     .setTitle(lookup("dialog.question"))
 | 
			
		||||
                     .setText(question)
 | 
			
		||||
                     .setOkButton(lookup("button.yes"), yesAction)
 | 
			
		||||
                     .setNoButton(lookup("button.no"))
 | 
			
		||||
                     .build()
 | 
			
		||||
                     .open();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Displays an error dialog with the specified error message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param errorMessage The error message to display in the dialog.
 | 
			
		||||
     */
 | 
			
		||||
    void errorDialog(String errorMessage) {
 | 
			
		||||
        DialogBuilder.simple(dialogManager)
 | 
			
		||||
                     .setTitle(lookup("dialog.error"))
 | 
			
		||||
                     .setText(errorMessage)
 | 
			
		||||
                     .setOkButton(lookup("button.ok"))
 | 
			
		||||
                     .build()
 | 
			
		||||
                     .open();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,196 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to the Battleship application configuration.
 | 
			
		||||
 * Extends {@link BattleshipClientConfig} to include additional properties specific to the client,
 | 
			
		||||
 * particularly those related to screen settings and visual customization.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>Note:</b> Attributes of this class should not be marked as {@code final}
 | 
			
		||||
 * to ensure proper functionality when reading from a properties file.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipAppConfig extends BattleshipClientConfig {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts a string value found in the properties file into an object of the specified type.
 | 
			
		||||
     * Extends the superclass method to support conversion to {@link ColorRGBA}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param value      the string value to be converted
 | 
			
		||||
     * @param targetType the target type into which the value string is converted
 | 
			
		||||
     * @return the converted object of the specified type
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Object convertToType(String value, Class<?> targetType) {
 | 
			
		||||
        if (targetType == ColorRGBA.class)
 | 
			
		||||
            return makeColorRGBA(value);
 | 
			
		||||
        return super.convertToType(value, targetType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts the specified string value to a corresponding {@link ColorRGBA} object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param value the color in the format "red, green, blue, alpha" with all values in the range [0..1]
 | 
			
		||||
     * @return a {@link ColorRGBA} object representing the color
 | 
			
		||||
     * @throws IllegalArgumentException if the input string is not in the expected format
 | 
			
		||||
     */
 | 
			
		||||
    private static ColorRGBA makeColorRGBA(String value) {
 | 
			
		||||
        String[] split = value.split(",", -1);
 | 
			
		||||
        try {
 | 
			
		||||
            if (split.length == 4)
 | 
			
		||||
                return new ColorRGBA(Float.parseFloat(split[0]),
 | 
			
		||||
                                     Float.parseFloat(split[1]),
 | 
			
		||||
                                     Float.parseFloat(split[2]),
 | 
			
		||||
                                     Float.parseFloat(split[3]));
 | 
			
		||||
        }
 | 
			
		||||
        catch (NumberFormatException e) {
 | 
			
		||||
            // deliberately left empty
 | 
			
		||||
        }
 | 
			
		||||
        throw new IllegalArgumentException(value + " should consist of exactly 4 numbers");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The width of the game view resolution in pixels.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.resolution.width") //NON-NLS
 | 
			
		||||
    private int resolutionWidth = 1200;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The height of the game view resolution in pixels.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.resolution.height") //NON-NLS
 | 
			
		||||
    private int resolutionHeight = 800;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies whether the game should start in full-screen mode.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.full-screen") //NON-NLS
 | 
			
		||||
    private boolean fullScreen = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies whether gamma correction should be enabled.
 | 
			
		||||
     * If enabled, the main framebuffer is configured for sRGB colors,
 | 
			
		||||
     * and sRGB images are linearized.
 | 
			
		||||
     * <p>
 | 
			
		||||
     * Requires a GPU that supports GL_ARB_framebuffer_sRGB; otherwise, this setting will be ignored.
 | 
			
		||||
     * </p>
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.use-gamma-correction") //NON-NLS
 | 
			
		||||
    private boolean useGammaCorrection = true;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies whether full resolution framebuffers should be used on Retina displays.
 | 
			
		||||
     * This setting is ignored on non-Retina platforms.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.use-retina-framebuffer") //NON-NLS
 | 
			
		||||
    private boolean useRetinaFrameBuffer = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies whether the settings window should be shown for configuring the game.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("settings.show") //NON-NLS
 | 
			
		||||
    private boolean showSettings = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Specifies whether the JME statistics window should be shown in the lower left corner of the screen.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("statistics.show") //NON-NLS
 | 
			
		||||
    private boolean showStatistics = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The color of the top text during gameplay, represented as a {@link ColorRGBA} object.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("overlay.top.color") //NON-NLS
 | 
			
		||||
    private ColorRGBA topColor = ColorRGBA.White;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a default {@code BattleshipAppConfig} with predefined values.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipAppConfig() {
 | 
			
		||||
        // Default constructor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the width of the game view resolution in pixels.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the game view resolution in pixels
 | 
			
		||||
     */
 | 
			
		||||
    public int getResolutionWidth() {
 | 
			
		||||
        return resolutionWidth;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the height of the game view resolution in pixels.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the game view resolution in pixels
 | 
			
		||||
     */
 | 
			
		||||
    public int getResolutionHeight() {
 | 
			
		||||
        return resolutionHeight;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the game should start in full-screen mode.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if the game should start in full-screen mode; {@code false} otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean fullScreen() {
 | 
			
		||||
        return fullScreen;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether gamma correction is enabled.
 | 
			
		||||
     * If enabled, the main framebuffer is configured for sRGB colors,
 | 
			
		||||
     * and sRGB images are linearized.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if gamma correction is enabled; {@code false} otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean useGammaCorrection() {
 | 
			
		||||
        return useGammaCorrection;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether full resolution framebuffers should be used on Retina displays.
 | 
			
		||||
     * This setting is ignored on non-Retina platforms.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if full resolution framebuffers should be used on Retina displays; {@code false} otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean useRetinaFrameBuffer() {
 | 
			
		||||
        return useRetinaFrameBuffer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the settings window should be shown for configuring the game.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if the settings window should be shown; {@code false} otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean getShowSettings() {
 | 
			
		||||
        return showSettings;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether the JME statistics window should be shown in the lower left corner of the screen.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if the statistics window should be shown; {@code false} otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean getShowStatistics() {
 | 
			
		||||
        return showStatistics;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the color of the top text during gameplay as a {@link ColorRGBA} object.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the color of the top text during gameplay
 | 
			
		||||
     */
 | 
			
		||||
    public ColorRGBA getTopColor() {
 | 
			
		||||
        return topColor;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.Application;
 | 
			
		||||
import com.jme3.app.state.AbstractAppState;
 | 
			
		||||
import com.jme3.app.state.AppStateManager;
 | 
			
		||||
import pp.battleship.game.client.ClientGameLogic;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract class representing a state in the Battleship game.
 | 
			
		||||
 * Extends the AbstractAppState from jMonkeyEngine to manage state behavior.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class BattleshipAppState extends AbstractAppState {
 | 
			
		||||
    private BattleshipApp app;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new BattleshipAppState that is initially disabled.
 | 
			
		||||
     *
 | 
			
		||||
     * @see #setEnabled(boolean)
 | 
			
		||||
     */
 | 
			
		||||
    protected BattleshipAppState() {
 | 
			
		||||
        setEnabled(false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the state manager and application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stateManager The state manager
 | 
			
		||||
     * @param application  The application instance
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(AppStateManager stateManager, Application application) {
 | 
			
		||||
        super.initialize(stateManager, application);
 | 
			
		||||
        this.app = (BattleshipApp) application;
 | 
			
		||||
        if (isEnabled()) enableState();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the BattleshipApp instance associated with this BattleshipAppState.
 | 
			
		||||
     *
 | 
			
		||||
     * @return The BattleshipApp instance.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipApp getApp() {
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the client game logic handler.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the client game logic handler
 | 
			
		||||
     */
 | 
			
		||||
    public ClientGameLogic getGameLogic() {
 | 
			
		||||
        return app.getGameLogic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if any dialog is currently displayed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if any dialog is currently shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showsDialog() {
 | 
			
		||||
        return app.getDialogManager().showsDialog();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the enabled state of the BattleshipAppState.
 | 
			
		||||
     * If the new state is the same as the current state, the method returns.
 | 
			
		||||
     *
 | 
			
		||||
     * @param enabled The new enabled state.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setEnabled(boolean enabled) {
 | 
			
		||||
        if (isEnabled() == enabled) return;
 | 
			
		||||
        super.setEnabled(enabled);
 | 
			
		||||
        if (app != null) {
 | 
			
		||||
            if (enabled)
 | 
			
		||||
                enableState();
 | 
			
		||||
            else
 | 
			
		||||
                disableState();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called when the state is enabled.
 | 
			
		||||
     * It is meant to be overridden by subclasses to perform
 | 
			
		||||
     * specific actions when the state is enabled.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void enableState();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called when the state is disabled.
 | 
			
		||||
     * It is meant to be overridden by subclasses to perform
 | 
			
		||||
     * specific actions when the state is disabled.
 | 
			
		||||
     */
 | 
			
		||||
    protected abstract void disableState();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,135 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.Application;
 | 
			
		||||
import com.jme3.app.state.AbstractAppState;
 | 
			
		||||
import com.jme3.app.state.AppStateManager;
 | 
			
		||||
import com.jme3.asset.AssetLoadException;
 | 
			
		||||
import com.jme3.asset.AssetNotFoundException;
 | 
			
		||||
import com.jme3.audio.AudioData;
 | 
			
		||||
import com.jme3.audio.AudioNode;
 | 
			
		||||
import pp.battleship.notification.GameEventListener;
 | 
			
		||||
import pp.battleship.notification.SoundEvent;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
import static pp.util.PreferencesUtils.getPreferences;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An application state that plays sounds.
 | 
			
		||||
 */
 | 
			
		||||
public class GameSound extends AbstractAppState implements GameEventListener {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(GameSound.class.getName());
 | 
			
		||||
    private static final Preferences PREFERENCES = getPreferences(GameSound.class);
 | 
			
		||||
    private static final String ENABLED_PREF = "enabled"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    private AudioNode splashSound;
 | 
			
		||||
    private AudioNode shipDestroyedSound;
 | 
			
		||||
    private AudioNode explosionSound;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sound is enabled in the preferences.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if sound is enabled, {@code false} otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean enabledInPreferences() {
 | 
			
		||||
        return PREFERENCES.getBoolean(ENABLED_PREF, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles the game sound on or off.
 | 
			
		||||
     */
 | 
			
		||||
    public void toggleSound() {
 | 
			
		||||
        setEnabled(!isEnabled());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the enabled state of this AppState.
 | 
			
		||||
     * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
 | 
			
		||||
     *
 | 
			
		||||
     * @param enabled {@code true} to enable the AppState, {@code false} to disable it.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setEnabled(boolean enabled) {
 | 
			
		||||
        if (isEnabled() == enabled) return;
 | 
			
		||||
        super.setEnabled(enabled);
 | 
			
		||||
        LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
 | 
			
		||||
        PREFERENCES.putBoolean(ENABLED_PREF, enabled);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the sound effects for the game.
 | 
			
		||||
     * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
 | 
			
		||||
     *
 | 
			
		||||
     * @param stateManager The state manager
 | 
			
		||||
     * @param app          The application
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(AppStateManager stateManager, Application app) {
 | 
			
		||||
        super.initialize(stateManager, app);
 | 
			
		||||
        shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
 | 
			
		||||
        splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
 | 
			
		||||
        explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a sound from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  The application
 | 
			
		||||
     * @param name The name of the sound file.
 | 
			
		||||
     * @return The loaded AudioNode.
 | 
			
		||||
     */
 | 
			
		||||
    private AudioNode loadSound(Application app, String name) {
 | 
			
		||||
        try {
 | 
			
		||||
            final AudioNode sound = new AudioNode(app.getAssetManager(), name, AudioData.DataType.Buffer);
 | 
			
		||||
            sound.setLooping(false);
 | 
			
		||||
            sound.setPositional(false);
 | 
			
		||||
            return sound;
 | 
			
		||||
        }
 | 
			
		||||
        catch (AssetLoadException | AssetNotFoundException ex) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, ex.getMessage(), ex);
 | 
			
		||||
        }
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the splash sound effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void splash() {
 | 
			
		||||
        if (isEnabled() && splashSound != null)
 | 
			
		||||
            splashSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays the explosion sound effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void explosion() {
 | 
			
		||||
        if (isEnabled() && explosionSound != null)
 | 
			
		||||
            explosionSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays sound effect when a ship has been destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    public void shipDestroyed() {
 | 
			
		||||
        if (isEnabled() && shipDestroyedSound != null)
 | 
			
		||||
            shipDestroyedSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(SoundEvent event) {
 | 
			
		||||
        switch (event.sound()) {
 | 
			
		||||
            case EXPLOSION -> explosion();
 | 
			
		||||
            case SPLASH -> splash();
 | 
			
		||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,143 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.simsilica.lemur.Button;
 | 
			
		||||
import com.simsilica.lemur.Checkbox;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.style.ElementId;
 | 
			
		||||
import pp.dialog.Dialog;
 | 
			
		||||
import pp.dialog.StateCheckboxModel;
 | 
			
		||||
import pp.dialog.TextInputDialog;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
import static pp.util.PreferencesUtils.getPreferences;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The Menu class represents the main menu in the Battleship game application.
 | 
			
		||||
 * It extends the Dialog class and provides functionalities for loading, saving,
 | 
			
		||||
 * returning to the game, and quitting the application.
 | 
			
		||||
 */
 | 
			
		||||
class Menu extends Dialog {
 | 
			
		||||
    private static final Preferences PREFERENCES = getPreferences(Menu.class);
 | 
			
		||||
    private static final String LAST_PATH = "last.file.path";
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
    private final Button loadButton = new Button(lookup("menu.map.load"));
 | 
			
		||||
    private final Button saveButton = new Button(lookup("menu.map.save"));
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs the Menu dialog for the Battleship application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app the BattleshipApp instance
 | 
			
		||||
     */
 | 
			
		||||
    public Menu(BattleshipApp app) {
 | 
			
		||||
        super(app.getDialogManager());
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS
 | 
			
		||||
        addChild(new Checkbox(lookup("menu.sound-enabled"),
 | 
			
		||||
                              new StateCheckboxModel(app, GameSound.class)));
 | 
			
		||||
        addChild(loadButton)
 | 
			
		||||
                .addClickCommands(s -> ifTopDialog(this::loadDialog));
 | 
			
		||||
        addChild(saveButton)
 | 
			
		||||
                .addClickCommands(s -> ifTopDialog(this::saveDialog));
 | 
			
		||||
        addChild(new Button(lookup("menu.return-to-game")))
 | 
			
		||||
                .addClickCommands(s -> ifTopDialog(this::close));
 | 
			
		||||
        addChild(new Button(lookup("menu.quit")))
 | 
			
		||||
                .addClickCommands(s -> ifTopDialog(app::closeApp));
 | 
			
		||||
        update();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the load and save buttons based on the game logic.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update() {
 | 
			
		||||
        loadButton.setEnabled(app.getGameLogic().mayLoadMap());
 | 
			
		||||
        saveButton.setEnabled(app.getGameLogic().maySaveMap());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * As an escape action, this method closes the menu if it is the top dialog.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void escape() {
 | 
			
		||||
        close();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Functional interface for file actions.
 | 
			
		||||
     */
 | 
			
		||||
    @FunctionalInterface
 | 
			
		||||
    private interface FileAction {
 | 
			
		||||
        /**
 | 
			
		||||
         * Executes a file action.
 | 
			
		||||
         *
 | 
			
		||||
         * @param file the file to be processed
 | 
			
		||||
         * @throws IOException if an I/O error occurs
 | 
			
		||||
         */
 | 
			
		||||
        void run(File file) throws IOException;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the file action for the provided dialog.
 | 
			
		||||
     *
 | 
			
		||||
     * @param fileAction the file action to be executed
 | 
			
		||||
     * @param dialog     the dialog providing the file input
 | 
			
		||||
     */
 | 
			
		||||
    private void handle(FileAction fileAction, TextInputDialog dialog) {
 | 
			
		||||
        try {
 | 
			
		||||
            final String path = dialog.getInput().getText();
 | 
			
		||||
            PREFERENCES.put(LAST_PATH, path);
 | 
			
		||||
            fileAction.run(new File(path));
 | 
			
		||||
            dialog.close();
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            app.errorDialog(e.getLocalizedMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows a file dialog for loading or saving files.
 | 
			
		||||
     *
 | 
			
		||||
     * @param fileAction the action to perform with the selected file
 | 
			
		||||
     * @param label      the label for the dialog
 | 
			
		||||
     */
 | 
			
		||||
    private void fileDialog(FileAction fileAction, String label) {
 | 
			
		||||
        final TextInputDialog dialog =
 | 
			
		||||
                TextInputDialog.builder(app.getDialogManager())
 | 
			
		||||
                               .setLabel(lookup("label.file"))
 | 
			
		||||
                               .setFocus(TextInputDialog::getInput)
 | 
			
		||||
                               .setTitle(label)
 | 
			
		||||
                               .setOkButton(lookup("button.ok"), d -> handle(fileAction, d))
 | 
			
		||||
                               .setNoButton(lookup("button.cancel"))
 | 
			
		||||
                               .setOkClose(false)
 | 
			
		||||
                               .build();
 | 
			
		||||
        final String path = PREFERENCES.get(LAST_PATH, null);
 | 
			
		||||
        if (path != null)
 | 
			
		||||
            dialog.getInput().setText(path.trim());
 | 
			
		||||
        dialog.open();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the load dialog for loading maps.
 | 
			
		||||
     */
 | 
			
		||||
    private void loadDialog() {
 | 
			
		||||
        fileDialog(app.getGameLogic()::loadMap, lookup("menu.map.load"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Shows the save dialog for saving maps.
 | 
			
		||||
     */
 | 
			
		||||
    private void saveDialog() {
 | 
			
		||||
        fileDialog(app.getGameLogic()::saveMap, lookup("menu.map.save"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,153 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import com.simsilica.lemur.Label;
 | 
			
		||||
import com.simsilica.lemur.TextField;
 | 
			
		||||
import com.simsilica.lemur.component.SpringGridLayout;
 | 
			
		||||
import pp.dialog.Dialog;
 | 
			
		||||
import pp.dialog.DialogBuilder;
 | 
			
		||||
import pp.dialog.SimpleDialog;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.concurrent.ExecutionException;
 | 
			
		||||
import java.util.concurrent.Future;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a dialog for setting up a network connection in the Battleship game.
 | 
			
		||||
 * Allows users to specify the host and port for connecting to a game server.
 | 
			
		||||
 */
 | 
			
		||||
class NetworkDialog extends SimpleDialog {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(NetworkDialog.class.getName());
 | 
			
		||||
    private static final String LOCALHOST = "localhost"; //NON-NLS
 | 
			
		||||
    private static final String DEFAULT_PORT = "1234"; //NON-NLS
 | 
			
		||||
    private final NetworkSupport network;
 | 
			
		||||
    private final TextField host = new TextField(LOCALHOST);
 | 
			
		||||
    private final TextField port = new TextField(DEFAULT_PORT);
 | 
			
		||||
    private String hostname;
 | 
			
		||||
    private int portNumber;
 | 
			
		||||
    private Future<Object> connectionFuture;
 | 
			
		||||
    private Dialog progressDialog;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new NetworkDialog.
 | 
			
		||||
     *
 | 
			
		||||
     * @param network The NetworkSupport instance to be used for network operations.
 | 
			
		||||
     */
 | 
			
		||||
    NetworkDialog(NetworkSupport network) {
 | 
			
		||||
        super(network.getApp().getDialogManager());
 | 
			
		||||
        this.network = network;
 | 
			
		||||
        host.setSingleLine(true);
 | 
			
		||||
        host.setPreferredWidth(400f);
 | 
			
		||||
        port.setSingleLine(true);
 | 
			
		||||
 | 
			
		||||
        final BattleshipApp app = network.getApp();
 | 
			
		||||
        final Container input = new Container(new SpringGridLayout());
 | 
			
		||||
        input.addChild(new Label(lookup("host.name") + ":  "));
 | 
			
		||||
        input.addChild(host, 1);
 | 
			
		||||
        input.addChild(new Label(lookup("port.number") + ":  "));
 | 
			
		||||
        input.addChild(port, 1);
 | 
			
		||||
 | 
			
		||||
        DialogBuilder.simple(app.getDialogManager())
 | 
			
		||||
                     .setTitle(lookup("server.dialog"))
 | 
			
		||||
                     .setExtension(d -> d.addChild(input))
 | 
			
		||||
                     .setOkButton(lookup("button.connect"), d -> connect())
 | 
			
		||||
                     .setNoButton(lookup("button.cancel"), app::closeApp)
 | 
			
		||||
                     .setOkClose(false)
 | 
			
		||||
                     .setNoClose(false)
 | 
			
		||||
                     .build(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the action for the connect button in the connection dialog.
 | 
			
		||||
     * Tries to parse the port number and initiate connection to the server.
 | 
			
		||||
     */
 | 
			
		||||
    private void connect() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
 | 
			
		||||
        try {
 | 
			
		||||
            hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
 | 
			
		||||
            portNumber = Integer.parseInt(port.getText());
 | 
			
		||||
            openProgressDialog();
 | 
			
		||||
            connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
 | 
			
		||||
        }
 | 
			
		||||
        catch (NumberFormatException e) {
 | 
			
		||||
            network.getApp().errorDialog(lookup("port.must.be.integer"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a dialog indicating that the connection is in progress.
 | 
			
		||||
     */
 | 
			
		||||
    private void openProgressDialog() {
 | 
			
		||||
        progressDialog = DialogBuilder.simple(network.getApp().getDialogManager())
 | 
			
		||||
                                      .setText(lookup("label.connecting"))
 | 
			
		||||
                                      .build();
 | 
			
		||||
        progressDialog.open();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Tries to initialize the network connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws RuntimeException If an error occurs when creating the client.
 | 
			
		||||
     */
 | 
			
		||||
    private Object initNetwork() {
 | 
			
		||||
        try {
 | 
			
		||||
            network.initNetwork(hostname, portNumber);
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception e) {
 | 
			
		||||
            throw new RuntimeException(e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically
 | 
			
		||||
     * updating this dialog. T
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        if (connectionFuture != null && connectionFuture.isDone())
 | 
			
		||||
            try {
 | 
			
		||||
                connectionFuture.get();
 | 
			
		||||
                success();
 | 
			
		||||
            }
 | 
			
		||||
            catch (ExecutionException e) {
 | 
			
		||||
                failure(e.getCause());
 | 
			
		||||
            }
 | 
			
		||||
            catch (InterruptedException e) {
 | 
			
		||||
                LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
 | 
			
		||||
                Thread.currentThread().interrupt();
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a successful connection to the game server.
 | 
			
		||||
     */
 | 
			
		||||
    private void success() {
 | 
			
		||||
        connectionFuture = null;
 | 
			
		||||
        progressDialog.close();
 | 
			
		||||
        this.close();
 | 
			
		||||
        network.getApp().setInfoText(lookup("wait.for.an.opponent"));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a failed connection attempt.
 | 
			
		||||
     *
 | 
			
		||||
     * @param e The cause of the failure.
 | 
			
		||||
     */
 | 
			
		||||
    private void failure(Throwable e) {
 | 
			
		||||
        connectionFuture = null;
 | 
			
		||||
        progressDialog.close();
 | 
			
		||||
        network.getApp().errorDialog(lookup("server.connection.failed"));
 | 
			
		||||
        network.getApp().setInfoText(e.getLocalizedMessage());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,152 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.Client;
 | 
			
		||||
import com.jme3.network.ClientStateListener;
 | 
			
		||||
import com.jme3.network.Message;
 | 
			
		||||
import com.jme3.network.MessageListener;
 | 
			
		||||
import com.jme3.network.Network;
 | 
			
		||||
import pp.battleship.game.client.ServerConnection;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.server.ServerMessage;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages the network connection for the Battleship application.
 | 
			
		||||
 * Handles connecting to and disconnecting from the server, and sending messages.
 | 
			
		||||
 */
 | 
			
		||||
class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
    private Client client;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a NetworkSupport instance for the given Battleship application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app The Battleship application instance.
 | 
			
		||||
     */
 | 
			
		||||
    public NetworkSupport(BattleshipApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the Battleship application instance.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Battleship application instance
 | 
			
		||||
     */
 | 
			
		||||
    BattleshipApp getApp() {
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is a connection to the game server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is a connection to the game server, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isConnected() {
 | 
			
		||||
        return client != null && client.isConnected();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to join the game if there is no connection yet.
 | 
			
		||||
     * Opens a dialog for the user to enter the host and port information.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connect() {
 | 
			
		||||
        if (client == null)
 | 
			
		||||
            new NetworkDialog(this).open();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes the client connection.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void disconnect() {
 | 
			
		||||
        if (client == null) return;
 | 
			
		||||
        client.close();
 | 
			
		||||
        client = null;
 | 
			
		||||
        LOGGER.log(Level.INFO, "client closed"); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the network connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param host The server's address.
 | 
			
		||||
     * @param port The server's port.
 | 
			
		||||
     * @throws IOException If an I/O error occurs when creating the client.
 | 
			
		||||
     */
 | 
			
		||||
    void initNetwork(String host, int port) throws IOException {
 | 
			
		||||
        if (client != null)
 | 
			
		||||
            throw new IllegalStateException("trying to join a game again");
 | 
			
		||||
        client = Network.connectToServer(host, port);
 | 
			
		||||
        client.start();
 | 
			
		||||
        client.addMessageListener(this);
 | 
			
		||||
        client.addClientStateListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when a message is received from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client  The client instance that received the message.
 | 
			
		||||
     * @param message The message received from the server.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void messageReceived(Client client, Message message) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "message received from server: {0}", message); //NON-NLS
 | 
			
		||||
        if (message instanceof ServerMessage serverMessage)
 | 
			
		||||
            app.enqueue(() -> serverMessage.accept(app.getGameLogic()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the client has successfully connected to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client The client that connected to the server.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clientConnected(Client client) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Client connected: {0}", client); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called when the client is disconnected from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param client         The client that was disconnected.
 | 
			
		||||
     * @param disconnectInfo Information about the disconnection.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Client {0} disconnected: {1}", client, disconnectInfo); //NON-NLS
 | 
			
		||||
        if (this.client != client)
 | 
			
		||||
            throw new IllegalArgumentException("parameter value must be client");
 | 
			
		||||
        LOGGER.log(Level.INFO, "client still connected: {0}", client.isConnected()); //NON-NLS
 | 
			
		||||
        this.client = null;
 | 
			
		||||
        disconnect();
 | 
			
		||||
        app.enqueue(() -> app.setInfoText(lookup("lost.connection.to.server")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends the specified message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message to be sent to the server.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void send(ClientMessage message) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "sending {0}", message); //NON-NLS
 | 
			
		||||
        if (client == null)
 | 
			
		||||
            app.errorDialog(lookup("lost.connection.to.server"));
 | 
			
		||||
        else
 | 
			
		||||
            client.send(message);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,122 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.input.controls.ActionListener;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.system.AppSettings;
 | 
			
		||||
import pp.battleship.client.BattleshipAppState;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.client.BattleshipApp.CLICK;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state responsible for managing the battle interface within the Battleship game.
 | 
			
		||||
 * This state handles the display and interaction of the battle map, including the opponent's map.
 | 
			
		||||
 * It manages GUI components, input events, and the layout of the interface when this state is enabled.
 | 
			
		||||
 */
 | 
			
		||||
public class BattleAppState extends BattleshipAppState {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleAppState.class.getName());
 | 
			
		||||
    private static final float DEPTH = 0f;
 | 
			
		||||
    private static final float GAP = 20f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A listener for handling click events in the battle interface.
 | 
			
		||||
     * When a click is detected, it triggers the corresponding actions on the opponent's map.
 | 
			
		||||
     */
 | 
			
		||||
    private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The root node for all GUI components in the battle state.
 | 
			
		||||
     */
 | 
			
		||||
    private final Node battleNode = new Node("Battle"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A view representing the opponent's map in the GUI.
 | 
			
		||||
     */
 | 
			
		||||
    private MapView opponentMapView;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enables the battle state by initializing, laying out, and adding GUI components.
 | 
			
		||||
     * Attaches the components to the GUI node and registers input listeners.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void enableState() {
 | 
			
		||||
        battleNode.detachAllChildren();
 | 
			
		||||
        initializeGuiComponents();
 | 
			
		||||
        layoutGuiComponents();
 | 
			
		||||
        addGuiComponents();
 | 
			
		||||
        getApp().getGuiNode().attachChild(battleNode);
 | 
			
		||||
        getApp().getInputManager().addListener(clickListener, CLICK);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disables the battle state by removing GUI components and unregistering input listeners.
 | 
			
		||||
     * Also handles cleanup of resources, such as the opponent's map view.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void disableState() {
 | 
			
		||||
        getApp().getGuiNode().detachChild(battleNode);
 | 
			
		||||
        getApp().getInputManager().removeListener(clickListener);
 | 
			
		||||
        if (opponentMapView != null) {
 | 
			
		||||
            opponentMapView.unregister();
 | 
			
		||||
            opponentMapView = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the GUI components used in the battle state.
 | 
			
		||||
     * Creates the opponent's map view and adds a grid overlay to it.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeGuiComponents() {
 | 
			
		||||
        opponentMapView = new MapView(getGameLogic().getOpponentMap(), getApp());
 | 
			
		||||
        opponentMapView.addGrid();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the initialized GUI components to the battle node.
 | 
			
		||||
     * Currently, it attaches the opponent's map view to the node.
 | 
			
		||||
     */
 | 
			
		||||
    private void addGuiComponents() {
 | 
			
		||||
        battleNode.attachChild(opponentMapView.getNode());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lays out the GUI components within the window, positioning them appropriately.
 | 
			
		||||
     * The opponent's map view is positioned based on the window's dimensions and a specified gap.
 | 
			
		||||
     */
 | 
			
		||||
    private void layoutGuiComponents() {
 | 
			
		||||
        final AppSettings s = getApp().getContext().getSettings();
 | 
			
		||||
        final float mapWidth = opponentMapView.getWidth();
 | 
			
		||||
        final float mapHeight = opponentMapView.getHeight();
 | 
			
		||||
        final float windowWidth = s.getWidth();
 | 
			
		||||
        final float windowHeight = s.getHeight();
 | 
			
		||||
 | 
			
		||||
        opponentMapView.getNode().setLocalTranslation(windowWidth - mapWidth - GAP,
 | 
			
		||||
                                                      windowHeight - mapHeight - GAP,
 | 
			
		||||
                                                      DEPTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles click events in the battle interface. If the event indicates a click (not a release),
 | 
			
		||||
     * it translates the cursor position to the model's coordinate system and triggers the game logic
 | 
			
		||||
     * for interacting with the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param isPressed whether the mouse button is currently pressed (true) or released (false)
 | 
			
		||||
     */
 | 
			
		||||
    private void click(boolean isPressed) {
 | 
			
		||||
        if (!isPressed || showsDialog())
 | 
			
		||||
            return;
 | 
			
		||||
        final IntPoint cursorPos = opponentMapView.mouseToModel(getApp().getInputManager().getCursorPosition());
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS
 | 
			
		||||
        getGameLogic().clickOpponentMap(cursorPos);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,173 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.input.controls.ActionListener;
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.system.AppSettings;
 | 
			
		||||
import com.simsilica.lemur.Button;
 | 
			
		||||
import com.simsilica.lemur.Container;
 | 
			
		||||
import pp.battleship.client.BattleshipAppState;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
import static pp.battleship.client.BattleshipApp.CLICK;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * EditorState manages the editor mode in the Battleship game,
 | 
			
		||||
 * allowing players to place and rotate ships.
 | 
			
		||||
 */
 | 
			
		||||
public class EditorAppState extends BattleshipAppState {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(EditorAppState.class.getName());
 | 
			
		||||
    private static final float DEPTH = 0f;
 | 
			
		||||
    private static final float GAP = 20f;
 | 
			
		||||
 | 
			
		||||
    private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed);
 | 
			
		||||
    private final Node editorNode = new Node("Editor"); //NON-NLS
 | 
			
		||||
    private Container buttonContainer;
 | 
			
		||||
    private Button rotateButton;
 | 
			
		||||
    private Button readyButton;
 | 
			
		||||
    private MapView ownMapView;
 | 
			
		||||
    private MapView harborView;
 | 
			
		||||
    private Vector2f oldCursorPosition;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enables the editor state by attaching necessary nodes and listeners.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void enableState() {
 | 
			
		||||
        editorNode.detachAllChildren();
 | 
			
		||||
        initializeGuiComponents();
 | 
			
		||||
        addGuiComponents();
 | 
			
		||||
        layoutGuiComponents();
 | 
			
		||||
        getApp().getGuiNode().attachChild(editorNode);
 | 
			
		||||
        getApp().getInputManager().addListener(clickListener, CLICK);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disables the editor state by detaching nodes and removing listeners.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void disableState() {
 | 
			
		||||
        getApp().getGuiNode().detachChild(editorNode);
 | 
			
		||||
        getApp().getInputManager().removeListener(clickListener);
 | 
			
		||||
        if (ownMapView != null) {
 | 
			
		||||
            ownMapView.unregister();
 | 
			
		||||
            ownMapView = null;
 | 
			
		||||
        }
 | 
			
		||||
        if (harborView != null) {
 | 
			
		||||
            harborView.unregister();
 | 
			
		||||
            harborView = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the editor state, handling cursor movement and enabling buttons.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf Time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        super.update(tpf);
 | 
			
		||||
        cursorMovement();
 | 
			
		||||
        enableButtons();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enables or disables buttons based on the logic state.
 | 
			
		||||
     */
 | 
			
		||||
    private void enableButtons() {
 | 
			
		||||
        readyButton.setEnabled(getGameLogic().isMapComplete());
 | 
			
		||||
        rotateButton.setEnabled(getGameLogic().movingShip());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles cursor movement for previewing ship placement.
 | 
			
		||||
     */
 | 
			
		||||
    private void cursorMovement() {
 | 
			
		||||
        if (!getGameLogic().movingShip() || ownMapView == null || showsDialog())
 | 
			
		||||
            return;
 | 
			
		||||
        final Vector2f cursorPosition = getApp().getInputManager().getCursorPosition();
 | 
			
		||||
        if (!cursorPosition.equals(oldCursorPosition)) {
 | 
			
		||||
            oldCursorPosition = new Vector2f(cursorPosition);
 | 
			
		||||
            getGameLogic().movePreview(ownMapView.mouseToModel(cursorPosition));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the GUI components for the editor.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeGuiComponents() {
 | 
			
		||||
        ownMapView = new MapView(getGameLogic().getOwnMap(), getApp());
 | 
			
		||||
        harborView = new MapView(getGameLogic().getHarbor(), getApp());
 | 
			
		||||
        ownMapView.addGrid();
 | 
			
		||||
        rotateButton = new Button(lookup("button.rotate"));
 | 
			
		||||
        readyButton = new Button(lookup("button.ready"));
 | 
			
		||||
        rotateButton.addClickCommands(e -> {
 | 
			
		||||
            if (!showsDialog())
 | 
			
		||||
                getGameLogic().rotateShip();
 | 
			
		||||
        });
 | 
			
		||||
        readyButton.addClickCommands(e -> {
 | 
			
		||||
            if (!showsDialog())
 | 
			
		||||
                getGameLogic().mapFinished();
 | 
			
		||||
        });
 | 
			
		||||
        buttonContainer = new Container();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the GUI components to the editor node.
 | 
			
		||||
     */
 | 
			
		||||
    private void addGuiComponents() {
 | 
			
		||||
        buttonContainer.addChild(rotateButton, 0, 0);
 | 
			
		||||
        buttonContainer.addChild(readyButton, 0, 1);
 | 
			
		||||
        editorNode.attachChild(ownMapView.getNode());
 | 
			
		||||
        editorNode.attachChild(harborView.getNode());
 | 
			
		||||
        editorNode.attachChild(buttonContainer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Lays out the GUI components on the screen.
 | 
			
		||||
     */
 | 
			
		||||
    private void layoutGuiComponents() {
 | 
			
		||||
        final AppSettings s = getApp().getContext().getSettings();
 | 
			
		||||
        final float harborWidth = harborView.getWidth();
 | 
			
		||||
        final float harborHeight = harborView.getHeight();
 | 
			
		||||
        final float ownMapWidth = ownMapView.getWidth();
 | 
			
		||||
        final float ownMapHeight = ownMapView.getHeight();
 | 
			
		||||
        final float windowWidth = s.getWidth();
 | 
			
		||||
        final float windowHeight = s.getHeight();
 | 
			
		||||
 | 
			
		||||
        ownMapView.getNode()
 | 
			
		||||
                  .setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP),
 | 
			
		||||
                                       0.5f * (windowHeight - ownMapHeight),
 | 
			
		||||
                                       DEPTH);
 | 
			
		||||
 | 
			
		||||
        harborView.getNode()
 | 
			
		||||
                  .setLocalTranslation(0.5f * (windowWidth - harborWidth + ownMapWidth + GAP),
 | 
			
		||||
                                       0.5f * (windowHeight - harborHeight),
 | 
			
		||||
                                       DEPTH);
 | 
			
		||||
 | 
			
		||||
        buttonContainer.setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP),
 | 
			
		||||
                                            0.5f * (windowHeight - ownMapHeight - GAP),
 | 
			
		||||
                                            DEPTH);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles click events to place or rotate ships on the maps.
 | 
			
		||||
     */
 | 
			
		||||
    private void click(boolean isPressed) {
 | 
			
		||||
        if (!isPressed || showsDialog()) return;
 | 
			
		||||
        final Vector2f cursorPos = getApp().getInputManager().getCursorPosition();
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS
 | 
			
		||||
        getGameLogic().clickHarbor(harborView.mouseToModel(cursorPos));
 | 
			
		||||
        getGameLogic().clickOwnMap(ownMapView.mouseToModel(cursorPos));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,191 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState.BlendMode;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial.CullHint;
 | 
			
		||||
import com.jme3.scene.shape.Quad;
 | 
			
		||||
import pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.util.FloatPoint;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the visual view of a {@link ShipMap}, used to display the map structure such as the player's map, harbor,
 | 
			
		||||
 * and opponent's map. This class handles the graphical representation of the map, including background setup, grid lines,
 | 
			
		||||
 * and interaction between the model and the view.
 | 
			
		||||
 */
 | 
			
		||||
class MapView {
 | 
			
		||||
    private static final float FIELD_SIZE = 40f;
 | 
			
		||||
    private static final float GRID_LINE_WIDTH = 2f;
 | 
			
		||||
    private static final float BACKGROUND_DEPTH = -4f;
 | 
			
		||||
    private static final float GRID_DEPTH = -1f;
 | 
			
		||||
    private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0, 0.05f, 0.05f, 0.5f);
 | 
			
		||||
    private static final ColorRGBA GRID_COLOR = ColorRGBA.Green;
 | 
			
		||||
 | 
			
		||||
    // Reference to the main application and the ship map being visualized
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
    private final Node mapNode = new Node("map"); // NON-NLS
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final MapViewSynchronizer synchronizer;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new MapView for a given {@link ShipMap} and {@link BattleshipApp}.
 | 
			
		||||
     * Initializes the view by setting up the background and registering a synchronizer to listen to changes in the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map the ship map to visualize
 | 
			
		||||
     * @param app the main application instance
 | 
			
		||||
     */
 | 
			
		||||
    MapView(ShipMap map, BattleshipApp app) {
 | 
			
		||||
        this.map = map;
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.synchronizer = new MapViewSynchronizer(this);
 | 
			
		||||
        setupBackground();
 | 
			
		||||
        app.getGameLogic().addListener(synchronizer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Unregisters the {@link MapViewSynchronizer} from the listener list of the ClientGameLogic,
 | 
			
		||||
     * stopping the view from receiving updates when the underlying {@link ShipMap} changes.
 | 
			
		||||
     * After calling this method, this MapView instance should no longer be used.
 | 
			
		||||
     */
 | 
			
		||||
    void unregister() {
 | 
			
		||||
        app.getGameLogic().removeListener(synchronizer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the {@link ShipMap} associated with this view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the ship map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getMap() {
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the {@link BattleshipApp} instance associated with this view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the main application instance
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipApp getApp() {
 | 
			
		||||
        return app;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up the background of the map view using a quad geometry.
 | 
			
		||||
     * The background is configured with a semi-transparent color and placed at a specific depth.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupBackground() {
 | 
			
		||||
        final Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); // NON-NLS
 | 
			
		||||
        mat.setColor("Color", BACKGROUND_COLOR); // NON-NLS
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
			
		||||
        final Position corner = modelToView(map.getWidth(), map.getHeight());
 | 
			
		||||
        final Geometry background = new Geometry("MapBackground", new Quad(corner.getX(), corner.getY()));
 | 
			
		||||
        background.setMaterial(mat);
 | 
			
		||||
        background.setLocalTranslation(0f, 0f, BACKGROUND_DEPTH);
 | 
			
		||||
        background.setCullHint(CullHint.Never);
 | 
			
		||||
        mapNode.attachChild(background);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds grid lines to the map view to visually separate the fields within the map.
 | 
			
		||||
     * The grid lines are drawn based on the dimensions of the ship map.
 | 
			
		||||
     */
 | 
			
		||||
    public void addGrid() {
 | 
			
		||||
        for (int x = 0; x <= map.getWidth(); x++) {
 | 
			
		||||
            final Position f = modelToView(x, 0);
 | 
			
		||||
            final Position t = modelToView(x, map.getHeight());
 | 
			
		||||
            mapNode.attachChild(gridLine(f, t));
 | 
			
		||||
        }
 | 
			
		||||
        for (int y = 0; y <= map.getHeight(); y++) {
 | 
			
		||||
            final Position f = modelToView(0, y);
 | 
			
		||||
            final Position t = modelToView(map.getWidth(), y);
 | 
			
		||||
            mapNode.attachChild(gridLine(f, t));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the root node containing all visual elements in this map view.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the root node for the map view
 | 
			
		||||
     */
 | 
			
		||||
    public Node getNode() {
 | 
			
		||||
        return mapNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the total width of the map in view coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the map in view coordinates
 | 
			
		||||
     */
 | 
			
		||||
    public float getWidth() {
 | 
			
		||||
        return FIELD_SIZE * map.getWidth();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the total height of the map in view coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the map in view coordinates
 | 
			
		||||
     */
 | 
			
		||||
    public float getHeight() {
 | 
			
		||||
        return FIELD_SIZE * map.getHeight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts coordinates from view coordinates to model coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate in view space
 | 
			
		||||
     * @param y the y-coordinate in view space
 | 
			
		||||
     * @return the corresponding model coordinates as an {@link IntPoint}
 | 
			
		||||
     */
 | 
			
		||||
    public IntPoint viewToModel(float x, float y) {
 | 
			
		||||
        return new IntPoint((int) Math.floor(x / FIELD_SIZE), (int) Math.floor(y / FIELD_SIZE));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts coordinates from model coordinates to view coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate in model space
 | 
			
		||||
     * @param y the y-coordinate in model space
 | 
			
		||||
     * @return the corresponding view coordinates as a {@link Position}
 | 
			
		||||
     */
 | 
			
		||||
    public Position modelToView(float x, float y) {
 | 
			
		||||
        return new FloatPoint(x * FIELD_SIZE, y * FIELD_SIZE);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts the mouse position to model coordinates.
 | 
			
		||||
     * This method takes into account the map's transformation in the 3D scene.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the 2D vector representing the mouse position in the view
 | 
			
		||||
     * @return the corresponding model coordinates as an {@link IntPoint}
 | 
			
		||||
     */
 | 
			
		||||
    public IntPoint mouseToModel(Vector2f pos) {
 | 
			
		||||
        final Vector3f world = new Vector3f(pos.getX(), pos.getY(), 0f);
 | 
			
		||||
        final Vector3f view = mapNode.getWorldTransform().transformInverseVector(world, null);
 | 
			
		||||
        return viewToModel(view.getX(), view.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual representation of a grid line between two positions.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p1 the start position of the grid line
 | 
			
		||||
     * @param p2 the end position of the grid line
 | 
			
		||||
     * @return a {@link Geometry} representing the grid line
 | 
			
		||||
     */
 | 
			
		||||
    private Geometry gridLine(Position p1, Position p2) {
 | 
			
		||||
        return app.getDraw().makeFatLine(p1, p2, GRID_DEPTH, GRID_COLOR, GRID_LINE_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,125 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronizes the visual representation of the ship map with the game model.
 | 
			
		||||
 * It handles the rendering of ships and shots on the map view, updating the view
 | 
			
		||||
 * whenever changes occur in the model.
 | 
			
		||||
 */
 | 
			
		||||
class MapViewSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    // Constants for rendering properties
 | 
			
		||||
    private static final float SHIP_LINE_WIDTH = 6f;
 | 
			
		||||
    private static final float SHOT_DEPTH = -2f;
 | 
			
		||||
    private static final float SHIP_DEPTH = 0f;
 | 
			
		||||
    private static final float INDENT = 4f;
 | 
			
		||||
 | 
			
		||||
    // Colors used for different visual elements
 | 
			
		||||
    private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
 | 
			
		||||
    private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
 | 
			
		||||
    private static final ColorRGBA SHIP_BORDER_COLOR = ColorRGBA.White;
 | 
			
		||||
    private static final ColorRGBA PREVIEW_COLOR = ColorRGBA.Gray;
 | 
			
		||||
    private static final ColorRGBA ERROR_COLOR = ColorRGBA.Red;
 | 
			
		||||
 | 
			
		||||
    // The MapView associated with this synchronizer
 | 
			
		||||
    private final MapView view;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new MapViewSynchronizer for the given MapView.
 | 
			
		||||
     * Initializes the synchronizer and adds existing elements from the model to the view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param view the MapView to synchronize with the game model
 | 
			
		||||
     */
 | 
			
		||||
    public MapViewSynchronizer(MapView view) {
 | 
			
		||||
        super(view.getMap(), view.getNode());
 | 
			
		||||
        this.view = view;
 | 
			
		||||
        addExisting();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual representation of a shot on the map.
 | 
			
		||||
     * A hit shot is represented in red, while a miss is represented in blue.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the Shot object representing the shot in the model
 | 
			
		||||
     * @return a Spatial representing the shot on the map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shot shot) {
 | 
			
		||||
        // Convert the shot's model coordinates to view coordinates
 | 
			
		||||
        final Position p1 = view.modelToView(shot.getX(), shot.getY());
 | 
			
		||||
        final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1);
 | 
			
		||||
        final ColorRGBA color = shot.isHit() ? HIT_COLOR : MISS_COLOR;
 | 
			
		||||
 | 
			
		||||
        // Create and return a rectangle representing the shot
 | 
			
		||||
        return view.getApp().getDraw().makeRectangle(p1.getX(), p1.getY(),
 | 
			
		||||
                                                     SHOT_DEPTH,
 | 
			
		||||
                                                     p2.getX() - p1.getX(), p2.getY() - p1.getY(),
 | 
			
		||||
                                                     color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual representation of a battleship on the map.
 | 
			
		||||
     * The ship's border color depends on its status: normal, valid preview, or invalid preview.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship object representing the ship in the model
 | 
			
		||||
     * @return a Spatial representing the ship on the map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Battleship ship) {
 | 
			
		||||
        // Create a node to represent the ship
 | 
			
		||||
        final Node shipNode = new Node("ship"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
        // Convert the ship's model coordinates to view coordinates
 | 
			
		||||
        final Position p1 = view.modelToView(ship.getMinX(), ship.getMinY());
 | 
			
		||||
        final Position p2 = view.modelToView(ship.getMaxX() + 1, ship.getMaxY() + 1);
 | 
			
		||||
 | 
			
		||||
        // Calculate the coordinates for the ship's bounding box
 | 
			
		||||
        final float x1 = p1.getX() + INDENT;
 | 
			
		||||
        final float y1 = p1.getY() + INDENT;
 | 
			
		||||
        final float x2 = p2.getX() - INDENT;
 | 
			
		||||
        final float y2 = p2.getY() - INDENT;
 | 
			
		||||
 | 
			
		||||
        // Determine the color based on the ship's status
 | 
			
		||||
        final ColorRGBA color = switch (ship.getStatus()) {
 | 
			
		||||
            case NORMAL -> SHIP_BORDER_COLOR;
 | 
			
		||||
            case VALID_PREVIEW -> PREVIEW_COLOR;
 | 
			
		||||
            case INVALID_PREVIEW -> ERROR_COLOR;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Add the ship's borders to the node
 | 
			
		||||
        shipNode.attachChild(shipLine(x1, y1, x2, y1, color));
 | 
			
		||||
        shipNode.attachChild(shipLine(x1, y2, x2, y2, color));
 | 
			
		||||
        shipNode.attachChild(shipLine(x1, y1, x1, y2, color));
 | 
			
		||||
        shipNode.attachChild(shipLine(x2, y1, x2, y2, color));
 | 
			
		||||
 | 
			
		||||
        // Return the complete ship representation
 | 
			
		||||
        return shipNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a line geometry representing part of the ship's border.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x1    the starting x-coordinate of the line
 | 
			
		||||
     * @param y1    the starting y-coordinate of the line
 | 
			
		||||
     * @param x2    the ending x-coordinate of the line
 | 
			
		||||
     * @param y2    the ending y-coordinate of the line
 | 
			
		||||
     * @param color the color of the line
 | 
			
		||||
     * @return a Geometry representing the line
 | 
			
		||||
     */
 | 
			
		||||
    private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) {
 | 
			
		||||
        return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,203 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.Application;
 | 
			
		||||
import com.jme3.app.state.AppStateManager;
 | 
			
		||||
import com.jme3.asset.AssetManager;
 | 
			
		||||
import com.jme3.light.AmbientLight;
 | 
			
		||||
import com.jme3.light.DirectionalLight;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector2f;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.Camera;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Box;
 | 
			
		||||
import com.jme3.shadow.DirectionalLightShadowRenderer;
 | 
			
		||||
import com.jme3.shadow.EdgeFilteringMode;
 | 
			
		||||
import com.jme3.texture.Texture;
 | 
			
		||||
import com.jme3.util.SkyFactory;
 | 
			
		||||
import com.jme3.util.TangentBinormalGenerator;
 | 
			
		||||
import pp.battleship.client.BattleshipAppState;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.util.FloatMath;
 | 
			
		||||
 | 
			
		||||
import static pp.util.FloatMath.TWO_PI;
 | 
			
		||||
import static pp.util.FloatMath.cos;
 | 
			
		||||
import static pp.util.FloatMath.sin;
 | 
			
		||||
import static pp.util.FloatMath.sqrt;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages the rendering and visual aspects of the sea and sky in the Battleship game.
 | 
			
		||||
 * This state is responsible for setting up and updating the sea, sky, and lighting
 | 
			
		||||
 * conditions, and controls the camera to create a dynamic view of the game environment.
 | 
			
		||||
 */
 | 
			
		||||
public class SeaAppState extends BattleshipAppState {
 | 
			
		||||
    /**
 | 
			
		||||
     * The path to the sea texture material.
 | 
			
		||||
     */
 | 
			
		||||
    private static final String SEA_TEXTURE = "Textures/Terrain/Water/Water.j3m"; //NON-NLS
 | 
			
		||||
    private static final float ABOVE_SEA_LEVEL = 4f;
 | 
			
		||||
    private static final float INCLINATION = 2.5f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The root node for all visual elements in this state.
 | 
			
		||||
     */
 | 
			
		||||
    private final Node viewNode = new Node("view"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The node containing the scene elements, such as the sea surface.
 | 
			
		||||
     */
 | 
			
		||||
    private final Node sceneNode = new Node("scene"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Synchronizes the sea's visual representation with the game logic.
 | 
			
		||||
     */
 | 
			
		||||
    private SeaSynchronizer synchronizer;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The current angle of the camera around the center of the map.
 | 
			
		||||
     */
 | 
			
		||||
    private float cameraAngle;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the state by setting up the sky, lights, and other visual components.
 | 
			
		||||
     * This method is called when the state is first attached to the state manager.
 | 
			
		||||
     *
 | 
			
		||||
     * @param stateManager the state manager
 | 
			
		||||
     * @param application  the application
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void initialize(AppStateManager stateManager, Application application) {
 | 
			
		||||
        super.initialize(stateManager, application);
 | 
			
		||||
        viewNode.attachChild(sceneNode);
 | 
			
		||||
        setupLights();
 | 
			
		||||
        setupSky();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enables the sea and sky state, setting up the scene and registering any necessary listeners.
 | 
			
		||||
     * This method is called when the state is set to active.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void enableState() {
 | 
			
		||||
        sceneNode.detachAllChildren();
 | 
			
		||||
        setupScene();
 | 
			
		||||
        if (synchronizer == null) {
 | 
			
		||||
            synchronizer = new SeaSynchronizer(getApp(), sceneNode, getGameLogic().getOwnMap());
 | 
			
		||||
            getGameLogic().addListener(synchronizer);
 | 
			
		||||
        }
 | 
			
		||||
        getApp().getRootNode().attachChild(viewNode);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disables the sea and sky state, removing visual elements from the scene and unregistering listeners.
 | 
			
		||||
     * This method is called when the state is set to inactive.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void disableState() {
 | 
			
		||||
        getApp().getRootNode().detachChild(viewNode);
 | 
			
		||||
        if (synchronizer != null) {
 | 
			
		||||
            getGameLogic().removeListener(synchronizer);
 | 
			
		||||
            synchronizer = null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state each frame, moving the camera to simulate it circling around the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf the time per frame (seconds)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float tpf) {
 | 
			
		||||
        super.update(tpf);
 | 
			
		||||
        cameraAngle += TWO_PI * 0.05f * tpf;
 | 
			
		||||
        adjustCamera();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up the lighting for the scene, including directional and ambient lights.
 | 
			
		||||
     * Also configures shadows to enhance the visual depth of the scene.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupLights() {
 | 
			
		||||
        final AssetManager assetManager = getApp().getAssetManager();
 | 
			
		||||
        final DirectionalLightShadowRenderer shRend = new DirectionalLightShadowRenderer(assetManager, 2048, 3);
 | 
			
		||||
        shRend.setLambda(0.55f);
 | 
			
		||||
        shRend.setShadowIntensity(0.6f);
 | 
			
		||||
        shRend.setEdgeFilteringMode(EdgeFilteringMode.Bilinear);
 | 
			
		||||
        getApp().getViewPort().addProcessor(shRend);
 | 
			
		||||
 | 
			
		||||
        final DirectionalLight sun = new DirectionalLight();
 | 
			
		||||
        sun.setDirection(new Vector3f(-1f, -0.7f, -1f).normalizeLocal());
 | 
			
		||||
        viewNode.addLight(sun);
 | 
			
		||||
        shRend.setLight(sun);
 | 
			
		||||
 | 
			
		||||
        final AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.3f, 0.3f, 0.3f, 0f));
 | 
			
		||||
        viewNode.addLight(ambientLight);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up the sky in the scene using a skybox with textures for all six directions.
 | 
			
		||||
     * This creates a realistic and immersive environment for the sea.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupSky() {
 | 
			
		||||
        final AssetManager assetManager = getApp().getAssetManager();
 | 
			
		||||
        final Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); //NON-NLS
 | 
			
		||||
        final Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); //NON-NLS
 | 
			
		||||
        final Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); //NON-NLS
 | 
			
		||||
        final Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); //NON-NLS
 | 
			
		||||
        final Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); //NON-NLS
 | 
			
		||||
        final Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); //NON-NLS
 | 
			
		||||
        final Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
 | 
			
		||||
        sky.rotate(0f, FloatMath.PI, 0f);
 | 
			
		||||
        viewNode.attachChild(sky);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets up the sea surface in the scene. This includes creating the sea mesh,
 | 
			
		||||
     * applying textures, and enabling shadows.
 | 
			
		||||
     */
 | 
			
		||||
    private void setupScene() {
 | 
			
		||||
        final ShipMap ownMap = getGameLogic().getOwnMap();
 | 
			
		||||
        final float x = 0.5f * ownMap.getWidth();
 | 
			
		||||
        final float y = 0.5f * ownMap.getHeight();
 | 
			
		||||
        final Box seaMesh = new Box(y + 0.5f, 0.1f, x + 0.5f);
 | 
			
		||||
        final Geometry seaGeo = new Geometry("sea", seaMesh); //NON-NLS
 | 
			
		||||
        seaGeo.setLocalTranslation(new Vector3f(y, -0.1f, x));
 | 
			
		||||
        seaMesh.scaleTextureCoordinates(new Vector2f(4f, 4f));
 | 
			
		||||
        final Material seaMat = getApp().getAssetManager().loadMaterial(SEA_TEXTURE);
 | 
			
		||||
        seaGeo.setMaterial(seaMat);
 | 
			
		||||
        seaGeo.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
        TangentBinormalGenerator.generate(seaGeo);
 | 
			
		||||
        sceneNode.attachChild(seaGeo);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjusts the camera position and orientation to create a circular motion around
 | 
			
		||||
     * the center of the map. This provides a dynamic view of the sea and surrounding environment.
 | 
			
		||||
     */
 | 
			
		||||
    private void adjustCamera() {
 | 
			
		||||
        final ShipMap ownMap = getGameLogic().getOwnMap();
 | 
			
		||||
        final float mx = 0.5f * ownMap.getWidth();
 | 
			
		||||
        final float my = 0.5f * ownMap.getHeight();
 | 
			
		||||
        final float radius = 2f * sqrt(mx * mx + my + my);
 | 
			
		||||
        final float cos = radius * cos(cameraAngle);
 | 
			
		||||
        final float sin = radius * sin(cameraAngle);
 | 
			
		||||
        final float x = mx - cos;
 | 
			
		||||
        final float y = my - sin;
 | 
			
		||||
        final Camera camera = getApp().getCamera();
 | 
			
		||||
        camera.setLocation(new Vector3f(y, ABOVE_SEA_LEVEL, x));
 | 
			
		||||
        camera.getRotation().lookAt(new Vector3f(sin, -INCLINATION, cos),
 | 
			
		||||
                                    Vector3f.UNIT_Y);
 | 
			
		||||
        camera.update();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,213 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState.BlendMode;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Box;
 | 
			
		||||
import com.jme3.scene.shape.Cylinder;
 | 
			
		||||
import pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
import static pp.util.FloatMath.HALF_PI;
 | 
			
		||||
import static pp.util.FloatMath.PI;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code SeaSynchronizer} class is responsible for synchronizing the graphical
 | 
			
		||||
 * representation of the ships and shots on the sea map with the underlying data model.
 | 
			
		||||
 * It extends the {@link ShipMapSynchronizer} to provide specific synchronization
 | 
			
		||||
 * logic for the sea map.
 | 
			
		||||
 */
 | 
			
		||||
class SeaSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
 | 
			
		||||
    private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
 | 
			
		||||
    private static final String COLOR = "Color"; //NON-NLS
 | 
			
		||||
    private static final String SHIP = "ship"; //NON-NLS
 | 
			
		||||
    private static final String SHOT = "shot"; //NON-NLS
 | 
			
		||||
    private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
 | 
			
		||||
    private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
 | 
			
		||||
    private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
 | 
			
		||||
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final BattleshipApp app;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app  the Battleship application
 | 
			
		||||
     * @param root the root node to which graphical elements will be attached
 | 
			
		||||
     * @param map  the ship map containing the ships and shots
 | 
			
		||||
     */
 | 
			
		||||
    public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
 | 
			
		||||
        super(app.getGameLogic().getOwnMap(), root);
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.map = map;
 | 
			
		||||
        addExisting();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a {@link Shot} and creates a graphical representation of it.
 | 
			
		||||
     * If the shot is a hit, it attaches the representation to the ship node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the shot to be represented
 | 
			
		||||
     * @return the graphical representation of the shot, or null if the shot is a hit
 | 
			
		||||
     * and the representation has been attached to the ship node
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shot shot) {
 | 
			
		||||
        return shot.isHit() ? handleHit(shot) : createCylinder(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a hit by attaching its representation to the node that
 | 
			
		||||
     * contains the ship model as a child so that it moves with the ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot a hit
 | 
			
		||||
     * @return always null to prevent the representation from being attached
 | 
			
		||||
     * to the items node as well
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial handleHit(Shot shot) {
 | 
			
		||||
        final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
 | 
			
		||||
        final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
 | 
			
		||||
 | 
			
		||||
        final Geometry representation = createCylinder(shot);
 | 
			
		||||
        representation.getLocalTranslation().subtractLocal(shipNode.getLocalTranslation());
 | 
			
		||||
        shipNode.attachChild(representation);
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a cylinder geometry representing the specified shot.
 | 
			
		||||
     * The appearance of the cylinder depends on whether the shot is a hit or a miss.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the shot to be represented
 | 
			
		||||
     * @return the geometry representing the shot
 | 
			
		||||
     */
 | 
			
		||||
    private Geometry createCylinder(Shot shot) {
 | 
			
		||||
        final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR;
 | 
			
		||||
        final float height = shot.isHit() ? 1.2f : 0.1f;
 | 
			
		||||
 | 
			
		||||
        final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true);
 | 
			
		||||
        final Geometry geometry = new Geometry(SHOT, cylinder);
 | 
			
		||||
 | 
			
		||||
        geometry.setMaterial(createColoredMaterial(color));
 | 
			
		||||
        geometry.rotate(HALF_PI, 0f, 0f);
 | 
			
		||||
        // compute the center of the shot in world coordinates
 | 
			
		||||
        geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
			
		||||
 | 
			
		||||
        return geometry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a {@link Battleship} and creates a graphical representation of it.
 | 
			
		||||
     * The representation is either a 3D model or a simple box depending on the
 | 
			
		||||
     * type of battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the node containing the graphical representation of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Battleship ship) {
 | 
			
		||||
        final Node node = new Node(SHIP);
 | 
			
		||||
        node.attachChild(createShip(ship));
 | 
			
		||||
        // compute the center of the ship in world coordinates
 | 
			
		||||
        final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
 | 
			
		||||
        final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
 | 
			
		||||
        node.setLocalTranslation(x, 0f, z);
 | 
			
		||||
        node.addControl(new ShipControl(ship));
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the appropriate graphical representation of the specified battleship.
 | 
			
		||||
     * The representation is either a detailed model or a simple box based on the length of the ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createShip(Battleship ship) {
 | 
			
		||||
        return ship.getLength() == 4 ? createBattleship(ship) : createBox(ship);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a simple box to represent a battleship that is not of the "King George V" type.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the geometry representing the battleship as a box
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createBox(Battleship ship) {
 | 
			
		||||
        final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f,
 | 
			
		||||
                                0.3f,
 | 
			
		||||
                                0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f);
 | 
			
		||||
        final Geometry geometry = new Geometry(SHIP, box);
 | 
			
		||||
        geometry.setMaterial(createColoredMaterial(BOX_COLOR));
 | 
			
		||||
        geometry.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return geometry;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new {@link Material} with the specified color.
 | 
			
		||||
     * If the color includes transparency (i.e., alpha value less than 1),
 | 
			
		||||
     * the material's render state is set to use alpha blending, allowing for
 | 
			
		||||
     * semi-transparent rendering.
 | 
			
		||||
     *
 | 
			
		||||
     * @param color the {@link ColorRGBA} to be applied to the material. If the alpha value
 | 
			
		||||
     *              of the color is less than 1, the material will support transparency.
 | 
			
		||||
     * @return a {@link Material} instance configured with the specified color and,
 | 
			
		||||
     * if necessary, alpha blending enabled.
 | 
			
		||||
     */
 | 
			
		||||
    private Material createColoredMaterial(ColorRGBA color) {
 | 
			
		||||
        final Material material = new Material(app.getAssetManager(), UNSHADED);
 | 
			
		||||
        if (color.getAlpha() < 1f)
 | 
			
		||||
            material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
			
		||||
        material.setColor(COLOR, color);
 | 
			
		||||
        return material;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a "King George V" battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be represented
 | 
			
		||||
     * @return the spatial representing the "King George V" battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createBattleship(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(1.48f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculates the rotation angle for the specified rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rot the rotation of the battleship
 | 
			
		||||
     * @return the rotation angle in radians
 | 
			
		||||
     */
 | 
			
		||||
    private static float calculateRotationAngle(Rotation rot) {
 | 
			
		||||
        return switch (rot) {
 | 
			
		||||
            case RIGHT -> HALF_PI;
 | 
			
		||||
            case DOWN -> 0f;
 | 
			
		||||
            case LEFT -> -HALF_PI;
 | 
			
		||||
            case UP -> PI;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,106 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.Quaternion;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
 | 
			
		||||
import static pp.util.FloatMath.DEG_TO_RAD;
 | 
			
		||||
import static pp.util.FloatMath.TWO_PI;
 | 
			
		||||
import static pp.util.FloatMath.sin;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the oscillating pitch motion of a battleship model in the game.
 | 
			
		||||
 * The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
 | 
			
		||||
 */
 | 
			
		||||
class ShipControl extends AbstractControl {
 | 
			
		||||
    /**
 | 
			
		||||
     * The axis of rotation for the ship's pitch (tilting forward and backward).
 | 
			
		||||
     */
 | 
			
		||||
    private final Vector3f axis;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The duration of one complete oscillation cycle in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    private final float cycle;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The amplitude of the pitch oscillation in radians, determining how much the ship tilts.
 | 
			
		||||
     */
 | 
			
		||||
    private final float amplitude;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A quaternion representing the ship's current pitch rotation.
 | 
			
		||||
     */
 | 
			
		||||
    private final Quaternion pitch = new Quaternion();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The current time within the oscillation cycle, used to calculate the ship's pitch angle.
 | 
			
		||||
     */
 | 
			
		||||
    private float time;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ShipControl instance for the specified Battleship.
 | 
			
		||||
     * The ship's orientation determines the axis of rotation, while its length influences
 | 
			
		||||
     * the cycle duration and amplitude of the oscillation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship object to control
 | 
			
		||||
     */
 | 
			
		||||
    public ShipControl(Battleship ship) {
 | 
			
		||||
        // Determine the axis of rotation based on the ship's orientation
 | 
			
		||||
        axis = switch (ship.getRot()) {
 | 
			
		||||
            case LEFT, RIGHT -> Vector3f.UNIT_X;
 | 
			
		||||
            case UP, DOWN -> Vector3f.UNIT_Z;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        // Set the cycle duration and amplitude based on the ship's length
 | 
			
		||||
        cycle = ship.getLength() * 2f;
 | 
			
		||||
        amplitude = 5f * DEG_TO_RAD / ship.getLength();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted
 | 
			
		||||
     * to create a continuous tilting motion, simulating the effect of waves.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame (in seconds), used to calculate the new pitch angle
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        // If spatial is null, do nothing
 | 
			
		||||
        if (spatial == null) return;
 | 
			
		||||
 | 
			
		||||
        // Update the time within the oscillation cycle
 | 
			
		||||
        time = (time + tpf) % cycle;
 | 
			
		||||
 | 
			
		||||
        // Calculate the current angle of the oscillation
 | 
			
		||||
        final float angle = amplitude * sin(time * TWO_PI / cycle);
 | 
			
		||||
 | 
			
		||||
        // Update the pitch Quaternion with the new angle
 | 
			
		||||
        pitch.fromAngleAxis(angle, axis);
 | 
			
		||||
 | 
			
		||||
        // Apply the pitch rotation to the spatial
 | 
			
		||||
        spatial.setLocalRotation(pitch);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called during the rendering phase, but it does not perform any
 | 
			
		||||
     * operations in this implementation as the control only influences the spatial's
 | 
			
		||||
     * transformation, not its rendering process.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager rendering the controlled Spatial (not null)
 | 
			
		||||
     * @param vp the ViewPort being rendered (not null)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // No rendering logic is needed for this control
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import pp.battleship.model.Item;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.Visitor;
 | 
			
		||||
import pp.battleship.notification.GameEventListener;
 | 
			
		||||
import pp.battleship.notification.ItemAddedEvent;
 | 
			
		||||
import pp.battleship.notification.ItemRemovedEvent;
 | 
			
		||||
import pp.view.ModelViewSynchronizer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract base class for synchronizing the visual representation of a {@link ShipMap} with its model state.
 | 
			
		||||
 * This class handles the addition and removal of items from the ship map, ensuring that changes in the model
 | 
			
		||||
 * are accurately reflected in the view.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * Subclasses are responsible for providing the specific implementation of how each item in the map
 | 
			
		||||
 * is represented visually by implementing the {@link Visitor} interface.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
abstract class ShipMapSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener {
 | 
			
		||||
    // The ship map that this synchronizer is responsible for
 | 
			
		||||
    private final ShipMap shipMap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ShipMapSynchronizer.
 | 
			
		||||
     * Initializes the synchronizer with the provided ship map and the root node for attaching view representations.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map  the ship map to be synchronized
 | 
			
		||||
     * @param root the root node to which the view representations of the ship map items are attached
 | 
			
		||||
     */
 | 
			
		||||
    protected ShipMapSynchronizer(ShipMap map, Node root) {
 | 
			
		||||
        super(root);
 | 
			
		||||
        this.shipMap = map;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Translates a model item into its corresponding visual representation.
 | 
			
		||||
     * The specific visual representation is determined by the concrete implementation of the {@link Visitor} interface.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item the item from the model to be translated
 | 
			
		||||
     * @return the visual representation of the item as a {@link Spatial}
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected Spatial translate(Item item) {
 | 
			
		||||
        return item.accept(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds the existing items from the ship map to the view.
 | 
			
		||||
     * This method should be called during initialization to ensure that all current items in the ship map
 | 
			
		||||
     * are visually represented.
 | 
			
		||||
     */
 | 
			
		||||
    protected void addExisting() {
 | 
			
		||||
        shipMap.getItems().forEach(this::add);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the event when an item is removed from the ship map.
 | 
			
		||||
     * Removes the visual representation of the item from the view if it belongs to the synchronized ship map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the event indicating that an item has been removed from the ship map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(ItemRemovedEvent event) {
 | 
			
		||||
        if (shipMap == event.map())
 | 
			
		||||
            delete(event.item());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the event when an item is added to the ship map.
 | 
			
		||||
     * Adds the visual representation of the new item to the view if it belongs to the synchronized ship map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the event indicating that an item has been added to the ship map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(ItemAddedEvent event) {
 | 
			
		||||
        if (shipMap == event.map())
 | 
			
		||||
            add(event.item());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 235 KiB  | 
| 
		 Before Width: | Height: | Size: 94 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
water 002
 | 
			
		||||
by Katsukagi on 29/11/2018
 | 
			
		||||
https://3dtextures.me/2018/11/29/water-002/
 | 
			
		||||
@@ -1,14 +0,0 @@
 | 
			
		||||
Material Water : Common/MatDefs/Light/Lighting.j3md {
 | 
			
		||||
     MaterialParameters {
 | 
			
		||||
         Shininess            : 64
 | 
			
		||||
         DiffuseMap           : Repeat Textures/Terrain/Water/Water_002_COLOR.jpg
 | 
			
		||||
         NormalMap            : Repeat Textures/Terrain/Water/Water_002_NORM.jpg
 | 
			
		||||
         SpecularMap          : Repeat Textures/Terrain/Water/Water_002_ROUGH.jpg
 | 
			
		||||
         ParallaxMap          : Repeat Textures/Terrain/Water/Water_002_DISP.png
 | 
			
		||||
         // PackedNormalParallax : true
 | 
			
		||||
         // UseMaterialColors : true
 | 
			
		||||
         Ambient  : 0.5 0.5 0.5 1.0
 | 
			
		||||
         Diffuse  : 1.0 1.0 1.0 1.0
 | 
			
		||||
         Specular : 1.0 1.0 1.0 1.0
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
		 Before Width: | Height: | Size: 207 KiB  | 
| 
		 Before Width: | Height: | Size: 88 KiB  | 
| 
		 Before Width: | Height: | Size: 31 KiB  | 
| 
		 Before Width: | Height: | Size: 70 KiB  | 
@@ -1,16 +0,0 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'buildlogic.jme-application-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'Battleship converter for resources'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation libs.jme3.core
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.desktop
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass = 'pp.battleship.exporter.ModelExporter'
 | 
			
		||||
}
 | 
			
		||||
@@ -1,69 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.exporter;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.SimpleApplication;
 | 
			
		||||
import com.jme3.export.JmeExporter;
 | 
			
		||||
import com.jme3.export.binary.BinaryExporter;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.system.JmeContext;
 | 
			
		||||
import com.jme3.util.TangentBinormalGenerator;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class transforms models into j3o format.
 | 
			
		||||
 */
 | 
			
		||||
public class ModelExporter extends SimpleApplication {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ModelExporter.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The main method of the converter
 | 
			
		||||
     *
 | 
			
		||||
     * @param args input args
 | 
			
		||||
     */
 | 
			
		||||
    public static void main(String[] args) {
 | 
			
		||||
        ModelExporter application = new ModelExporter();
 | 
			
		||||
        application.start(JmeContext.Type.Headless);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Overrides  {@link com.jme3.app.SimpleApplication#simpleInitApp()}.
 | 
			
		||||
     * It initializes a simple app by exporting robots and rocks.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void simpleInitApp() {
 | 
			
		||||
        export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
        stop();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Exports spatial into a file
 | 
			
		||||
     *
 | 
			
		||||
     * @param fileName       the file name of the model
 | 
			
		||||
     * @param exportFileName the name of the file where the .j3o file is going to be stored
 | 
			
		||||
     */
 | 
			
		||||
    private void export(String fileName, String exportFileName) {
 | 
			
		||||
        final File file = new File(exportFileName);
 | 
			
		||||
        JmeExporter exporter = BinaryExporter.getInstance();
 | 
			
		||||
        try {
 | 
			
		||||
            final Spatial model = getAssetManager().loadModel(fileName);
 | 
			
		||||
            TangentBinormalGenerator.generate(model);
 | 
			
		||||
            exporter.save(model, file);
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException exception) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "write to {0} failed", file); //NON-NLS
 | 
			
		||||
            throw new RuntimeException(exception);
 | 
			
		||||
        }
 | 
			
		||||
        LOGGER.log(Level.INFO, "wrote file {0}", file); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 235 KiB  | 
@@ -1,18 +0,0 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 16.03.2012 14:15:53
 | 
			
		||||
 | 
			
		||||
newmtl _King_George_V
 | 
			
		||||
	Ns 60.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 1.0000 1.0000 1.0000
 | 
			
		||||
	Kd 1.0000 1.0000 1.0000
 | 
			
		||||
	Ks 0.4500 0.4500 0.4500
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka King_George_V.jpg
 | 
			
		||||
	map_Kd King_George_V.jpg
 | 
			
		||||
	map_bump King_George_V_bump.jpg
 | 
			
		||||
	bump King_George_V_bump.jpg
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 94 KiB  | 
@@ -1,3 +0,0 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -1,12 +0,0 @@
 | 
			
		||||
plugins {
 | 
			
		||||
    id 'buildlogic.java-library-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'Battleship common model'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api project(":common")
 | 
			
		||||
    api libs.jme3.networking
 | 
			
		||||
    implementation libs.gson
 | 
			
		||||
    testImplementation libs.mockito.core
 | 
			
		||||
}
 | 
			
		||||
@@ -1,103 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship;
 | 
			
		||||
 | 
			
		||||
import pp.util.config.Config;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
import java.util.TreeMap;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Math.max;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to the configuration settings for the Battleship game.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * This class allows for loading configuration settings from a properties file,
 | 
			
		||||
 * including the server port, map dimensions, and the number of ships of various lengths.
 | 
			
		||||
 * </p>
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>Note:</b> Attributes of this class are not marked as {@code final} to allow
 | 
			
		||||
 * for proper initialization when reading from a properties file.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipConfig extends Config {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The default port number for the Battleship server.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("port")
 | 
			
		||||
    private int port = 1234;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The width of the game map in terms of grid units.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.width")
 | 
			
		||||
    private int mapWidth = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The height of the game map in terms of grid units.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.height")
 | 
			
		||||
    private int mapHeight = 10;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * An array representing the number of ships available for each length.
 | 
			
		||||
     * The index corresponds to the ship length minus one, and the value at each index
 | 
			
		||||
     * is the number of ships of that length.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("ship.nums")
 | 
			
		||||
    private int[] shipNums = {4, 3, 2, 1};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an instance of {@code BattleshipConfig} with default settings.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipConfig() {
 | 
			
		||||
        // Default constructor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the port number configured for the Battleship server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the port number
 | 
			
		||||
     */
 | 
			
		||||
    public int getPort() {
 | 
			
		||||
        return port;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the width of the game map. The width is guaranteed to be at least 2 units.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getMapWidth() {
 | 
			
		||||
        return max(mapWidth, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the height of the game map. The height is guaranteed to be at least 2 units.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getMapHeight() {
 | 
			
		||||
        return max(mapHeight, 2);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a map representing the number of ships for each length.
 | 
			
		||||
     * The keys are ship lengths, and the values are the corresponding number of ships.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a map of ship lengths to the number of ships
 | 
			
		||||
     */
 | 
			
		||||
    public Map<Integer, Integer> getShipNums() {
 | 
			
		||||
        final TreeMap<Integer, Integer> ships = new TreeMap<>();
 | 
			
		||||
        for (int i = 0; i < shipNums.length; i++)
 | 
			
		||||
            if (shipNums[i] > 0)
 | 
			
		||||
                ships.put(i + 1, shipNums[i]);
 | 
			
		||||
        return ships;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship;
 | 
			
		||||
 | 
			
		||||
import java.util.ResourceBundle;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides access to the resource bundle of the game.
 | 
			
		||||
 *
 | 
			
		||||
 * @see #BUNDLE
 | 
			
		||||
 */
 | 
			
		||||
public class Resources {
 | 
			
		||||
    /**
 | 
			
		||||
     * The resource bundle for the Battleship game.
 | 
			
		||||
     */
 | 
			
		||||
    public static final ResourceBundle BUNDLE = ResourceBundle.getBundle("battleship"); //NON-NLS
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets a string for the given key from the resource bundle in {@linkplain #BUNDLE}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key for the desired string
 | 
			
		||||
     * @return the string for the given key
 | 
			
		||||
     * @throws NullPointerException               if {@code key} is {@code null}
 | 
			
		||||
     * @throws java.util.MissingResourceException if no object for the given key can be found
 | 
			
		||||
     * @throws ClassCastException                 if the object found for the given key is not a string
 | 
			
		||||
     */
 | 
			
		||||
    public static String lookup(String key) {
 | 
			
		||||
        return BUNDLE.getString(key);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Private constructor to prevent instantiation.
 | 
			
		||||
     */
 | 
			
		||||
    private Resources() { /* do not instantiate */ }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.notification.Sound;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client where players take turns to attack each other's ships.
 | 
			
		||||
 */
 | 
			
		||||
class BattleState extends ClientState {
 | 
			
		||||
    private boolean myTurn;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of {@link BattleState}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic  the game logic
 | 
			
		||||
     * @param myTurn true if it is my turn
 | 
			
		||||
     */
 | 
			
		||||
    public BattleState(ClientGameLogic logic, boolean myTurn) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
        this.myTurn = myTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        if (!myTurn)
 | 
			
		||||
            logic.setInfoText("wait.its.not.your.turn");
 | 
			
		||||
        else if (logic.getOpponentMap().isValid(pos))
 | 
			
		||||
            logic.send(new ShootMessage(pos));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEffect(EffectMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
 | 
			
		||||
        playSound(msg);
 | 
			
		||||
        myTurn = msg.isMyTurn();
 | 
			
		||||
        logic.setInfoText(msg.getInfoTextKey());
 | 
			
		||||
        affectedMap(msg).add(msg.getShot());
 | 
			
		||||
        if (destroyedOpponentShip(msg))
 | 
			
		||||
            logic.getOpponentMap().add(msg.getDestroyedShip());
 | 
			
		||||
        if (msg.isGameOver()) {
 | 
			
		||||
            msg.getRemainingOpponentShips().forEach(logic.getOwnMap()::add);
 | 
			
		||||
            logic.setState(new GameOverState(logic));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines which map (own or opponent's) should be affected by the shot based on the message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message received from the server
 | 
			
		||||
     * @return the map (either the opponent's or player's own map) that is affected by the shot
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap affectedMap(EffectMessage msg) {
 | 
			
		||||
        return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the opponent's ship was destroyed by the player's shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message received from the server
 | 
			
		||||
     * @return true if the shot destroyed an opponent's ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private boolean destroyedOpponentShip(EffectMessage msg) {
 | 
			
		||||
        return msg.getDestroyedShip() != null && msg.isOwnShot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
 | 
			
		||||
     * or destruction of a ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the effect message containing the result of the shot
 | 
			
		||||
     */
 | 
			
		||||
    private void playSound(EffectMessage msg) {
 | 
			
		||||
        if (!msg.getShot().isHit())
 | 
			
		||||
            logic.playSound(Sound.SPLASH);
 | 
			
		||||
        else if (msg.getDestroyedShip() == null)
 | 
			
		||||
            logic.playSound(Sound.EXPLOSION);
 | 
			
		||||
        else
 | 
			
		||||
            logic.playSound(Sound.DESTROYED_SHIP);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,38 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a Battleship client.
 | 
			
		||||
 * Provides methods to access game logic, configuration, and to enqueue tasks.
 | 
			
		||||
 */
 | 
			
		||||
public interface BattleshipClient {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the game logic associated with this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the ClientGameLogic instance
 | 
			
		||||
     */
 | 
			
		||||
    ClientGameLogic getGameLogic();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the configuration associated with this client.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the BattleshipConfig instance
 | 
			
		||||
     */
 | 
			
		||||
    BattleshipClientConfig getConfig();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Enqueues a task to be executed by the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param runnable the task to be executed
 | 
			
		||||
     */
 | 
			
		||||
    void enqueue(Runnable runnable);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,355 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.ServerInterpreter;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
import pp.battleship.notification.ClientStateEvent;
 | 
			
		||||
import pp.battleship.notification.GameEvent;
 | 
			
		||||
import pp.battleship.notification.GameEventBroker;
 | 
			
		||||
import pp.battleship.notification.GameEventListener;
 | 
			
		||||
import pp.battleship.notification.InfoTextEvent;
 | 
			
		||||
import pp.battleship.notification.Sound;
 | 
			
		||||
import pp.battleship.notification.SoundEvent;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Math.max;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the client-side game logic for Battleship.
 | 
			
		||||
 * Manages the player's ship placement, interactions with the map, and response to server messages.
 | 
			
		||||
 */
 | 
			
		||||
public class ClientGameLogic implements ServerInterpreter, GameEventBroker {
 | 
			
		||||
    static final Logger LOGGER = System.getLogger(ClientGameLogic.class.getName());
 | 
			
		||||
    private final ClientSender clientSender;
 | 
			
		||||
    private final List<GameEventListener> listeners = new ArrayList<>();
 | 
			
		||||
    private GameDetails details;
 | 
			
		||||
    private ShipMap ownMap;
 | 
			
		||||
    private ShipMap harbor;
 | 
			
		||||
    private ShipMap opponentMap;
 | 
			
		||||
    private ClientState state = new InitialState(this);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ClientGameLogic with the specified sender object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientSender the object used to send messages to the server
 | 
			
		||||
     */
 | 
			
		||||
    public ClientGameLogic(ClientSender clientSender) {
 | 
			
		||||
        this.clientSender = clientSender;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current state of the game logic.
 | 
			
		||||
     */
 | 
			
		||||
    ClientState getState() {
 | 
			
		||||
        return state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the current state of the game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param newState the new state to be set
 | 
			
		||||
     */
 | 
			
		||||
    void setState(ClientState newState) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state.getName(), newState.getName()); //NON-NLS
 | 
			
		||||
        state = newState;
 | 
			
		||||
        notifyListeners(new ClientStateEvent());
 | 
			
		||||
        state.entry();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the game details
 | 
			
		||||
     */
 | 
			
		||||
    GameDetails getDetails() {
 | 
			
		||||
        return details;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's own map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getOwnMap() {
 | 
			
		||||
        return ownMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the opponent's map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getOpponentMap() {
 | 
			
		||||
        return opponentMap;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the harbor map
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getHarbor() {
 | 
			
		||||
        return harbor;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return state.showEditor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return state.showBattle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(GameDetails details) {
 | 
			
		||||
        state.receivedGameDetails(details);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    public void movePreview(IntPoint pos) {
 | 
			
		||||
        state.movePreview(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        state.clickOwnMap(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickHarbor(IntPoint pos) {
 | 
			
		||||
        state.clickHarbor(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        state.clickOpponentMap(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    public void rotateShip() {
 | 
			
		||||
        state.rotateShip();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player's map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    public void mapFinished() {
 | 
			
		||||
        state.mapFinished();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isMapComplete() {
 | 
			
		||||
        return state.isMapComplete();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean movingShip() {
 | 
			
		||||
        return state.movingShip();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(StartBattleMessage msg) {
 | 
			
		||||
        state.receivedStartBattle(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(EffectMessage msg) {
 | 
			
		||||
        state.receivedEffect(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes the player's own map, opponent's map, and harbor based on the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    void initializeMaps(GameDetails details) {
 | 
			
		||||
        this.details = details;
 | 
			
		||||
        final int numShips = details.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
 | 
			
		||||
        final int maxLength = details.getShipNums().keySet().stream().mapToInt(Integer::intValue).max().orElse(2);
 | 
			
		||||
        ownMap = new ShipMap(details.getWidth(), details.getHeight(), this);
 | 
			
		||||
        opponentMap = new ShipMap(details.getWidth(), details.getHeight(), this);
 | 
			
		||||
        harbor = new ShipMap(max(maxLength, 2), max(numShips, details.getHeight()), this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the informational text to be displayed to the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the key for the info text
 | 
			
		||||
     */
 | 
			
		||||
    void setInfoText(String key) {
 | 
			
		||||
        notifyListeners(new InfoTextEvent(key));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Emits an event to play the specified sound.
 | 
			
		||||
     *
 | 
			
		||||
     * @param sound the sound to be played.
 | 
			
		||||
     */
 | 
			
		||||
    public void playSound(Sound sound) {
 | 
			
		||||
        notifyListeners(new SoundEvent(sound));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    public void loadMap(File file) throws IOException {
 | 
			
		||||
        state.loadMap(file);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be loaded from a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean mayLoadMap() {
 | 
			
		||||
        return state.mayLoadMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be saved to a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return state.maySaveMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the player's own map to the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to save the map to
 | 
			
		||||
     * @throws IOException if the map cannot be saved in the current state
 | 
			
		||||
     */
 | 
			
		||||
    public void saveMap(File file) throws IOException {
 | 
			
		||||
        if (ownMap != null && maySaveMap())
 | 
			
		||||
            new ShipMapDTO(ownMap).saveTo(file);
 | 
			
		||||
        else
 | 
			
		||||
            throw new IOException("You are not allowed to save the map in this state of the game");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message to be sent
 | 
			
		||||
     */
 | 
			
		||||
    void send(ClientMessage msg) {
 | 
			
		||||
        if (clientSender == null)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            clientSender.send(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a listener to receive game events.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the listener to add
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void addListener(GameEventListener listener) {
 | 
			
		||||
        listeners.add(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a listener from receiving game events.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the listener to remove
 | 
			
		||||
     */
 | 
			
		||||
    public synchronized void removeListener(GameEventListener listener) {
 | 
			
		||||
        listeners.remove(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies all listeners of a game event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the game event to notify listeners of
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListeners(GameEvent event) {
 | 
			
		||||
        final List<GameEventListener> copy;
 | 
			
		||||
        synchronized (this) {
 | 
			
		||||
            copy = new ArrayList<>(listeners);
 | 
			
		||||
        }
 | 
			
		||||
        for (GameEventListener listener : copy)
 | 
			
		||||
            event.notifyListener(listener);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called once per frame by the update loop.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta time in seconds since the last update call
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        state.update(delta);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,22 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for sending messages to the server.
 | 
			
		||||
 */
 | 
			
		||||
public interface ClientSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the specified message to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    void send(ClientMessage message);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,202 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines the behavior and state transitions for the client-side game logic.
 | 
			
		||||
 * Different states of the game logic implement this interface to handle various game events and actions.
 | 
			
		||||
 */
 | 
			
		||||
abstract class ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * The game logic object.
 | 
			
		||||
     */
 | 
			
		||||
    final ClientGameLogic logic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a client state of the specified game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    ClientState(ClientGameLogic logic) {
 | 
			
		||||
        this.logic = logic;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Method to be overridden by subclasses for post-transition initialization.
 | 
			
		||||
     * By default, it does nothing, but it can be overridden in derived states.
 | 
			
		||||
     */
 | 
			
		||||
    void entry() {
 | 
			
		||||
        // Default implementation does nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the name of the current state.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the name of the current state
 | 
			
		||||
     */
 | 
			
		||||
    String getName() {
 | 
			
		||||
        return getClass().getSimpleName();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean showEditor() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean showBattle() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean isMapComplete() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean movingShip() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickOwnMap has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickHarbor(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickHarbor has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "clickOpponentMap has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    void movePreview(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "movePreview has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    void rotateShip() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "rotateShip has no effect in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user has marked the map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    void mapFinished() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "mapFinished not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    void receivedGameDetails(GameDetails details) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedGameDetails not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    void receivedStartBattle(StartBattleMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedStartBattle not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Reports the effect of a shot based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message containing the effect of the shot
 | 
			
		||||
     */
 | 
			
		||||
    void receivedEffect(EffectMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName()); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if the map cannot be loaded in the current state
 | 
			
		||||
     */
 | 
			
		||||
    void loadMap(File file) throws IOException {
 | 
			
		||||
        throw new IOException("You are not allowed to load a map in this state of the game");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the own map may be loaded from file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean mayLoadMap() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the own map may be saved to file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    boolean maySaveMap() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called once per frame by the update loop if this state is active.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta time in seconds since the last update call
 | 
			
		||||
     */
 | 
			
		||||
    void update(float delta) { /* do nothing by default */ }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,267 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.NORMAL;
 | 
			
		||||
import static pp.battleship.model.Battleship.Status.VALID_PREVIEW;
 | 
			
		||||
import static pp.battleship.model.Rotation.RIGHT;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client setting up the ship map.
 | 
			
		||||
 */
 | 
			
		||||
class EditorState extends ClientState {
 | 
			
		||||
    private Battleship preview;
 | 
			
		||||
    private Battleship selectedInHarbor;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new EditorState with the specified ClientGameLogic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the ClientGameLogic associated with this state
 | 
			
		||||
     */
 | 
			
		||||
    public EditorState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true to indicate that the editor should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the editor should be shown
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the preview ship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position for the preview ship
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void movePreview(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
 | 
			
		||||
        if (preview == null || !ownMap().isValid(pos)) return;
 | 
			
		||||
        preview.moveTo(pos);
 | 
			
		||||
        setPreviewStatus(preview);
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOwnMap(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
 | 
			
		||||
        if (!ownMap().isValid(pos)) return;
 | 
			
		||||
        if (preview == null)
 | 
			
		||||
            modifyShip(pos);
 | 
			
		||||
        else
 | 
			
		||||
            placeShip(pos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Modifies a ship on the map at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cursor the position of the ship to modify
 | 
			
		||||
     */
 | 
			
		||||
    private void modifyShip(IntPoint cursor) {
 | 
			
		||||
        preview = ownMap().findShipAt(cursor);
 | 
			
		||||
        if (preview == null)
 | 
			
		||||
            return;
 | 
			
		||||
        preview.moveTo(cursor);
 | 
			
		||||
        setPreviewStatus(preview);
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
        selectedInHarbor = new Battleship(preview.getLength(), 0, freeY(), RIGHT);
 | 
			
		||||
        selectedInHarbor.setStatus(VALID_PREVIEW);
 | 
			
		||||
        harbor().add(selectedInHarbor);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Places the preview ship at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param cursor the position to place the ship
 | 
			
		||||
     */
 | 
			
		||||
    private void placeShip(IntPoint cursor) {
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        preview.moveTo(cursor);
 | 
			
		||||
        if (ownMap().isValid(preview)) {
 | 
			
		||||
            preview.setStatus(NORMAL);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            preview = null;
 | 
			
		||||
            selectedInHarbor = null;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            preview.setStatus(INVALID_PREVIEW);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickHarbor(IntPoint pos) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
 | 
			
		||||
        if (!harbor().isValid(pos)) return;
 | 
			
		||||
        final Battleship shipAtCursor = harbor().findShipAt(pos);
 | 
			
		||||
        if (preview != null) {
 | 
			
		||||
            ownMap().remove(preview);
 | 
			
		||||
            selectedInHarbor.setStatus(NORMAL);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            harbor().add(selectedInHarbor);
 | 
			
		||||
            preview = null;
 | 
			
		||||
            selectedInHarbor = null;
 | 
			
		||||
        }
 | 
			
		||||
        else if (shipAtCursor != null) {
 | 
			
		||||
            selectedInHarbor = shipAtCursor;
 | 
			
		||||
            selectedInHarbor.setStatus(VALID_PREVIEW);
 | 
			
		||||
            harbor().remove(selectedInHarbor);
 | 
			
		||||
            harbor().add(selectedInHarbor);
 | 
			
		||||
            preview = new Battleship(selectedInHarbor.getLength(), 0, 0, RIGHT);
 | 
			
		||||
            setPreviewStatus(preview);
 | 
			
		||||
            ownMap().add(preview);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the preview ship.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void rotateShip() {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
 | 
			
		||||
        if (preview == null) return;
 | 
			
		||||
        preview.rotated();
 | 
			
		||||
        ownMap().remove(preview);
 | 
			
		||||
        ownMap().add(preview);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds a free position in the harbor to place a ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the y coordinate of a free position in the harbor
 | 
			
		||||
     */
 | 
			
		||||
    private int freeY() {
 | 
			
		||||
        for (int i = 0; i < harbor().getHeight(); i++)
 | 
			
		||||
            if (harbor().findShipAt(0, i) == null)
 | 
			
		||||
                return i;
 | 
			
		||||
        throw new RuntimeException("Cannot find a free slot in harbor");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the status of the specified ship based on its validity.
 | 
			
		||||
     */
 | 
			
		||||
    private void setPreviewStatus(Battleship ship) {
 | 
			
		||||
        ship.setStatus(ownMap().isValid(ship) ? VALID_PREVIEW : INVALID_PREVIEW);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The user has marked the map as finished.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void mapFinished() {
 | 
			
		||||
        if (!harbor().getItems().isEmpty()) return;
 | 
			
		||||
        logic.send(new MapMessage(ownMap().getRemainingShips()));
 | 
			
		||||
        logic.setInfoText("wait.for.opponent");
 | 
			
		||||
        logic.setState(new WaitState(logic));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's map is complete (i.e., all ships are placed).
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if all ships are placed, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isMapComplete() {
 | 
			
		||||
        return harbor().getItems().isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if there is currently a preview ship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if there is currently a preview ship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean movingShip() {
 | 
			
		||||
        return preview != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's own map
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap ownMap() {
 | 
			
		||||
        return logic.getOwnMap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the harbor map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the harbor map
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMap harbor() {
 | 
			
		||||
        return logic.getHarbor();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a map from the specified file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to load the map from
 | 
			
		||||
     * @throws IOException if the map cannot be loaded
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void loadMap(File file) throws IOException {
 | 
			
		||||
        final ShipMapDTO dto = ShipMapDTO.loadFrom(file);
 | 
			
		||||
        if (!dto.fits(logic.getDetails()))
 | 
			
		||||
            throw new IOException(lookup("map.doesnt.fit"));
 | 
			
		||||
        ownMap().clear();
 | 
			
		||||
        dto.getShips().forEach(ownMap()::add);
 | 
			
		||||
        harbor().clear();
 | 
			
		||||
        preview = null;
 | 
			
		||||
        selectedInHarbor = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be loaded from a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be loaded from file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean mayLoadMap() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the player's own map may be saved to a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the own map may be saved to file, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return harbor().getItems().isEmpty();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client when the game is over.
 | 
			
		||||
 */
 | 
			
		||||
class GameOverState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of GameOverState.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the client game logic
 | 
			
		||||
     */
 | 
			
		||||
    GameOverState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns true to indicate that the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
 | 
			
		||||
import java.util.Map.Entry;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client waiting for the
 | 
			
		||||
 * {@linkplain pp.battleship.message.server.GameDetails}
 | 
			
		||||
 * from the server.
 | 
			
		||||
 */
 | 
			
		||||
class InitialState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new initial state.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    public InitialState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the game details provided by the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedGameDetails(GameDetails details) {
 | 
			
		||||
        logic.initializeMaps(details);
 | 
			
		||||
        fillHarbor(details);
 | 
			
		||||
        logic.setInfoText(details.getInfoTextKey());
 | 
			
		||||
        logic.setState(new EditorState(logic));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fills the harbor with ships as specified by the game details.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details including map size and ships
 | 
			
		||||
     */
 | 
			
		||||
    private void fillHarbor(GameDetails details) {
 | 
			
		||||
        int y = 0;
 | 
			
		||||
        for (Entry<Integer, Integer> entry : details.getShipNums().entrySet()) {
 | 
			
		||||
            final int len = entry.getKey();
 | 
			
		||||
            final int num = entry.getValue();
 | 
			
		||||
            for (int i = 0; i < num; i++) {
 | 
			
		||||
                final Battleship ship = new Battleship(len, 0, y++, Rotation.RIGHT);
 | 
			
		||||
                logic.getHarbor().add(ship);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean maySaveMap() {
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a connection to the server.
 | 
			
		||||
 * Extends ClientSender to allow sending messages to the server.
 | 
			
		||||
 */
 | 
			
		||||
public interface ServerConnection extends ClientSender {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the client is currently connected to the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if connected, false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    boolean isConnected();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Establishes a connection to the server.
 | 
			
		||||
     */
 | 
			
		||||
    void connect();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Disconnects from the server.
 | 
			
		||||
     */
 | 
			
		||||
    void disconnect();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
class WaitState extends ClientState {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new instance of {@link WaitState}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic
 | 
			
		||||
     */
 | 
			
		||||
    public WaitState(ClientGameLogic logic) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showEditor() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the battle based on the server message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message indicating whose turn it is to shoot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedStartBattle(StartBattleMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.INFO, "start battle, {0} turn", msg.isMyTurn() ? "my" : "other's"); //NON-NLS
 | 
			
		||||
        logic.setInfoText(msg.getInfoTextKey());
 | 
			
		||||
        logic.setState(new BattleState(logic, msg.isMyTurn()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,54 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class representing a player
 | 
			
		||||
 */
 | 
			
		||||
public class Player {
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
    private final String name;
 | 
			
		||||
    private final int id;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates new Player
 | 
			
		||||
     *
 | 
			
		||||
     * @param id     the id of the connection to the client represented by this player
 | 
			
		||||
     * @param name   the human-readable name of this player
 | 
			
		||||
     * @param config model holding the player
 | 
			
		||||
     */
 | 
			
		||||
    Player(int id, String name, BattleshipConfig config) {
 | 
			
		||||
        this.id = id;
 | 
			
		||||
        this.name = name;
 | 
			
		||||
        map = new ShipMap(config.getMapWidth(), config.getMapHeight(), null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the id of the connection to the client represented by this player.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the id
 | 
			
		||||
     */
 | 
			
		||||
    public int getId() {
 | 
			
		||||
        return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return String.format("Player(%s,%s)", name, id); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return map containing own ships and shots
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap getMap() {
 | 
			
		||||
        return map;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,220 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.ServerMessage;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the server-side game logic for Battleship.
 | 
			
		||||
 * Manages game states, player interactions, and message handling.
 | 
			
		||||
 */
 | 
			
		||||
public class ServerGameLogic implements ClientInterpreter {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ServerGameLogic.class.getName());
 | 
			
		||||
 | 
			
		||||
    private final BattleshipConfig config;
 | 
			
		||||
    private final List<Player> players = new ArrayList<>(2);
 | 
			
		||||
    private final Set<Player> readyPlayers = new HashSet<>();
 | 
			
		||||
    private final ServerSender serverSender;
 | 
			
		||||
    private Player activePlayer;
 | 
			
		||||
    private ServerState state = ServerState.WAIT;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ServerGameLogic with the specified sender and configuration.
 | 
			
		||||
     *
 | 
			
		||||
     * @param serverSender the sender used to send messages to clients
 | 
			
		||||
     * @param config       the game configuration
 | 
			
		||||
     */
 | 
			
		||||
    public ServerGameLogic(ServerSender serverSender, BattleshipConfig config) {
 | 
			
		||||
        this.serverSender = serverSender;
 | 
			
		||||
        this.config = config;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the  state of the game.
 | 
			
		||||
     */
 | 
			
		||||
    ServerState getState() {
 | 
			
		||||
        return state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the new state of the game and logs the state transition.
 | 
			
		||||
     *
 | 
			
		||||
     * @param newState the new state to set
 | 
			
		||||
     */
 | 
			
		||||
    void setState(ServerState newState) {
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "state transition {0} --> {1}", state, newState); //NON-NLS
 | 
			
		||||
        state = newState;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the opponent of the specified player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p the player
 | 
			
		||||
     * @return the opponent of the player
 | 
			
		||||
     */
 | 
			
		||||
    Player getOpponent(Player p) {
 | 
			
		||||
        if (players.size() != 2)
 | 
			
		||||
            throw new RuntimeException("trying to find opponent without having 2 players");
 | 
			
		||||
        final int index = players.indexOf(p);
 | 
			
		||||
        if (index < 0)
 | 
			
		||||
            throw new RuntimeException("Nonexistent player " + p);
 | 
			
		||||
        return players.get(1 - index);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the player representing the client with the specified connection ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id the ID of the client
 | 
			
		||||
     * @return the player associated with the client ID, or null if not found
 | 
			
		||||
     */
 | 
			
		||||
    public Player getPlayerById(int id) {
 | 
			
		||||
        for (Player player : players)
 | 
			
		||||
            if (player.getId() == id)
 | 
			
		||||
                return player;
 | 
			
		||||
        LOGGER.log(Level.ERROR, "no player found with connection {0}", id); //NON-NLS
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sends a message to the specified player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player to send the message to
 | 
			
		||||
     * @param msg    the message to send
 | 
			
		||||
     */
 | 
			
		||||
    void send(Player player, ServerMessage msg) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "sending to {0}: {1}", player, msg); //NON-NLS
 | 
			
		||||
        serverSender.send(player.getId(), msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a new player to the game if there are less than two players.
 | 
			
		||||
     * Transitions the state to SET_UP if two players are present.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id the connection ID of the new player
 | 
			
		||||
     * @return the player added to the game, or null if the game is not in the right state
 | 
			
		||||
     */
 | 
			
		||||
    public Player addPlayer(int id) {
 | 
			
		||||
        if (state != ServerState.WAIT) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "addPlayer not allowed in {0}", state); //NON-NLS
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        final int n = players.size() + 1;
 | 
			
		||||
        final Player player = new Player(id, "player " + n, config); //NON-NLS
 | 
			
		||||
        LOGGER.log(Level.INFO, "adding {0}", player); //NON-NLS
 | 
			
		||||
        players.add(player);
 | 
			
		||||
        if (players.size() == 2) {
 | 
			
		||||
            activePlayer = players.get(0);
 | 
			
		||||
            for (Player p : players)
 | 
			
		||||
                send(p, new GameDetails(config));
 | 
			
		||||
            setState(ServerState.SET_UP);
 | 
			
		||||
        }
 | 
			
		||||
        return player;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a MapMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received MapMessage
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(MapMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.SET_UP)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            playerReady(getPlayerById(from), msg.getShips());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a ShootMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received ShootMessage
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(ShootMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.BATTLE)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            shoot(getPlayerById(from), msg.getPosition());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player as ready and sets their ships.
 | 
			
		||||
     * Transitions the state to PLAY if both players are ready.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player who is ready
 | 
			
		||||
     * @param ships  the list of ships placed by the player
 | 
			
		||||
     */
 | 
			
		||||
    void playerReady(Player player, List<Battleship> ships) {
 | 
			
		||||
        if (!readyPlayers.add(player)) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "{0} was already ready", player); //NON-NLS
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        ships.forEach(player.getMap()::add);
 | 
			
		||||
        if (readyPlayers.size() == 2) {
 | 
			
		||||
            for (Player p : players)
 | 
			
		||||
                send(p, new StartBattleMessage(p == activePlayer));
 | 
			
		||||
            setState(ServerState.BATTLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the shooting action by the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @param p   the player who shot
 | 
			
		||||
     * @param pos the position of the shot
 | 
			
		||||
     */
 | 
			
		||||
    void shoot(Player p, IntPoint pos) {
 | 
			
		||||
        if (p != activePlayer) return;
 | 
			
		||||
        final Player otherPlayer = getOpponent(activePlayer);
 | 
			
		||||
        final Battleship selectedShip = otherPlayer.getMap().findShipAt(pos);
 | 
			
		||||
        if (selectedShip == null) {
 | 
			
		||||
            // shot missed
 | 
			
		||||
            send(activePlayer, EffectMessage.miss(true, pos));
 | 
			
		||||
            send(otherPlayer, EffectMessage.miss(false, pos));
 | 
			
		||||
            activePlayer = otherPlayer;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            // shot hit a ship
 | 
			
		||||
            selectedShip.hit(pos);
 | 
			
		||||
            if (otherPlayer.getMap().getRemainingShips().isEmpty()) {
 | 
			
		||||
                // game is over
 | 
			
		||||
                send(activePlayer, EffectMessage.won(pos, selectedShip));
 | 
			
		||||
                send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
 | 
			
		||||
                setState(ServerState.GAME_OVER);
 | 
			
		||||
            }
 | 
			
		||||
            else if (selectedShip.isDestroyed()) {
 | 
			
		||||
                // ship has been destroyed, but game is not yet over
 | 
			
		||||
                send(activePlayer, EffectMessage.shipDestroyed(true, pos, selectedShip));
 | 
			
		||||
                send(otherPlayer, EffectMessage.shipDestroyed(false, pos, selectedShip));
 | 
			
		||||
            }
 | 
			
		||||
            else {
 | 
			
		||||
                // ship has been hit, but it hasn't been destroyed
 | 
			
		||||
                send(activePlayer, EffectMessage.hit(true, pos));
 | 
			
		||||
                send(otherPlayer, EffectMessage.hit(false, pos));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.server.ServerMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface for sending messages to a client.
 | 
			
		||||
 */
 | 
			
		||||
public interface ServerSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the specified message to the client.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id      the id of the client that shall receive the message
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    void send(int id, ServerMessage message);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the different states of the Battleship server during the game lifecycle.
 | 
			
		||||
 */
 | 
			
		||||
enum ServerState {
 | 
			
		||||
    /**
 | 
			
		||||
     * The server is waiting for clients to connect.
 | 
			
		||||
     */
 | 
			
		||||
    WAIT,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The server is waiting for clients to set up their maps.
 | 
			
		||||
     */
 | 
			
		||||
    SET_UP,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The battle of the game where players take turns to attack each other's ships.
 | 
			
		||||
     */
 | 
			
		||||
    BATTLE,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The game has ended because all the ships of one player have been destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    GAME_OVER
 | 
			
		||||
}
 | 
			
		||||
@@ -1,114 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Class providing access to the Battleship client configuration.
 | 
			
		||||
 * Extends {@link BattleshipConfig} to include additional properties specific to the client.
 | 
			
		||||
 * This class manages configuration settings related to the RobotClient's behavior
 | 
			
		||||
 * and the game maps used in single mode.
 | 
			
		||||
 * <p>
 | 
			
		||||
 * <b>Note:</b> Attributes of this class should not be marked as {@code final}
 | 
			
		||||
 * to ensure proper functionality when reading from a properties file.
 | 
			
		||||
 * </p>
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipClientConfig extends BattleshipConfig {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Array representing the predefined shooting locations for the RobotClient.
 | 
			
		||||
     * The array stores coordinates in pairs, where even indices represent x-coordinates
 | 
			
		||||
     * and odd indices represent y-coordinates.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("robot.targets")
 | 
			
		||||
    private int[] robotTargets = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The delay (in milliseconds) between shots fired by the RobotClient.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("robot.delay")
 | 
			
		||||
    private int delay = 750;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the file representing the opponent's map.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.opponent")
 | 
			
		||||
    private String opponentMap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Path to the file representing the player's own map.
 | 
			
		||||
     */
 | 
			
		||||
    @Property("map.own")
 | 
			
		||||
    private String ownMap;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a default {@code BattleshipClientConfig} with predefined values.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipClientConfig() {
 | 
			
		||||
        // Default constructor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an iterator of {@link IntPoint} objects representing the predefined
 | 
			
		||||
     * shooting locations for the RobotClient.
 | 
			
		||||
     *
 | 
			
		||||
     * @return an iterator of {@code IntPoint} representing the shooting locations.
 | 
			
		||||
     */
 | 
			
		||||
    public Iterator<IntPoint> getRobotTargets() {
 | 
			
		||||
        List<IntPoint> targets = new ArrayList<>();
 | 
			
		||||
        for (int i = 0; i < robotTargets.length; i += 2) {
 | 
			
		||||
            int x = robotTargets[i];
 | 
			
		||||
            int y = robotTargets[i + 1];
 | 
			
		||||
            targets.add(new IntPoint(x, y));
 | 
			
		||||
        }
 | 
			
		||||
        return targets.iterator();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the delay (in milliseconds) between shots by the RobotClient.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the delay in milliseconds.
 | 
			
		||||
     */
 | 
			
		||||
    public int getDelay() {
 | 
			
		||||
        return delay;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the file representing the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the opponent's map file, or {@code null} if not set.
 | 
			
		||||
     */
 | 
			
		||||
    public File getOpponentMap() {
 | 
			
		||||
        return opponentMap == null ? null : new File(opponentMap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the file representing the player's own map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the player's own map file, or {@code null} if not set.
 | 
			
		||||
     */
 | 
			
		||||
    public File getOwnMap() {
 | 
			
		||||
        return ownMap == null ? null : new File(ownMap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the game is in single mode based on the presence of an opponent map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return {@code true} if the opponent map is set, indicating single mode; {@code false} otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isSingleMode() {
 | 
			
		||||
        return opponentMap != null;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,75 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The {@code Copycat} class is a utility that creates a copy of a {@link ClientMessage}.
 | 
			
		||||
 * It implements the {@link ClientInterpreter} interface to interpret and process
 | 
			
		||||
 * different types of messages.
 | 
			
		||||
 */
 | 
			
		||||
class Copycat implements ClientInterpreter {
 | 
			
		||||
    private ClientMessage copiedMessage;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a copy of the provided {@link ClientMessage}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the message to be copied
 | 
			
		||||
     * @return a copy of the provided message
 | 
			
		||||
     */
 | 
			
		||||
    static ClientMessage copy(ClientMessage msg) {
 | 
			
		||||
        final Copycat copycat = new Copycat();
 | 
			
		||||
        msg.accept(copycat, 0);
 | 
			
		||||
        return copycat.copiedMessage;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Private constructor to prevent direct instantiation of {@code Copycat}.
 | 
			
		||||
     */
 | 
			
		||||
    private Copycat() { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a {@link ShootMessage}.
 | 
			
		||||
     * Since a {@code ShootMessage} does not need to be copied, it is directly assigned.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received {@code ShootMessage}
 | 
			
		||||
     * @param from the identifier of the sender
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(ShootMessage msg, int from) {
 | 
			
		||||
        // copying is not necessary
 | 
			
		||||
        copiedMessage = msg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a {@link MapMessage}.
 | 
			
		||||
     * Creates a deep copy of the {@code MapMessage} by copying each {@link Battleship} in the message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received {@code MapMessage}
 | 
			
		||||
     * @param from the identifier of the sender
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(MapMessage msg, int from) {
 | 
			
		||||
        copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a copy of the provided {@link Battleship}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be copied
 | 
			
		||||
     * @return a copy of the provided battleship
 | 
			
		||||
     */
 | 
			
		||||
    private static Battleship copy(Battleship ship) {
 | 
			
		||||
        return new Battleship(ship.getLength(), ship.getX(), ship.getY(), ship.getRot());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.client.BattleshipClient;
 | 
			
		||||
import pp.battleship.game.client.ClientGameLogic;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.ServerInterpreter;
 | 
			
		||||
import pp.battleship.message.server.ServerMessage;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A proxy class that interprets messages from the server and forwards them to the BattleshipClient.
 | 
			
		||||
 * Implements the ServerInterpreter interface to handle specific server messages.
 | 
			
		||||
 */
 | 
			
		||||
class InterpreterProxy implements ServerInterpreter {
 | 
			
		||||
    private final BattleshipClient playerClient;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an InterpreterProxy with the specified BattleshipClient.
 | 
			
		||||
     *
 | 
			
		||||
     * @param playerClient the client to which the server messages are forwarded
 | 
			
		||||
     */
 | 
			
		||||
    InterpreterProxy(BattleshipClient playerClient) {
 | 
			
		||||
        this.playerClient = playerClient;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the received GameDetails message by accepting it with the client's game logic.
 | 
			
		||||
     * If the client's own map option is set, it also loads the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the GameDetails message received from the server
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(GameDetails msg) {
 | 
			
		||||
        msg.accept(playerClient.getGameLogic());
 | 
			
		||||
        if (playerClient.getConfig().getOwnMap() != null)
 | 
			
		||||
            playerClient.enqueue(this::loadMap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the map specified in the client's options and notifies the game logic that the map is finished.
 | 
			
		||||
     *
 | 
			
		||||
     * @throws RuntimeException if the map fails to load.
 | 
			
		||||
     */
 | 
			
		||||
    private void loadMap() {
 | 
			
		||||
        final ClientGameLogic clientGameLogic = playerClient.getGameLogic();
 | 
			
		||||
        try {
 | 
			
		||||
            clientGameLogic.loadMap(playerClient.getConfig().getOwnMap());
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException("Failed to load PlayerClient map", e);
 | 
			
		||||
        }
 | 
			
		||||
        clientGameLogic.mapFinished();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the received StartBattleMessage to the client's game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the StartBattleMessage received from the server
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(StartBattleMessage msg) {
 | 
			
		||||
        forward(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the received EffectMessage to the client's game logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the EffectMessage received from the server
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(EffectMessage msg) {
 | 
			
		||||
        forward(msg);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the specified ServerMessage to the client's game logic by enqueuing the message acceptance.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the ServerMessage to forward
 | 
			
		||||
     */
 | 
			
		||||
    private void forward(ServerMessage msg) {
 | 
			
		||||
        playerClient.enqueue(() -> msg.accept(playerClient.getGameLogic()));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,127 +0,0 @@
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.client.BattleshipClient;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.message.server.ServerInterpreter;
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
import pp.util.RandomPositionIterator;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.Timer;
 | 
			
		||||
import java.util.TimerTask;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.Resources.lookup;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * RobotClient simulates a client in the Battleship game.
 | 
			
		||||
 * It handles its own shooting targets and acts as a sender of client messages.
 | 
			
		||||
 * The RobotClient can shoot at predefined targets or generate random targets if predefined ones are exhausted.
 | 
			
		||||
 */
 | 
			
		||||
class RobotClient implements ServerInterpreter {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(RobotClient.class.getName());
 | 
			
		||||
    private final BattleshipClient app;
 | 
			
		||||
    private final ServerConnectionMockup connection;
 | 
			
		||||
    private final BattleshipClientConfig config;
 | 
			
		||||
    private final ShipMapDTO dto;
 | 
			
		||||
    private final Iterator<IntPoint> targetIterator;
 | 
			
		||||
    private final Iterator<IntPoint> randomPositionIterator;
 | 
			
		||||
    private final Set<IntPoint> shotTargets = new HashSet<>();
 | 
			
		||||
    private final Timer timer = new Timer(true);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a RobotClient instance with the given connection and configuration.
 | 
			
		||||
     * Initializes the shooting targets from the configuration.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app        The BattleshipApp instance, used to enqueue actions on the main thread
 | 
			
		||||
     * @param connection The ServerConnectionMockup instance
 | 
			
		||||
     * @param config     The BattleshipClientConfig instance
 | 
			
		||||
     * @param dto        The ShipMap dto specified at app start
 | 
			
		||||
     */
 | 
			
		||||
    RobotClient(BattleshipClient app, ServerConnectionMockup connection, BattleshipClientConfig config, ShipMapDTO dto) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.connection = connection;
 | 
			
		||||
        this.config = config;
 | 
			
		||||
        this.dto = dto;
 | 
			
		||||
        this.targetIterator = config.getRobotTargets();
 | 
			
		||||
        this.randomPositionIterator = new RandomPositionIterator<>(IntPoint::new, config.getMapWidth(), config.getMapHeight());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Schedules the RobotClient to take a shot after the specified delay.
 | 
			
		||||
     */
 | 
			
		||||
    private void shoot() {
 | 
			
		||||
        timer.schedule(new TimerTask() {
 | 
			
		||||
            @Override
 | 
			
		||||
            public void run() {
 | 
			
		||||
                app.enqueue(RobotClient.this::robotShot);
 | 
			
		||||
            }
 | 
			
		||||
        }, config.getDelay());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Makes the RobotClient take a shot by sending a ShootMessage with the target position.
 | 
			
		||||
     */
 | 
			
		||||
    private void robotShot() {
 | 
			
		||||
        connection.sendRobotMessage(new ShootMessage(getShotPosition()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines the next shot position. If predefined targets are available, uses the next target.
 | 
			
		||||
     * Otherwise, generates a random target that has not been shot at before.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the next shot position as IntPosition
 | 
			
		||||
     */
 | 
			
		||||
    private IntPoint getShotPosition() {
 | 
			
		||||
        while (true) {
 | 
			
		||||
            final IntPoint target = targetIterator.hasNext() ? targetIterator.next() : randomPositionIterator.next();
 | 
			
		||||
            if (shotTargets.add(target)) return target;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receives GameDetails, creates and sends the ShipMap to the mock server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details The game details
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(GameDetails details) {
 | 
			
		||||
        if (!dto.fits(details))
 | 
			
		||||
            throw new RuntimeException(lookup("map.doesnt.fit"));
 | 
			
		||||
        app.enqueue(() -> connection.sendRobotMessage(new MapMessage(dto.getShips())));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receives the StartBattleMessage and updates the turn status.
 | 
			
		||||
     * If it is RobotClient's turn to shoot, schedules a shot using shoot();
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg The start battle message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(StartBattleMessage msg) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Received StartBattleMessage: {0}", msg); //NON-NLS
 | 
			
		||||
        if (msg.isMyTurn())
 | 
			
		||||
            shoot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Receives an effect message, logs it, and updates the turn status.
 | 
			
		||||
     * If it is RobotClient's turn to shoot, schedules a shot using shoot();
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg The effect message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(EffectMessage msg) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS
 | 
			
		||||
        if (msg.isMyTurn())
 | 
			
		||||
            shoot();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,145 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.client.BattleshipClient;
 | 
			
		||||
import pp.battleship.game.client.ServerConnection;
 | 
			
		||||
import pp.battleship.game.server.ServerGameLogic;
 | 
			
		||||
import pp.battleship.game.server.ServerSender;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.server.ServerInterpreter;
 | 
			
		||||
import pp.battleship.message.server.ServerMessage;
 | 
			
		||||
import pp.battleship.model.dto.ShipMapDTO;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import static pp.battleship.game.singlemode.Copycat.copy;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A mock implementation of the ServerConnection interface for single mode.
 | 
			
		||||
 * Simulates a server connection without actual network communication.
 | 
			
		||||
 * Handles a mock player named RobotClient and schedules its shots when due.
 | 
			
		||||
 */
 | 
			
		||||
public class ServerConnectionMockup implements ServerConnection, ServerSender {
 | 
			
		||||
    /**
 | 
			
		||||
     * The id of the player client.
 | 
			
		||||
     */
 | 
			
		||||
    private static final int PLAYER_CLIENT = 1;
 | 
			
		||||
    /**
 | 
			
		||||
     * The id of the robot client.
 | 
			
		||||
     */
 | 
			
		||||
    private static final int ROBOT_CLIENT = 2;
 | 
			
		||||
 | 
			
		||||
    private final BattleshipClient playerClient;
 | 
			
		||||
    private final RobotClient robotClient;
 | 
			
		||||
    private final InterpreterProxy playerProxy;
 | 
			
		||||
    private final ServerGameLogic serverGameLogic;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ServerConnectionMockup instance for the given Battleship application.
 | 
			
		||||
     * Creates a RobotClient instance and an instance of ServerGameLogic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param playerClient The Battleship client instance, e.g., a BattleshipApp instance.
 | 
			
		||||
     */
 | 
			
		||||
    public ServerConnectionMockup(BattleshipClient playerClient) {
 | 
			
		||||
        this.playerClient = playerClient;
 | 
			
		||||
        robotClient = new RobotClient(playerClient, this, playerClient.getConfig(), getShipMapDTO());
 | 
			
		||||
        serverGameLogic = new ServerGameLogic(this, playerClient.getConfig());
 | 
			
		||||
        playerProxy = new InterpreterProxy(playerClient);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Always returns true as this is a mock connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true, indicating the mock connection is always considered connected.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean isConnected() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Simulates connecting to a server by adding the PlayerClient and the RobotClient to the serverGameLogic.
 | 
			
		||||
     * Loads the map of the PlayerClient and triggers sending it to the serverGameLogic.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connect() {
 | 
			
		||||
        serverGameLogic.addPlayer(PLAYER_CLIENT);
 | 
			
		||||
        serverGameLogic.addPlayer(ROBOT_CLIENT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Does nothing upon shutdown of the app.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void disconnect() {
 | 
			
		||||
        //do nothing
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the specified message received from the player client to the server logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message from the player client to be processed.
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void send(ClientMessage message) { // from PlayerClient, as this is the PlayerClients 'serverConnection'
 | 
			
		||||
        copy(message).accept(serverGameLogic, PLAYER_CLIENT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the specified message received from the robot client to the server logic.
 | 
			
		||||
     *
 | 
			
		||||
     * @param message The message from the robot client to be processed.
 | 
			
		||||
     */
 | 
			
		||||
    void sendRobotMessage(ClientMessage message) {
 | 
			
		||||
        message.accept(serverGameLogic, ROBOT_CLIENT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Forwards the specified message received from the server logic either to the player client or to the
 | 
			
		||||
     * robot client, depending on the specified id.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id      The recipient id
 | 
			
		||||
     * @param message The server message to be processed
 | 
			
		||||
     * @see #PLAYER_CLIENT
 | 
			
		||||
     * @see #ROBOT_CLIENT
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void send(int id, ServerMessage message) {
 | 
			
		||||
        message.accept(getInterpreter(id));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the ServerInterpreter of the client with the specified id.
 | 
			
		||||
     *
 | 
			
		||||
     * @param clientId the id of the client whose ServerInterpreter shall be retrieved.
 | 
			
		||||
     * @return the ServerInterpreter of the client
 | 
			
		||||
     * @throws java.lang.IllegalArgumentException if there is no client with the specified id.
 | 
			
		||||
     */
 | 
			
		||||
    private ServerInterpreter getInterpreter(int clientId) {
 | 
			
		||||
        return switch (clientId) {
 | 
			
		||||
            case PLAYER_CLIENT -> playerProxy;
 | 
			
		||||
            case ROBOT_CLIENT -> robotClient;
 | 
			
		||||
            default -> throw new IllegalArgumentException("Unexpected value: " + clientId);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads the ShipMapDTO from the opponent map file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the loaded ShipMapDTO.
 | 
			
		||||
     */
 | 
			
		||||
    private ShipMapDTO getShipMapDTO() {
 | 
			
		||||
        try {
 | 
			
		||||
            return ShipMapDTO.loadFrom(playerClient.getConfig().getOpponentMap());
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            throw new RuntimeException("Failed to load RobotClient map", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.client;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Visitor interface for processing all client messages.
 | 
			
		||||
 */
 | 
			
		||||
public interface ClientInterpreter {
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes a received ShootMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the ShootMessage to be processed
 | 
			
		||||
     * @param from the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    void received(ShootMessage msg, int from);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes a received MapMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the MapMessage to be processed
 | 
			
		||||
     * @param from the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    void received(MapMessage msg, int from);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,32 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.AbstractMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An abstract base class for client messages used in network transfer.
 | 
			
		||||
 * It extends the AbstractMessage class provided by the jme3-network library.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ClientMessage extends AbstractMessage {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ClientMessage instance.
 | 
			
		||||
     */
 | 
			
		||||
    protected ClientMessage() {
 | 
			
		||||
        super(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor for processing this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to be used for processing
 | 
			
		||||
     * @param from        the connection ID of the sender
 | 
			
		||||
     */
 | 
			
		||||
    public abstract void accept(ClientInterpreter interpreter, int from);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message sent by the client containing the positions of the ships on the player's map.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class MapMessage extends ClientMessage {
 | 
			
		||||
    private List<Battleship> ships;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private MapMessage() { /* empty */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a MapMessage with the specified list of ships.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ships the list of ships placed on the player's map
 | 
			
		||||
     */
 | 
			
		||||
    public MapMessage(List<Battleship> ships) {
 | 
			
		||||
        this.ships = new ArrayList<>(ships);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the list of ships on the player's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the list of ships
 | 
			
		||||
     */
 | 
			
		||||
    public List<Battleship> getShips() {
 | 
			
		||||
        return ships;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the MapMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the MapMessage
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "MapMessage{ships=" + ships + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to process this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to process this message
 | 
			
		||||
     * @param from        the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(ClientInterpreter interpreter, int from) {
 | 
			
		||||
        interpreter.received(this, from);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,63 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message sent by the client to indicate a shooting action in the game.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class ShootMessage extends ClientMessage {
 | 
			
		||||
    private IntPoint position;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private ShootMessage() { /* empty */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ShootMessage with the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param position the position where the shot is fired
 | 
			
		||||
     */
 | 
			
		||||
    public ShootMessage(IntPoint position) {
 | 
			
		||||
        this.position = position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the position of the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the position of the shot
 | 
			
		||||
     */
 | 
			
		||||
    public IntPoint getPosition() {
 | 
			
		||||
        return position;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the ShootMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the ShootMessage
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "ShootMessage{position=" + position + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to process this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to process this message
 | 
			
		||||
     * @param from        the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(ClientInterpreter interpreter, int from) {
 | 
			
		||||
        interpreter.received(this, from);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,211 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static pp.util.Util.copy;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message sent by the server to inform clients about the effects of a shot in the Battleship game.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class EffectMessage extends ServerMessage {
 | 
			
		||||
    private boolean ownShot;
 | 
			
		||||
    private Shot shot;
 | 
			
		||||
    private Battleship destroyedShip;
 | 
			
		||||
    private List<Battleship> remainingOpponentShips;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an EffectMessage indicating a hit.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownShot true if the shot was fired by the player, false if by the opponent
 | 
			
		||||
     * @param pos     the position of the shot
 | 
			
		||||
     * @return an EffectMessage indicating a hit
 | 
			
		||||
     */
 | 
			
		||||
    public static EffectMessage hit(boolean ownShot, IntPoint pos) {
 | 
			
		||||
        return new EffectMessage(ownShot, new Shot(pos, true), null, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an EffectMessage indicating a miss.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownShot true if the shot was fired by the player, false if by the opponent
 | 
			
		||||
     * @param pos     the position of the shot
 | 
			
		||||
     * @return an EffectMessage indicating a miss
 | 
			
		||||
     */
 | 
			
		||||
    public static EffectMessage miss(boolean ownShot, IntPoint pos) {
 | 
			
		||||
        return new EffectMessage(ownShot, new Shot(pos, false), null, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an EffectMessage indicating a ship was destroyed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownShot       true if the shot was fired by the player, false if by the opponent
 | 
			
		||||
     * @param pos           the position of the shot
 | 
			
		||||
     * @param destroyedShip the ship that was destroyed
 | 
			
		||||
     * @return an EffectMessage indicating a ship was destroyed
 | 
			
		||||
     */
 | 
			
		||||
    public static EffectMessage shipDestroyed(boolean ownShot, IntPoint pos, Battleship destroyedShip) {
 | 
			
		||||
        return new EffectMessage(ownShot, new Shot(pos, true), destroyedShip, null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an EffectMessage indicating the player has won the game.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos           the position of the shot
 | 
			
		||||
     * @param destroyedShip the ship that was destroyed
 | 
			
		||||
     * @return an EffectMessage indicating the player has won
 | 
			
		||||
     */
 | 
			
		||||
    public static EffectMessage won(IntPoint pos, Battleship destroyedShip) {
 | 
			
		||||
        return new EffectMessage(true, new Shot(pos, true), destroyedShip, Collections.emptyList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates an EffectMessage indicating the player has lost the game.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos                    the position of the shot
 | 
			
		||||
     * @param destroyedShip          the ship that was destroyed
 | 
			
		||||
     * @param remainingOpponentShips the list of opponent's remaining ships
 | 
			
		||||
     * @return an EffectMessage indicating the player has lost
 | 
			
		||||
     */
 | 
			
		||||
    public static EffectMessage lost(IntPoint pos, Battleship destroyedShip, List<Battleship> remainingOpponentShips) {
 | 
			
		||||
        return new EffectMessage(false, new Shot(pos, true), destroyedShip, remainingOpponentShips);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private EffectMessage() { /* empty */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an EffectMessage with the specified parameters.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ownShot                true if the shot was fired by the player, false if by the opponent
 | 
			
		||||
     * @param shot                   the shot fired
 | 
			
		||||
     * @param destroyedShip          the ship that was destroyed by the shot, null if no ship was destroyed
 | 
			
		||||
     * @param remainingOpponentShips the list of opponent's remaining ships after the shot
 | 
			
		||||
     */
 | 
			
		||||
    private EffectMessage(boolean ownShot, Shot shot, Battleship destroyedShip, List<Battleship> remainingOpponentShips) {
 | 
			
		||||
        this.ownShot = ownShot;
 | 
			
		||||
        this.shot = shot;
 | 
			
		||||
        this.destroyedShip = destroyedShip;
 | 
			
		||||
        this.remainingOpponentShips = copy(remainingOpponentShips);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to process this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to process this message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(ServerInterpreter interpreter) {
 | 
			
		||||
        interpreter.received(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the shot was fired by the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the shot was fired by the player, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isOwnShot() {
 | 
			
		||||
        return ownShot;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the shot fired.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the shot fired
 | 
			
		||||
     */
 | 
			
		||||
    public Shot getShot() {
 | 
			
		||||
        return shot;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the ship that was destroyed by the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the destroyed ship, null if no ship was destroyed
 | 
			
		||||
     */
 | 
			
		||||
    public Battleship getDestroyedShip() {
 | 
			
		||||
        return destroyedShip;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the list of opponent's remaining ships after the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the list of opponent's remaining ships, null if the game is not yet over
 | 
			
		||||
     */
 | 
			
		||||
    public List<Battleship> getRemainingOpponentShips() {
 | 
			
		||||
        return remainingOpponentShips;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the game is over.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the game is over, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isGameOver() {
 | 
			
		||||
        return remainingOpponentShips != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the game is won by the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the game is won by the player, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isGameWon() {
 | 
			
		||||
        return isGameOver() && isOwnShot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the game is lost by the player.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the game is lost by the player, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isGameLost() {
 | 
			
		||||
        return isGameOver() && !isOwnShot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if it's currently the player's turn.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if it's the player's turn, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isMyTurn() {
 | 
			
		||||
        return isOwnShot() == shot.isHit();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the EffectMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the EffectMessage
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "EffectMessage{ownShot=" + ownShot + ", shot=" + shot + ", destroyedShip=" + destroyedShip + //NON-NLS
 | 
			
		||||
               ", remainingOpponentShips=" + remainingOpponentShips + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the key for the informational text associated with this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the key for the informational text
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getInfoTextKey() {
 | 
			
		||||
        if (isGameOver())
 | 
			
		||||
            return isGameWon() ? "you.won.the.game" : "you.lost.the.game";
 | 
			
		||||
        return isMyTurn() ? "its.your.turn" : "wait.for.opponent";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,97 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message sent by the server to provide details about the game configuration.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class GameDetails extends ServerMessage {
 | 
			
		||||
    private Map<Integer, Integer> shipNums;
 | 
			
		||||
    private int width;
 | 
			
		||||
    private int height;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private GameDetails() { /* empty */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a GameDetails message with the specified BattleshipConfig.
 | 
			
		||||
     *
 | 
			
		||||
     * @param config the BattleshipConfig containing game configuration details
 | 
			
		||||
     */
 | 
			
		||||
    public GameDetails(BattleshipConfig config) {
 | 
			
		||||
        this.shipNums = config.getShipNums();
 | 
			
		||||
        this.width = config.getMapWidth();
 | 
			
		||||
        this.height = config.getMapHeight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to process this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to process this message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(ServerInterpreter interpreter) {
 | 
			
		||||
        interpreter.received(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a map where the keys represent ship lengths
 | 
			
		||||
     * and the values represent the number of ships of that length.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a map of ship lengths to the number of ships
 | 
			
		||||
     */
 | 
			
		||||
    public Map<Integer, Integer> getShipNums() {
 | 
			
		||||
        return shipNums;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the width of the game map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getWidth() {
 | 
			
		||||
        return width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the height of the game map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the game map
 | 
			
		||||
     */
 | 
			
		||||
    public int getHeight() {
 | 
			
		||||
        return height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the GameDetails message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the GameDetails message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "GameDetails{" + "ships=" + getShipNums() + ", width=" + width + ", height=" + height + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the key for the informational text associated with this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the key for the informational text
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getInfoTextKey() {
 | 
			
		||||
        return "place.ships.in.your.map";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.server;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for processing server messages.
 | 
			
		||||
 * Implementations of this interface can be used to handle different types of server messages.
 | 
			
		||||
 */
 | 
			
		||||
public interface ServerInterpreter {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a GameDetails message received from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the GameDetails message received
 | 
			
		||||
     */
 | 
			
		||||
    void received(GameDetails msg);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a StartBattleMessage received from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the StartBattleMessage received
 | 
			
		||||
     */
 | 
			
		||||
    void received(StartBattleMessage msg);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles an EffectMessage received from the server.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg the EffectMessage received
 | 
			
		||||
     */
 | 
			
		||||
    void received(EffectMessage msg);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.AbstractMessage;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An abstract base class for server messages used in network transfer.
 | 
			
		||||
 * It extends the AbstractMessage class provided by the jme3-network library.
 | 
			
		||||
 */
 | 
			
		||||
public abstract class ServerMessage extends AbstractMessage {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new ServerMessage instance.
 | 
			
		||||
     */
 | 
			
		||||
    protected ServerMessage() {
 | 
			
		||||
        super(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor for processing this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to be used for processing
 | 
			
		||||
     */
 | 
			
		||||
    public abstract void accept(ServerInterpreter interpreter);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the bundle key of the informational text to be shown at the client.
 | 
			
		||||
     * This key is used to retrieve the appropriate localized text for display.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the bundle key of the informational text
 | 
			
		||||
     */
 | 
			
		||||
    public abstract String getInfoTextKey();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,71 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.message.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message sent by the server to inform clients about the start of the battle.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class StartBattleMessage extends ServerMessage {
 | 
			
		||||
    private boolean myTurn;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private StartBattleMessage() { /* empty */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a StartBattleMessage with the specified turn indicator.
 | 
			
		||||
     *
 | 
			
		||||
     * @param myTurn true if it's the client's turn to shoot, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public StartBattleMessage(boolean myTurn) {
 | 
			
		||||
        this.myTurn = myTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to process this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @param interpreter the visitor to process this message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(ServerInterpreter interpreter) {
 | 
			
		||||
        interpreter.received(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if it's the client's turn to shoot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if it's the client's turn, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isMyTurn() {
 | 
			
		||||
        return myTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the StartBattleMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the StartBattleMessage
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "StartBattleMessage{myTurn=" + myTurn + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the key for the informational text associated with this message.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the key for the informational text
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String getInfoTextKey() {
 | 
			
		||||
        return isMyTurn() ? "its.your.turn" : "wait.for.opponent";
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,324 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
import static java.lang.Math.max;
 | 
			
		||||
import static java.lang.Math.min;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a battleship in the game. A battleship is characterized by its length, position,
 | 
			
		||||
 * rotation, and status. It can be moved, rotated, and hit during the game. This class also
 | 
			
		||||
 * provides methods to check for collisions with other ships and to determine whether the
 | 
			
		||||
 * battleship has been destroyed.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class Battleship implements Item {
 | 
			
		||||
    /**
 | 
			
		||||
     * Enumeration representing the different statuses a battleship can have during the game.
 | 
			
		||||
     */
 | 
			
		||||
    public enum Status {
 | 
			
		||||
        /**
 | 
			
		||||
         * The ship is in its normal state, not being previewed for placement.
 | 
			
		||||
         */
 | 
			
		||||
        NORMAL,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The ship is being previewed in a valid position for placement.
 | 
			
		||||
         */
 | 
			
		||||
        VALID_PREVIEW,
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * The ship is being previewed in an invalid position for placement.
 | 
			
		||||
         */
 | 
			
		||||
        INVALID_PREVIEW
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private final int length; // The length of the battleship
 | 
			
		||||
    private int x;            // The x-coordinate of the battleship's position
 | 
			
		||||
    private int y;            // The y-coordinate of the battleship's position
 | 
			
		||||
    private Rotation rot;     // The rotation of the battleship
 | 
			
		||||
    private Status status;    // The current status of the battleship
 | 
			
		||||
    private final Set<IntPoint> damaged = new HashSet<>(); // The set of positions that have been hit on this ship
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization. Initializes a battleship with length 0,
 | 
			
		||||
     * at position (0, 0), with a default rotation of RIGHT.
 | 
			
		||||
     */
 | 
			
		||||
    private Battleship() {
 | 
			
		||||
        this(0, 0, 0, Rotation.RIGHT);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new Battleship with the specified length, position, and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @param length the length of the battleship
 | 
			
		||||
     * @param x      the x-coordinate of the battleship's initial position
 | 
			
		||||
     * @param y      the y-coordinate of the battleship's initial position
 | 
			
		||||
     * @param rot    the rotation of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public Battleship(int length, int x, int y, Rotation rot) {
 | 
			
		||||
        this.x = x;
 | 
			
		||||
        this.y = y;
 | 
			
		||||
        this.rot = rot;
 | 
			
		||||
        this.length = length;
 | 
			
		||||
        this.status = Status.NORMAL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current x-coordinate of the battleship's position.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the x-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getX() {
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current y-coordinate of the battleship's position.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the y-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getY() {
 | 
			
		||||
        return y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the battleship to the specified coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the new x-coordinate of the battleship's position
 | 
			
		||||
     * @param y the new y-coordinate of the battleship's position
 | 
			
		||||
     */
 | 
			
		||||
    public void moveTo(int x, int y) {
 | 
			
		||||
        this.x = x;
 | 
			
		||||
        this.y = y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Moves the battleship to the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the new position of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public void moveTo(IntPosition pos) {
 | 
			
		||||
        moveTo(pos.getX(), pos.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current status of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the status of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public Status getStatus() {
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the status of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param status the new status to be set for the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public void setStatus(Status status) {
 | 
			
		||||
        this.status = status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the length of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the length of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getLength() {
 | 
			
		||||
        return length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the minimum x-coordinate that the battleship occupies based on its current position and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the minimum x-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getMinX() {
 | 
			
		||||
        return x + min(0, (length - 1) * rot.dx());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the maximum x-coordinate that the battleship occupies based on its current position and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the maximum x-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getMaxX() {
 | 
			
		||||
        return x + max(0, (length - 1) * rot.dx());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the minimum y-coordinate that the battleship occupies based on its current position and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the minimum y-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getMinY() {
 | 
			
		||||
        return y + min(0, (length - 1) * rot.dy());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the maximum y-coordinate that the battleship occupies based on its current position and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the maximum y-coordinate of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public int getMaxY() {
 | 
			
		||||
        return y + max(0, (length - 1) * rot.dy());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the current rotation of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the rotation of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public Rotation getRot() {
 | 
			
		||||
        return rot;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Sets the rotation of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rot the new rotation to be set for the battleship
 | 
			
		||||
     */
 | 
			
		||||
    public void setRotation(Rotation rot) {
 | 
			
		||||
        this.rot = rot;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the battleship by 90 degrees clockwise.
 | 
			
		||||
     */
 | 
			
		||||
    public void rotated() {
 | 
			
		||||
        setRotation(rot.rotate());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to hit the battleship at the specified position.
 | 
			
		||||
     * If the position is part of the battleship, the hit is recorded.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate of the position to hit
 | 
			
		||||
     * @param y the y-coordinate of the position to hit
 | 
			
		||||
     * @return true if the position is part of the battleship, false otherwise
 | 
			
		||||
     * @see #contains(int, int)
 | 
			
		||||
     */
 | 
			
		||||
    public boolean hit(int x, int y) {
 | 
			
		||||
        if (!contains(x, y))
 | 
			
		||||
            return false;
 | 
			
		||||
        damaged.add(new IntPoint(x, y));
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Attempts to hit the battleship at the specified position.
 | 
			
		||||
     * If the position is part of the battleship, the hit is recorded.
 | 
			
		||||
     * This is a convenience method for {@linkplain #hit(int, int)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param position the position to hit
 | 
			
		||||
     * @return true if the position is part of the battleship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean hit(IntPosition position) {
 | 
			
		||||
        return hit(position.getX(), position.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the positions of this battleship that have been hit.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a set of positions that have been hit
 | 
			
		||||
     * @see #hit(int, int)
 | 
			
		||||
     */
 | 
			
		||||
    public Set<IntPoint> getDamaged() {
 | 
			
		||||
        return Collections.unmodifiableSet(damaged);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks whether the specified position is covered by the battleship. This method does
 | 
			
		||||
     * not record a hit, only checks coverage.
 | 
			
		||||
     * This is a convenience method for {@linkplain #contains(int, int)}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position to check
 | 
			
		||||
     * @return true if the position is covered by the battleship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean contains(IntPosition pos) {
 | 
			
		||||
        return contains(pos.getX(), pos.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks whether the specified position is covered by the battleship. This method does
 | 
			
		||||
     * not record a hit, only checks coverage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate of the position to check
 | 
			
		||||
     * @param y the y-coordinate of the position to check
 | 
			
		||||
     * @return true if the position is covered by the battleship, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean contains(int x, int y) {
 | 
			
		||||
        return getMinX() <= x && x <= getMaxX() &&
 | 
			
		||||
               getMinY() <= y && y <= getMaxY();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Determines if the battleship has been completely destroyed. A battleship is considered
 | 
			
		||||
     * destroyed if all of its positions have been hit.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battleship is destroyed, false otherwise
 | 
			
		||||
     * @see #hit(int, int)
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isDestroyed() {
 | 
			
		||||
        return damaged.size() == length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks whether this battleship collides with another battleship. Two battleships collide
 | 
			
		||||
     * if any of their occupied positions overlap.
 | 
			
		||||
     *
 | 
			
		||||
     * @param other the other battleship to check collision with
 | 
			
		||||
     * @return true if the battleships collide, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean collidesWith(Battleship other) {
 | 
			
		||||
        return other.getMaxX() >= getMinX() && getMaxX() >= other.getMinX() &&
 | 
			
		||||
               other.getMaxY() >= getMinY() && getMaxY() >= other.getMinY();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the battleship, including its length, position,
 | 
			
		||||
     * and rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the battleship
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "Ship{length=" + length + ", x=" + x + ", y=" + y + ", rot=" + rot + '}';  //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor that returns a value of type {@code T}. This method is part of the
 | 
			
		||||
     * Visitor design pattern.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor to accept
 | 
			
		||||
     * @param <T>     the type of the value returned by the visitor
 | 
			
		||||
     * @return the value returned by the visitor
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T accept(Visitor<T> visitor) {
 | 
			
		||||
        return visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor that does not return a value. This method is part of the
 | 
			
		||||
     * Visitor design pattern.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor to accept
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(VoidVisitor visitor) {
 | 
			
		||||
        visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,94 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a point in the two-dimensional plane with integer coordinates.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public final class IntPoint implements IntPosition {
 | 
			
		||||
    private int x;
 | 
			
		||||
    private int y;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Default constructor for serialization purposes.
 | 
			
		||||
     */
 | 
			
		||||
    private IntPoint() { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new IntPoint with the specified coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate of the point
 | 
			
		||||
     * @param y the y-coordinate of the point
 | 
			
		||||
     */
 | 
			
		||||
    public IntPoint(int x, int y) {
 | 
			
		||||
        this.x = x;
 | 
			
		||||
        this.y = y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the x-coordinate of the point.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the x-coordinate
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getX() {
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the y-coordinate of the point.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the y-coordinate
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getY() {
 | 
			
		||||
        return y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates whether some other object is "equal to" this one.
 | 
			
		||||
     *
 | 
			
		||||
     * @param obj the reference object with which to compare
 | 
			
		||||
     * @return true if this object is the same as the obj argument; false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj == this) return true;
 | 
			
		||||
        if (obj == null || obj.getClass() != this.getClass()) return false;
 | 
			
		||||
        var that = (IntPoint) obj;
 | 
			
		||||
        return this.x == that.x &&
 | 
			
		||||
               this.y == that.y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a hash code value for the IntPoint.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a hash code value for this object
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hash(x, y);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the IntPoint.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the object
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "IntPoint[" + //NON-NLS
 | 
			
		||||
               "x=" + x + ", " + //NON-NLS
 | 
			
		||||
               "y=" + y + ']'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Interface representing a position with X and Y coordinates.
 | 
			
		||||
 */
 | 
			
		||||
public interface IntPosition {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the X coordinate of this position.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the X coordinate.
 | 
			
		||||
     */
 | 
			
		||||
    int getX();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the Y coordinate of this position.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the Y coordinate.
 | 
			
		||||
     */
 | 
			
		||||
    int getY();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface representing any item on a ship map.
 | 
			
		||||
 * It extends the IntPosition interface to provide position information.
 | 
			
		||||
 */
 | 
			
		||||
public interface Item {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to perform operations on the item.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor performing operations on the item
 | 
			
		||||
     * @param <T>     the type of result returned by the visitor
 | 
			
		||||
     * @return the result of the visitor's operation on the item
 | 
			
		||||
     */
 | 
			
		||||
    <T> T accept(Visitor<T> visitor);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor to perform operations on the item without returning a result.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor performing operations on the item
 | 
			
		||||
     */
 | 
			
		||||
    void accept(VoidVisitor visitor);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,67 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the rotation of a ship and provides functionality related to rotation.
 | 
			
		||||
 */
 | 
			
		||||
public enum Rotation implements Serializable {
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents the ship facing upwards.
 | 
			
		||||
     */
 | 
			
		||||
    UP,
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents the ship facing rightwards.
 | 
			
		||||
     */
 | 
			
		||||
    RIGHT,
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents the ship facing downwards.
 | 
			
		||||
     */
 | 
			
		||||
    DOWN,
 | 
			
		||||
    /**
 | 
			
		||||
     * Represents the ship facing leftwards.
 | 
			
		||||
     */
 | 
			
		||||
    LEFT;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the change in x-coordinate corresponding to this rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the change in x-coordinate
 | 
			
		||||
     */
 | 
			
		||||
    public int dx() {
 | 
			
		||||
        return switch (this) {
 | 
			
		||||
            case UP, DOWN -> 0;
 | 
			
		||||
            case RIGHT -> 1;
 | 
			
		||||
            case LEFT -> -1;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the change in y-coordinate corresponding to this rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the change in y-coordinate
 | 
			
		||||
     */
 | 
			
		||||
    public int dy() {
 | 
			
		||||
        return switch (this) {
 | 
			
		||||
            case UP -> 1;
 | 
			
		||||
            case LEFT, RIGHT -> 0;
 | 
			
		||||
            case DOWN -> -1;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Rotates the orientation clockwise and returns the next rotation.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the next rotation after rotating clockwise
 | 
			
		||||
     */
 | 
			
		||||
    public Rotation rotate() {
 | 
			
		||||
        return values()[(ordinal() + 1) % values().length];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,251 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.notification.GameEvent;
 | 
			
		||||
import pp.battleship.notification.GameEventBroker;
 | 
			
		||||
import pp.battleship.notification.ItemAddedEvent;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Collections;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a rectangular map that holds ships and registers shots fired.
 | 
			
		||||
 * It also supports event notification for game state changes such as item addition or removal.
 | 
			
		||||
 * Valid positions on this map have x-coordinates in the range of 0 to width-1 and y-coordinates
 | 
			
		||||
 * in the range of 0 to height-1.
 | 
			
		||||
 *
 | 
			
		||||
 * @see #getWidth()
 | 
			
		||||
 * @see #getHeight()
 | 
			
		||||
 */
 | 
			
		||||
public class ShipMap {
 | 
			
		||||
    /**
 | 
			
		||||
     * A list of items (ships, shots, etc.) placed on the map.
 | 
			
		||||
     */
 | 
			
		||||
    private final List<Item> items = new ArrayList<>();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The broker responsible for notifying registered listeners of events
 | 
			
		||||
     * (such as when an item is added or removed from the map).
 | 
			
		||||
     * Can be null, in which case no notifications will be sent.
 | 
			
		||||
     */
 | 
			
		||||
    private final GameEventBroker eventBroker;
 | 
			
		||||
 | 
			
		||||
    private final int width;
 | 
			
		||||
    private final int height;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an empty map with the given dimensions. The specified event broker
 | 
			
		||||
     * will handle the notification of changes in the map state, such as adding or removing items.
 | 
			
		||||
     * Passing null as the event broker is allowed, but in that case, no notifications will occur.
 | 
			
		||||
     *
 | 
			
		||||
     * @param width       the number of columns (width) of the map
 | 
			
		||||
     * @param height      the number of rows (height) of the map
 | 
			
		||||
     * @param eventBroker the event broker used for notifying listeners, or null if event distribution is not needed
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMap(int width, int height, GameEventBroker eventBroker) {
 | 
			
		||||
        if (width < 1 || height < 1)
 | 
			
		||||
            throw new IllegalArgumentException("Invalid map size");
 | 
			
		||||
        this.width = width;
 | 
			
		||||
        this.height = height;
 | 
			
		||||
        this.eventBroker = eventBroker;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds an item (e.g., a ship or a shot) to the map and triggers the appropriate event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item the item to be added to the map
 | 
			
		||||
     */
 | 
			
		||||
    private void addItem(Item item) {
 | 
			
		||||
        items.add(item);
 | 
			
		||||
        notifyListeners(new ItemAddedEvent(item, this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a battleship to the map and triggers an item addition event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to be added to the map
 | 
			
		||||
     */
 | 
			
		||||
    public void add(Battleship ship) {
 | 
			
		||||
        addItem(ship);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers a shot on the map, updates the state of the affected ship (if any),
 | 
			
		||||
     * and triggers an item addition event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the shot to be registered on the map
 | 
			
		||||
     */
 | 
			
		||||
    public void add(Shot shot) {
 | 
			
		||||
        final Battleship ship = findShipAt(shot);
 | 
			
		||||
        if (ship != null)
 | 
			
		||||
            ship.hit(shot);
 | 
			
		||||
        addItem(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes an item from the map and triggers an item removal event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param item the item to be removed from the map
 | 
			
		||||
     */
 | 
			
		||||
    public void remove(Item item) {
 | 
			
		||||
        items.remove(item);
 | 
			
		||||
        notifyListeners(new ItemAddedEvent(item, this));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes all items from the map and triggers corresponding removal events for each.
 | 
			
		||||
     */
 | 
			
		||||
    public void clear() {
 | 
			
		||||
        new ArrayList<>(items).forEach(this::remove);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a stream of items of a specified type (class).
 | 
			
		||||
     *
 | 
			
		||||
     * @param clazz the class type to filter items by
 | 
			
		||||
     * @param <T>   the type of items to return
 | 
			
		||||
     * @return a stream of items matching the specified class type
 | 
			
		||||
     */
 | 
			
		||||
    private <T extends Item> Stream<T> getItems(Class<T> clazz) {
 | 
			
		||||
        return items.stream().filter(clazz::isInstance).map(clazz::cast);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a stream of all battleships currently on the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a stream of battleships
 | 
			
		||||
     */
 | 
			
		||||
    public Stream<Battleship> getShips() {
 | 
			
		||||
        return getItems(Battleship.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a list of all remaining battleships that have not been destroyed.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a list of remaining battleships
 | 
			
		||||
     */
 | 
			
		||||
    public List<Battleship> getRemainingShips() {
 | 
			
		||||
        return getShips().filter(s -> !s.isDestroyed()).toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a stream of all shots fired on the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a stream of shots
 | 
			
		||||
     */
 | 
			
		||||
    public Stream<Shot> getShots() {
 | 
			
		||||
        return getItems(Shot.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns an unmodifiable list of all items currently on the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return an unmodifiable list of all items
 | 
			
		||||
     */
 | 
			
		||||
    public List<Item> getItems() {
 | 
			
		||||
        return Collections.unmodifiableList(items);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the width (number of columns) of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the width of the map
 | 
			
		||||
     */
 | 
			
		||||
    public int getWidth() {
 | 
			
		||||
        return width;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the height (number of rows) of the map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the height of the map
 | 
			
		||||
     */
 | 
			
		||||
    public int getHeight() {
 | 
			
		||||
        return height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the given ship is in a valid position (within the map bounds and non-colliding with other ships).
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the battleship to validate
 | 
			
		||||
     * @return true if the ship's position is valid, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isValid(Battleship ship) {
 | 
			
		||||
        return isValid(ship.getMinX(), ship.getMinY()) &&
 | 
			
		||||
               isValid(ship.getMaxX(), ship.getMaxY()) &&
 | 
			
		||||
               getShips().filter(s -> s != ship).noneMatch(ship::collidesWith);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds a battleship at the specified coordinates.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate of the position
 | 
			
		||||
     * @param y the y-coordinate of the position
 | 
			
		||||
     * @return the ship at the specified coordinates, or null if none is found
 | 
			
		||||
     */
 | 
			
		||||
    public Battleship findShipAt(int x, int y) {
 | 
			
		||||
        return getShips().filter(ship -> ship.contains(x, y))
 | 
			
		||||
                         .findAny()
 | 
			
		||||
                         .orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Finds a battleship at the specified position. This is a convenience method.
 | 
			
		||||
     *
 | 
			
		||||
     * @param position the position within the map
 | 
			
		||||
     * @return the ship at the specified position, or null if none is found
 | 
			
		||||
     */
 | 
			
		||||
    public Battleship findShipAt(IntPosition position) {
 | 
			
		||||
        return findShipAt(position.getX(), position.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Validates whether the specified position is within the map boundaries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position to validate
 | 
			
		||||
     * @return true if the position is within the map, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isValid(IntPosition pos) {
 | 
			
		||||
        return isValid(pos.getX(), pos.getY());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the specified coordinates are within the map boundaries.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x the x-coordinate to validate
 | 
			
		||||
     * @param y the y-coordinate to validate
 | 
			
		||||
     * @return true if the coordinates are valid, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isValid(int x, int y) {
 | 
			
		||||
        return x >= 0 && x < width &&
 | 
			
		||||
               y >= 0 && y < height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the ship map.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the ship map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "ShipMap{" + items + '}'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies all registered listeners about a game event if the event broker is available.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the event to be distributed to listeners
 | 
			
		||||
     */
 | 
			
		||||
    private void notifyListeners(GameEvent event) {
 | 
			
		||||
        if (eventBroker != null)
 | 
			
		||||
            eventBroker.notifyListeners(event);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,140 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a shot in the Battleship game.
 | 
			
		||||
 * A shot is defined by its coordinates and whether it was a hit or miss.
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public final class Shot implements Item, IntPosition {
 | 
			
		||||
    private int x;
 | 
			
		||||
    private int y;
 | 
			
		||||
    private boolean hit;
 | 
			
		||||
 | 
			
		||||
    // Private no-arg constructor for serialization purposes
 | 
			
		||||
    private Shot() {}
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @param x   the x-coordinate of the shot
 | 
			
		||||
     * @param y   the y-coordinate of the shot
 | 
			
		||||
     * @param hit indicates whether the shot was a hit
 | 
			
		||||
     */
 | 
			
		||||
    public Shot(int x, int y, boolean hit) {
 | 
			
		||||
        this.x = x;
 | 
			
		||||
        this.y = y;
 | 
			
		||||
        this.hit = hit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a new shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position of the shot
 | 
			
		||||
     * @param hit indicates whether the shot was a hit
 | 
			
		||||
     */
 | 
			
		||||
    public Shot(IntPosition pos, boolean hit) {
 | 
			
		||||
        this(pos.getX(), pos.getY(), hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the x-coordinate of the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the x-coordinate of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getX() {
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the y-coordinate of the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the y-coordinate of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int getY() {
 | 
			
		||||
        return y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the shot was a hit.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the shot was a hit, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isHit() {
 | 
			
		||||
        return hit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if this shot is equal to another object.
 | 
			
		||||
     * Two shots are considered equal if they have the same coordinates and hit status.
 | 
			
		||||
     *
 | 
			
		||||
     * @param obj the object to compare with
 | 
			
		||||
     * @return true if the objects are equal, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(Object obj) {
 | 
			
		||||
        if (obj == this) return true;
 | 
			
		||||
        if (obj == null || obj.getClass() != this.getClass()) return false;
 | 
			
		||||
        var that = (Shot) obj;
 | 
			
		||||
        return this.x == that.x &&
 | 
			
		||||
               this.y == that.y &&
 | 
			
		||||
               this.hit == that.hit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Computes the hash code of this shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the hash code of this shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public int hashCode() {
 | 
			
		||||
        return Objects.hash(x, y, hit);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns a string representation of the shot.
 | 
			
		||||
     *
 | 
			
		||||
     * @return a string representation of the shot
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
        return "Shot[" + //NON-NLS
 | 
			
		||||
               "x=" + x + ", " + //NON-NLS
 | 
			
		||||
               "y=" + y + ", " + //NON-NLS
 | 
			
		||||
               "hit=" + hit + ']'; //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor with a return value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor to accept
 | 
			
		||||
     * @param <T>     the type of the return value
 | 
			
		||||
     * @return the result of the visitor's visit method
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T accept(Visitor<T> visitor) {
 | 
			
		||||
        return visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor without a return value.
 | 
			
		||||
     *
 | 
			
		||||
     * @param visitor the visitor to accept
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(VoidVisitor visitor) {
 | 
			
		||||
        visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for implementing the Visitor pattern for different types of elements in the Battleship model.
 | 
			
		||||
 *
 | 
			
		||||
 * @param <T> the type of result returned by the visit methods
 | 
			
		||||
 */
 | 
			
		||||
public interface Visitor<T> {
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Shot element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the Shot element to visit
 | 
			
		||||
     * @return the result of visiting the Shot element
 | 
			
		||||
     */
 | 
			
		||||
    T visit(Shot shot);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Battleship element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship element to visit
 | 
			
		||||
     * @return the result of visiting the Battleship element
 | 
			
		||||
     */
 | 
			
		||||
    T visit(Battleship ship);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface for implementing the Visitor pattern for different types of elements in the Battleship model
 | 
			
		||||
 * without returning any result.
 | 
			
		||||
 */
 | 
			
		||||
public interface VoidVisitor {
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Shot element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot the Shot element to visit
 | 
			
		||||
     */
 | 
			
		||||
    void visit(Shot shot);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Battleship element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship element to visit
 | 
			
		||||
     */
 | 
			
		||||
    void visit(Battleship ship);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,51 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model.dto;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A class representing data transfer objects of battleships for JSON serialization and deserialization.
 | 
			
		||||
 */
 | 
			
		||||
class BattleshipDTO {
 | 
			
		||||
    private final int length;   // The length of the battleship
 | 
			
		||||
    private final int x;        // The x-coordinate of the battleship's position
 | 
			
		||||
    private final int y;        // The y-coordinate of the battleship's position
 | 
			
		||||
    private final Rotation rot; // The rotation of the battleship
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a BattleshipDTO object from a Battleship object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param ship the Battleship object to be converted
 | 
			
		||||
     */
 | 
			
		||||
    BattleshipDTO(Battleship ship) {
 | 
			
		||||
        length = ship.getLength();
 | 
			
		||||
        x = ship.getX();
 | 
			
		||||
        y = ship.getY();
 | 
			
		||||
        rot = ship.getRot();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Gets the length of the battleship.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the length
 | 
			
		||||
     */
 | 
			
		||||
    public int getLength() {
 | 
			
		||||
        return length;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts this BattleshipDTO object to a Battleship object.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the Battleship object
 | 
			
		||||
     */
 | 
			
		||||
    Battleship toBattleship() {
 | 
			
		||||
        return new Battleship(length, x, y, rot);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,117 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.model.dto;
 | 
			
		||||
 | 
			
		||||
import com.google.gson.Gson;
 | 
			
		||||
import com.google.gson.GsonBuilder;
 | 
			
		||||
import com.google.gson.JsonParseException;
 | 
			
		||||
import pp.battleship.message.server.GameDetails;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.FileReader;
 | 
			
		||||
import java.io.FileWriter;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A class representing data transfer objects of ship maps for JSON serialization and deserialization.
 | 
			
		||||
 */
 | 
			
		||||
public class ShipMapDTO {
 | 
			
		||||
    // Logger instance for logging messages
 | 
			
		||||
    static final Logger LOGGER = System.getLogger(ShipMapDTO.class.getName());
 | 
			
		||||
 | 
			
		||||
    // Width of the ship map
 | 
			
		||||
    private int width;
 | 
			
		||||
 | 
			
		||||
    // Height of the ship map
 | 
			
		||||
    private int height;
 | 
			
		||||
 | 
			
		||||
    // List of ships in the map
 | 
			
		||||
    private List<BattleshipDTO> ships;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a ShipMapDTO object from a ShipMap object.
 | 
			
		||||
     *
 | 
			
		||||
     * @param map the ShipMap object to be converted
 | 
			
		||||
     */
 | 
			
		||||
    public ShipMapDTO(ShipMap map) {
 | 
			
		||||
        this.width = map.getWidth();
 | 
			
		||||
        this.height = map.getHeight();
 | 
			
		||||
        this.ships = map.getShips().map(BattleshipDTO::new).toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the current ship map fits the game details provided.
 | 
			
		||||
     *
 | 
			
		||||
     * @param details the game details to be matched against
 | 
			
		||||
     * @return true if the ship map fits the game details, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean fits(GameDetails details) {
 | 
			
		||||
        if (width != details.getWidth() || height != details.getHeight())
 | 
			
		||||
            return false;
 | 
			
		||||
        int numShips = details.getShipNums().values().stream().mapToInt(Integer::intValue).sum();
 | 
			
		||||
        if (numShips != ships.size())
 | 
			
		||||
            return false;
 | 
			
		||||
        for (var e : details.getShipNums().entrySet()) {
 | 
			
		||||
            final int shipLength = e.getKey();
 | 
			
		||||
            final int requiredNum = e.getValue();
 | 
			
		||||
            final int actualNum = (int) ships.stream()
 | 
			
		||||
                                             .filter(s -> s.getLength() == shipLength)
 | 
			
		||||
                                             .count();
 | 
			
		||||
            if (requiredNum != actualNum)
 | 
			
		||||
                return false;
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the ships stored in this DTO.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the ships stored in this DTO
 | 
			
		||||
     */
 | 
			
		||||
    public List<Battleship> getShips() {
 | 
			
		||||
        return ships.stream().map(BattleshipDTO::toBattleship).toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Saves the current ShipMapDTO to a file in JSON format.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file to which the ShipMapDTO will be saved
 | 
			
		||||
     * @throws IOException if an I/O error occurs
 | 
			
		||||
     */
 | 
			
		||||
    public void saveTo(File file) throws IOException {
 | 
			
		||||
        try (FileWriter writer = new FileWriter(file)) {
 | 
			
		||||
            final Gson gson = new GsonBuilder().setPrettyPrinting().create();
 | 
			
		||||
            final String json = gson.toJson(this);
 | 
			
		||||
            LOGGER.log(Level.DEBUG, "JSON of map: {0}", json); //NON-NLS
 | 
			
		||||
            writer.write(json);
 | 
			
		||||
            LOGGER.log(Level.INFO, "JSON written to {0}", file.getAbsolutePath()); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Loads a ShipMapDTO from a file containing JSON data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param file the file from which the ShipMapDTO will be loaded
 | 
			
		||||
     * @return the loaded ShipMapDTO object
 | 
			
		||||
     * @throws IOException if an I/O error occurs or if the JSON is invalid
 | 
			
		||||
     */
 | 
			
		||||
    public static ShipMapDTO loadFrom(File file) throws IOException {
 | 
			
		||||
        try (FileReader reader = new FileReader(file)) {
 | 
			
		||||
            final Gson gson = new Gson();
 | 
			
		||||
            return gson.fromJson(reader, ShipMapDTO.class);
 | 
			
		||||
        }
 | 
			
		||||
        catch (JsonParseException e) {
 | 
			
		||||
            throw new IOException(e.getLocalizedMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event when an item is added to a map.
 | 
			
		||||
 */
 | 
			
		||||
public record ClientStateEvent() implements GameEvent {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of this event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListener(GameEventListener listener) {
 | 
			
		||||
        listener.receivedEvent(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * An interface used for all game events.
 | 
			
		||||
 */
 | 
			
		||||
public interface GameEvent {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of the event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener to be notified
 | 
			
		||||
     */
 | 
			
		||||
    void notifyListener(GameEventListener listener);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Defines a broker for distributing game events to registered listeners.
 | 
			
		||||
 */
 | 
			
		||||
public interface GameEventBroker {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies all registered listeners about the specified game event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the game event to be broadcast to listeners
 | 
			
		||||
     */
 | 
			
		||||
    void notifyListeners(GameEvent event);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,48 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Listener interface for all events implemented by subclasses of {@linkplain pp.battleship.notification.GameEvent}.
 | 
			
		||||
 */
 | 
			
		||||
public interface GameEventListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that an item has been destroyed
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    default void receivedEvent(ItemRemovedEvent event) { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that an item has been added to a map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    default void receivedEvent(ItemAddedEvent event) { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that an info text shall be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    default void receivedEvent(InfoTextEvent event) { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that a sound shall be played.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    default void receivedEvent(SoundEvent event) { /* do nothing */ }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Indicates that the client's state has changed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    default void receivedEvent(ClientStateEvent event) { /* do nothing */ }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event when an item is added to a map.
 | 
			
		||||
 *
 | 
			
		||||
 * @param key the bundle key for the message
 | 
			
		||||
 */
 | 
			
		||||
public record InfoTextEvent(String key) implements GameEvent {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of this event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListener(GameEventListener listener) {
 | 
			
		||||
        listener.receivedEvent(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.model.Item;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event when an item is added to a map.
 | 
			
		||||
 *
 | 
			
		||||
 * @param item the added item
 | 
			
		||||
 * @param map  the map that got the additional item
 | 
			
		||||
 */
 | 
			
		||||
public record ItemAddedEvent(Item item, ShipMap map) implements GameEvent {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of this event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListener(GameEventListener listener) {
 | 
			
		||||
        listener.receivedEvent(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.model.Item;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event when an item gets removed.
 | 
			
		||||
 *
 | 
			
		||||
 * @param item the destroyed item
 | 
			
		||||
 */
 | 
			
		||||
public record ItemRemovedEvent(Item item, ShipMap map) implements GameEvent {
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of this event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListener(GameEventListener listener) {
 | 
			
		||||
        listener.receivedEvent(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Enumeration representing different types of sounds used in the game.
 | 
			
		||||
 */
 | 
			
		||||
public enum Sound {
 | 
			
		||||
    /**
 | 
			
		||||
     * Sound of an explosion.
 | 
			
		||||
     */
 | 
			
		||||
    EXPLOSION,
 | 
			
		||||
    /**
 | 
			
		||||
     * Sound of a splash.
 | 
			
		||||
     */
 | 
			
		||||
    SPLASH,
 | 
			
		||||
    /**
 | 
			
		||||
     * Sound of a ship being destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    DESTROYED_SHIP
 | 
			
		||||
}
 | 
			
		||||
@@ -1,26 +0,0 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.notification;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Event when an item is added to a map.
 | 
			
		||||
 *
 | 
			
		||||
 * @param sound the sound to be played
 | 
			
		||||
 */
 | 
			
		||||
public record SoundEvent(Sound sound) implements GameEvent {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Notifies the game event listener of this event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param listener the game event listener
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void notifyListener(GameEventListener listener) {
 | 
			
		||||
        listener.receivedEvent(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||