Compare commits
	
		
			17 Commits
		
	
	
		
			dev_client
			...
			b_Bauer_Lu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					66798eb3e4 | ||
| 
						 | 
					edff9e1ad9 | ||
| 
						 | 
					bf16a18b71 | ||
| 
						 | 
					6100e95e76 | ||
| 
						 | 
					1b7b842ca7 | ||
| 
						 | 
					865b0fc33c | ||
| 
						 | 
					5a9b0b1de0 | ||
| 
						 | 
					8650fa5b58 | ||
| 
						 | 
					113da79df6 | ||
| 
						 | 
					e41d21584a | ||
| 
						 | 
					5e773ca1ba | ||
| 
						 | 
					b2b3bc23bf | ||
| 
						 | 
					ce490fb877 | ||
| 
						 | 
					ce5e908349 | ||
| 
						 | 
					07e922d01e | ||
| 
						 | 
					42b442e937 | ||
| 
						 | 
					93ade34282 | 
@@ -1,14 +0,0 @@
 | 
			
		||||
root = true
 | 
			
		||||
 | 
			
		||||
[*]
 | 
			
		||||
charset = utf-8
 | 
			
		||||
end_of_line = lf
 | 
			
		||||
insert_final_newline = true
 | 
			
		||||
trim_trailing_whitespace = true
 | 
			
		||||
 | 
			
		||||
[*.java]
 | 
			
		||||
indent_style = space
 | 
			
		||||
indent_size = 4
 | 
			
		||||
 | 
			
		||||
[*.txt]
 | 
			
		||||
indent_style = tab
 | 
			
		||||
							
								
								
									
										59
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,59 +0,0 @@
 | 
			
		||||
# Git configuration files
 | 
			
		||||
.gitattributes text
 | 
			
		||||
.gitignore text
 | 
			
		||||
.gitconfig text
 | 
			
		||||
.gitmodules text
 | 
			
		||||
 | 
			
		||||
# Documentation
 | 
			
		||||
*.md text
 | 
			
		||||
*.txt text
 | 
			
		||||
AUTHORS text
 | 
			
		||||
CHANGELOG text
 | 
			
		||||
CHANGES text
 | 
			
		||||
CONTRIBUTING text
 | 
			
		||||
COPYING text
 | 
			
		||||
INSTALL text
 | 
			
		||||
LICENSE text
 | 
			
		||||
NEWS text
 | 
			
		||||
TODO text
 | 
			
		||||
 | 
			
		||||
# Java sources
 | 
			
		||||
*.java          text diff=java
 | 
			
		||||
*.kt            text diff=kotlin
 | 
			
		||||
*.groovy        text diff=java
 | 
			
		||||
*.scala         text diff=java
 | 
			
		||||
*.gradle        text diff=java
 | 
			
		||||
*.gradle.kts    text diff=kotlin
 | 
			
		||||
 | 
			
		||||
# Normalize text files (Convert crlf => lf)
 | 
			
		||||
*.css           text diff=css
 | 
			
		||||
*.scss          text diff=css
 | 
			
		||||
*.sass          text
 | 
			
		||||
*.df            text
 | 
			
		||||
*.htm           text diff=html
 | 
			
		||||
*.html          text diff=html
 | 
			
		||||
*.js            text
 | 
			
		||||
*.mjs           text
 | 
			
		||||
*.cjs           text
 | 
			
		||||
*.jsp           text
 | 
			
		||||
*.jspf          text
 | 
			
		||||
*.jspx          text
 | 
			
		||||
*.properties    text
 | 
			
		||||
*.tld           text
 | 
			
		||||
*.tag           text
 | 
			
		||||
*.tagx          text
 | 
			
		||||
*.xml           text
 | 
			
		||||
 | 
			
		||||
# Binary files
 | 
			
		||||
# (binary is a macro for -text -diff)
 | 
			
		||||
*.class         binary
 | 
			
		||||
*.dll           binary
 | 
			
		||||
*.ear           binary
 | 
			
		||||
*.jar           binary
 | 
			
		||||
*.so            binary
 | 
			
		||||
*.war           binary
 | 
			
		||||
*.jks           binary
 | 
			
		||||
 | 
			
		||||
# Build tool wrappers
 | 
			
		||||
mvnw            text eol=lf
 | 
			
		||||
gradlew         text eol=lf
 | 
			
		||||
							
								
								
									
										51
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,43 +1,26 @@
 | 
			
		||||
# Gradle
 | 
			
		||||
.gradle
 | 
			
		||||
build/
 | 
			
		||||
#!gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
#!**/src/main/**/build/
 | 
			
		||||
#!**/src/test/**/build/
 | 
			
		||||
build
 | 
			
		||||
 | 
			
		||||
### IntelliJ IDEA ###
 | 
			
		||||
.idea
 | 
			
		||||
.idea/modules.xml
 | 
			
		||||
.idea/jarRepositories.xml
 | 
			
		||||
.idea/compiler.xml
 | 
			
		||||
.idea/libraries/
 | 
			
		||||
*.iws
 | 
			
		||||
# VSC
 | 
			
		||||
bin
 | 
			
		||||
 | 
			
		||||
# IntelliJ
 | 
			
		||||
*.iml
 | 
			
		||||
*.ipr
 | 
			
		||||
out/
 | 
			
		||||
#!**/src/main/**/out/
 | 
			
		||||
#!**/src/test/**/out/
 | 
			
		||||
.idea
 | 
			
		||||
out
 | 
			
		||||
 | 
			
		||||
### Eclipse ###
 | 
			
		||||
.apt_generated
 | 
			
		||||
# Eclipse
 | 
			
		||||
.classpath
 | 
			
		||||
.factorypath
 | 
			
		||||
.project
 | 
			
		||||
.settings
 | 
			
		||||
.springBeans
 | 
			
		||||
.sts4-cache
 | 
			
		||||
bin/
 | 
			
		||||
#!**/src/main/**/bin/
 | 
			
		||||
#!**/src/test/**/bin/
 | 
			
		||||
 | 
			
		||||
### NetBeans ###
 | 
			
		||||
/nbproject/private/
 | 
			
		||||
/nbbuild/
 | 
			
		||||
/dist/
 | 
			
		||||
/nbdist/
 | 
			
		||||
/.nb-gradle/
 | 
			
		||||
# Libraries
 | 
			
		||||
*.so
 | 
			
		||||
*.dylib
 | 
			
		||||
*.dll
 | 
			
		||||
*.jar
 | 
			
		||||
*.class
 | 
			
		||||
 | 
			
		||||
### VS Code ###
 | 
			
		||||
.vscode/
 | 
			
		||||
 | 
			
		||||
### Mac OS ###
 | 
			
		||||
.DS_Store
 | 
			
		||||
!Projekte/gradle/wrapper/gradle-wrapper.jar
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								Projekte/.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
			
		||||
*.bat           text eol=crlf
 | 
			
		||||
gradlew         text eol=lf
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										17
									
								
								Projekte/.run/BattleshipApp (Mac).run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
<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.server.*" />
 | 
			
		||||
        <option name="ENABLED" value="true" />
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										19
									
								
								Projekte/.run/BattleshipApp.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
 | 
			
		||||
    <option name="ALTERNATIVE_JRE_PATH" value="temurin-20" />
 | 
			
		||||
    <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
 | 
			
		||||
    <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp" />
 | 
			
		||||
    <module name="Gruppe-01" />
 | 
			
		||||
    <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.server.*" />
 | 
			
		||||
        <option name="ENABLED" value="true" />
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										17
									
								
								Projekte/.run/BattleshipServer.run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,17 @@
 | 
			
		||||
<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,17 +0,0 @@
 | 
			
		||||
<component name="ProjectRunConfigurationManager">
 | 
			
		||||
  <configuration default="false" name="MdgaApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
 | 
			
		||||
    <option name="MAIN_CLASS_NAME" value="pp.mdga.client.MdgaApp" />
 | 
			
		||||
    <module name="Projekte.mdga.client.main" />
 | 
			
		||||
    <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties -ea" />
 | 
			
		||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
 | 
			
		||||
    <extension name="coverage">
 | 
			
		||||
      <pattern>
 | 
			
		||||
        <option name="PATTERN" value="pp.mdga.client.Board.*" />
 | 
			
		||||
        <option name="ENABLED" value="true" />
 | 
			
		||||
      </pattern>
 | 
			
		||||
    </extension>
 | 
			
		||||
    <method v="2">
 | 
			
		||||
      <option name="Make" enabled="true" />
 | 
			
		||||
    </method>
 | 
			
		||||
  </configuration>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										24
									
								
								Projekte/.run/Projekte [test].run.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,24 @@
 | 
			
		||||
<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>
 | 
			
		||||
							
								
								
									
										189
									
								
								Projekte/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,189 @@
 | 
			
		||||
# 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
 | 
			
		||||
@@ -2,13 +2,14 @@
 | 
			
		||||
    id 'buildlogic.jme-application-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'mdga client'
 | 
			
		||||
description = 'Battleship Client'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    implementation project(":jme-common")
 | 
			
		||||
    implementation project(":mdga:model")
 | 
			
		||||
    implementation project(":battleship:model")
 | 
			
		||||
 | 
			
		||||
    implementation libs.jme3.desktop
 | 
			
		||||
    implementation libs.jme3.effects
 | 
			
		||||
 | 
			
		||||
    runtimeOnly libs.jme3.awt.dialogs
 | 
			
		||||
    runtimeOnly libs.jme3.plugins
 | 
			
		||||
@@ -17,6 +18,6 @@ implementation project(":mdga:model")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
application {
 | 
			
		||||
    mainClass = 'pp.mdga.client.MdgaApp'
 | 
			
		||||
    applicationName = 'MDGA'
 | 
			
		||||
    mainClass = 'pp.battleship.client.BattleshipApp'
 | 
			
		||||
    applicationName = 'battleship'
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								Projekte/battleship/client/client.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,73 @@
 | 
			
		||||
########################################
 | 
			
		||||
## 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
 | 
			
		||||
							
								
								
									
										8
									
								
								Projekte/battleship/client/logging.properties
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,8 @@
 | 
			
		||||
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
 | 
			
		||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map1.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
{
 | 
			
		||||
  "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"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map2.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
{
 | 
			
		||||
  "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"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map3.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
{
 | 
			
		||||
  "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": 7,
 | 
			
		||||
      "y": 4,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "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"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										66
									
								
								Projekte/battleship/client/maps/map4.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,66 @@
 | 
			
		||||
{
 | 
			
		||||
  "width": 10,
 | 
			
		||||
  "height": 10,
 | 
			
		||||
  "ships": [
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 9,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 9,
 | 
			
		||||
      "y": 1,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 1,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 1,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 8,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 0,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 2,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 1,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 5,
 | 
			
		||||
      "y": 2,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 3,
 | 
			
		||||
      "x": 7,
 | 
			
		||||
      "y": 3,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "length": 4,
 | 
			
		||||
      "x": 6,
 | 
			
		||||
      "y": 4,
 | 
			
		||||
      "rot": "RIGHT"
 | 
			
		||||
    }
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,95 @@
 | 
			
		||||
package pp.battleship.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.app.Application;
 | 
			
		||||
import com.jme3.audio.AudioData.DataType;
 | 
			
		||||
import com.jme3.audio.AudioNode;
 | 
			
		||||
import com.jme3.audio.AudioSource.Status;
 | 
			
		||||
 | 
			
		||||
import java.util.prefs.Preferences;
 | 
			
		||||
 | 
			
		||||
public class BackgroundMusic {
 | 
			
		||||
 | 
			
		||||
    private static final String VOLUME_PREF = "volume";
 | 
			
		||||
    private static final String MUSIC_ENABLED_PREF = "musicEnabled";
 | 
			
		||||
    private Preferences prefs = Preferences.userNodeForPackage(BackgroundMusic.class);
 | 
			
		||||
 | 
			
		||||
    private final AudioNode backgroundMusic;
 | 
			
		||||
    private boolean musicEnabled;
 | 
			
		||||
    private float volume;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constuctor for the BackgroundMusic
 | 
			
		||||
     * @param app for synchronising and saving data with the application
 | 
			
		||||
     * @param musicFilePath the filepath for the BackgroundMusic
 | 
			
		||||
     */
 | 
			
		||||
    public BackgroundMusic(Application app, String musicFilePath) {
 | 
			
		||||
        this.volume = prefs.getFloat(VOLUME_PREF, 1.0f);
 | 
			
		||||
        this.musicEnabled = prefs.getBoolean(MUSIC_ENABLED_PREF, true);
 | 
			
		||||
 | 
			
		||||
        backgroundMusic = new AudioNode(app.getAssetManager(), musicFilePath, DataType.Stream);
 | 
			
		||||
        backgroundMusic.setLooping(true);
 | 
			
		||||
        backgroundMusic.setPositional(false);
 | 
			
		||||
        backgroundMusic.setVolume(1.0f);
 | 
			
		||||
 | 
			
		||||
        if (musicEnabled) {
 | 
			
		||||
            play();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the condition is met to play BackgroundMusic
 | 
			
		||||
     */
 | 
			
		||||
    public void play() {
 | 
			
		||||
        if (musicEnabled && (backgroundMusic.getStatus() == Status.Stopped || backgroundMusic.getStatus() == Status.Paused)) {
 | 
			
		||||
            backgroundMusic.play();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * function to stop the BackgroundMusic
 | 
			
		||||
     */
 | 
			
		||||
    public void stop() {
 | 
			
		||||
        if (backgroundMusic.getStatus() == Status.Playing) {
 | 
			
		||||
            backgroundMusic.stop();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * function to toggle the Backgroundmusic also sets the volume to the previous session
 | 
			
		||||
     */
 | 
			
		||||
    public void toogleMusic() {
 | 
			
		||||
        this.musicEnabled = !this.musicEnabled;
 | 
			
		||||
        if (musicEnabled) {
 | 
			
		||||
            play();
 | 
			
		||||
        } else {
 | 
			
		||||
            stop();
 | 
			
		||||
        }
 | 
			
		||||
        prefs.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Setter for the Volume also safes the volume from the previous session
 | 
			
		||||
     * @param volume variable for the volume of the BackgroundMusic
 | 
			
		||||
     */
 | 
			
		||||
    public void setVolume(float volume) {
 | 
			
		||||
        this.volume = volume;
 | 
			
		||||
        backgroundMusic.setVolume(volume);
 | 
			
		||||
        prefs.putFloat(VOLUME_PREF, volume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for Volume
 | 
			
		||||
     * @return a float value of the volume
 | 
			
		||||
     */
 | 
			
		||||
    public float getVolume() {
 | 
			
		||||
        return volume;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for musicEnabled
 | 
			
		||||
     * @return if the music is enabled return true, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isMusicEnabled() {
 | 
			
		||||
        return musicEnabled;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,443 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    /**
 | 
			
		||||
     *
 | 
			
		||||
     */
 | 
			
		||||
    private BackgroundMusic backgroundMusic;
 | 
			
		||||
 | 
			
		||||
    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();
 | 
			
		||||
        backgroundMusic = new BackgroundMusic(this, "Sound/Effects/BackgroundMusic/boss_battle_#2_metal_opening.wav");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Getter for the BackgroundMusic
 | 
			
		||||
     * @return the BackgroundMusic
 | 
			
		||||
     */
 | 
			
		||||
    public BackgroundMusic getBackgroundMusic(){
 | 
			
		||||
        return backgroundMusic;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,196 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,102 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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();
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,141 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
    private AudioNode missileLaunch;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
        missileLaunch = loadSound(app, "Sound/Effects/missileLaunch.wav");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 missile launch sound effect.
 | 
			
		||||
     */
 | 
			
		||||
    public void missileLaunch() {
 | 
			
		||||
        missileLaunch.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks the according case for the soundeffect
 | 
			
		||||
     * @param event the received event
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(SoundEvent event) {
 | 
			
		||||
        switch (event.sound()) {
 | 
			
		||||
            case EXPLOSION -> explosion();
 | 
			
		||||
            case SPLASH -> splash();
 | 
			
		||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
			
		||||
            case MISSILE_LAUNCH -> missileLaunch();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,189 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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 com.simsilica.lemur.DefaultRangedValueModel;
 | 
			
		||||
import com.simsilica.lemur.Slider;
 | 
			
		||||
import com.simsilica.lemur.core.VersionedReference;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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"));
 | 
			
		||||
    private final VersionedReference<Double> volumeRef;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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)));
 | 
			
		||||
 | 
			
		||||
        Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
 | 
			
		||||
        musicToggle.setChecked(app.getBackgroundMusic().isMusicEnabled());
 | 
			
		||||
        musicToggle.addClickCommands(s -> toggleMusic());
 | 
			
		||||
 | 
			
		||||
        addChild(musicToggle);
 | 
			
		||||
 | 
			
		||||
        Slider volumeSlider = new Slider();
 | 
			
		||||
        volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 2.0, app.getBackgroundMusic().getVolume()));
 | 
			
		||||
        volumeSlider.setDelta(0.1);
 | 
			
		||||
        addChild(volumeSlider);
 | 
			
		||||
 | 
			
		||||
        volumeRef = volumeSlider.getModel().createReference();
 | 
			
		||||
 | 
			
		||||
        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 volume if there is a change and adjusts it accordingly.
 | 
			
		||||
     * @param tmp unused time parameter
 | 
			
		||||
     */
 | 
			
		||||
    public void update(float tmp){
 | 
			
		||||
        if(volumeRef.update()) {
 | 
			
		||||
            double newVolume = volumeRef.get();
 | 
			
		||||
            adjustVolume(newVolume);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adjusts the background music volume to the specified value.
 | 
			
		||||
     * @param newVolume the new volume level
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    private void adjustVolume(double newVolume) {
 | 
			
		||||
        app.getBackgroundMusic().setVolume((float) newVolume);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggles the background music on or off.
 | 
			
		||||
     */
 | 
			
		||||
    private void toggleMusic(){
 | 
			
		||||
        app.getBackgroundMusic().toogleMusic();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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"));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,233 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.*;
 | 
			
		||||
import com.simsilica.lemur.component.SpringGridLayout;
 | 
			
		||||
import pp.battleship.client.server.BattleshipServer;
 | 
			
		||||
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;
 | 
			
		||||
    private BattleshipServer battleshipServer;
 | 
			
		||||
    private boolean GUI = true;
 | 
			
		||||
    private Container menu;
 | 
			
		||||
    private boolean serverToggle = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
 | 
			
		||||
        this.menu = new Container();
 | 
			
		||||
        menu();
 | 
			
		||||
        addChild(menu);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Main Menu creation
 | 
			
		||||
     */
 | 
			
		||||
    private void menu() {
 | 
			
		||||
 | 
			
		||||
        Button connectGame = new Button("Multiplayer");
 | 
			
		||||
        connectGame.addClickCommands(source -> serverConnectGUI());
 | 
			
		||||
        Checkbox serverHost = new Checkbox("Host own Game");
 | 
			
		||||
        serverHost.addClickCommands(source -> toggleServer());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        menu.addChild(serverHost);
 | 
			
		||||
        menu.addChild(connectGame);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Logic for the ServerGUI
 | 
			
		||||
     */
 | 
			
		||||
    private void serverConnectGUI() {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Hosting Server...");
 | 
			
		||||
 | 
			
		||||
        if (GUI) {
 | 
			
		||||
            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())
 | 
			
		||||
                    .setOkClose(false)
 | 
			
		||||
                    .build(this);
 | 
			
		||||
            GUI = false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Connect to server if serverToggle is false hosts otherwise
 | 
			
		||||
     */
 | 
			
		||||
    private void connect(){
 | 
			
		||||
        if (serverToggle) {
 | 
			
		||||
            hostServer();
 | 
			
		||||
        } else {
 | 
			
		||||
            connectServer();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 connectServer() {
 | 
			
		||||
        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"));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the Server is running or starts the Server and handles Exceptions     *
 | 
			
		||||
     */
 | 
			
		||||
    private void hostServer() {
 | 
			
		||||
        if (battleshipServer == null) {
 | 
			
		||||
            startServer(); // Starts the server in a new thread
 | 
			
		||||
            try {
 | 
			
		||||
                Thread.sleep(1000); // Wait for the server to start
 | 
			
		||||
            } catch (InterruptedException e) {
 | 
			
		||||
                LOGGER.log(Level.WARNING, e.getMessage(), e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // Proceed with connecting to the server
 | 
			
		||||
        connectServer();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Toggle Method for Boolean
 | 
			
		||||
     */
 | 
			
		||||
    private void toggleServer(){
 | 
			
		||||
        serverToggle = !serverToggle;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Allows Client to Start a Server in a new Thread
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer() {
 | 
			
		||||
        new Thread(() -> {
 | 
			
		||||
            try {
 | 
			
		||||
                // Initialize and run the server
 | 
			
		||||
                battleshipServer = new BattleshipServer();
 | 
			
		||||
                battleshipServer.run(Integer.parseInt(port.getText()));
 | 
			
		||||
            } catch (Exception e) {
 | 
			
		||||
                LOGGER.log(Level.ERROR, e);
 | 
			
		||||
            }
 | 
			
		||||
        }).start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,152 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,122 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,173 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,155 @@
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.asset.AssetManager;
 | 
			
		||||
import com.jme3.effect.ParticleEmitter;
 | 
			
		||||
import com.jme3.effect.ParticleMesh.Type;
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Manages the visual effects for impact events such as hits and misses.
 | 
			
		||||
 */
 | 
			
		||||
public class ImpactEffectManager {
 | 
			
		||||
 | 
			
		||||
    private final AssetManager assetManager;
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ImpactEffectManager.class.getName());
 | 
			
		||||
 | 
			
		||||
    private Material particleMaterial;
 | 
			
		||||
    private BattleshipApp app;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructor to initialize the asset manager via the main application.
 | 
			
		||||
     *
 | 
			
		||||
     * @param app The main application instance.
 | 
			
		||||
     */
 | 
			
		||||
    public ImpactEffectManager(BattleshipApp app) {
 | 
			
		||||
        this.app = app;
 | 
			
		||||
        this.assetManager = app.getAssetManager();
 | 
			
		||||
        this.particleMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Generates a visual effect for a hit at the specified position.
 | 
			
		||||
     *
 | 
			
		||||
     * @param battleshipNode The node where the effect should be attached.
 | 
			
		||||
     * @param shot           The details of the shot where the hit occurred.
 | 
			
		||||
     */
 | 
			
		||||
    public void triggerHitEffect(Node battleshipNode, Shot shot) {
 | 
			
		||||
        ParticleEmitter hitEffect = createParticleEmitter("HitEffect", 50, ColorRGBA.Orange, ColorRGBA.Red, 0.45f, 0.1f, 1f, 2f);
 | 
			
		||||
 | 
			
		||||
        hitEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
 | 
			
		||||
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "Hit effect created at position: {0}", hitEffect.getLocalTranslation().toString());
 | 
			
		||||
 | 
			
		||||
//        hitEffect.emitAllParticles();
 | 
			
		||||
        hitEffect.addControl(new EffectCleanupControl(hitEffect));
 | 
			
		||||
 | 
			
		||||
        app.getRootNode().attachChild(hitEffect);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual effect for a missed shot at the specified location.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shot The details of the missed shot.
 | 
			
		||||
     * @return The particle emitter representing the miss effect.
 | 
			
		||||
     */
 | 
			
		||||
    public ParticleEmitter triggerMissEffect(Shot shot) {
 | 
			
		||||
        ParticleEmitter missEffect = createParticleEmitter("MissEffect", 50, ColorRGBA.Blue, ColorRGBA.Cyan, 0.3f, 0.05f, 0.5f, 1.5f);
 | 
			
		||||
        missEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
 | 
			
		||||
        missEffect.addControl(new EffectCleanupControl(missEffect));
 | 
			
		||||
        return missEffect;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method to create a particle emitter with predefined parameters.
 | 
			
		||||
     *
 | 
			
		||||
     * @param name          The name of the particle emitter.
 | 
			
		||||
     * @param count         The number of particles to emit.
 | 
			
		||||
     * @param startColor    The initial color of the particles.
 | 
			
		||||
     * @param endColor      The final color of the particles.
 | 
			
		||||
     * @param startSize     The initial size of the particles.
 | 
			
		||||
     * @param endSize       The final size of the particles.
 | 
			
		||||
     * @param lowLife       The minimum lifetime of the particles.
 | 
			
		||||
     * @param highLife      The maximum lifetime of the particles.
 | 
			
		||||
     * @return The configured ParticleEmitter instance.
 | 
			
		||||
     */
 | 
			
		||||
    private ParticleEmitter createParticleEmitter(String name, int count, ColorRGBA startColor, ColorRGBA endColor, float startSize, float endSize, float lowLife, float highLife) {
 | 
			
		||||
        ParticleEmitter emitter = new ParticleEmitter(name, Type.Triangle, count);
 | 
			
		||||
        emitter.setNumParticles(0);
 | 
			
		||||
        emitter.setMaterial(particleMaterial);
 | 
			
		||||
        emitter.setImagesX(2);
 | 
			
		||||
        emitter.setImagesY(2);
 | 
			
		||||
        emitter.setStartColor(startColor);
 | 
			
		||||
        emitter.setEndColor(endColor);
 | 
			
		||||
        emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
 | 
			
		||||
        emitter.setStartSize(startSize);
 | 
			
		||||
        emitter.setEndSize(endSize);
 | 
			
		||||
        emitter.setGravity(0, -0.5f, 0);
 | 
			
		||||
        emitter.setLowLife(lowLife);
 | 
			
		||||
        emitter.setHighLife(highLife);
 | 
			
		||||
        return emitter;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Custom control class to handle the cleanup of particle effects once they are no longer visible.
 | 
			
		||||
     */
 | 
			
		||||
    private static class EffectCleanupControl extends AbstractControl {
 | 
			
		||||
 | 
			
		||||
        private final ParticleEmitter emitter;
 | 
			
		||||
 | 
			
		||||
        private float currentTime = 0;
 | 
			
		||||
        private final float duration = 3.5f;
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Constructor for managing cleanup of a standalone particle emitter.
 | 
			
		||||
         *
 | 
			
		||||
         * @param emitter The particle emitter to manage.
 | 
			
		||||
         */
 | 
			
		||||
        public EffectCleanupControl(ParticleEmitter emitter) {
 | 
			
		||||
            this.emitter = emitter;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Removes the emitter when all particles are no longer visible.
 | 
			
		||||
         *
 | 
			
		||||
         * @param tpf Time per frame.
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void controlUpdate(float tpf) {
 | 
			
		||||
            currentTime += tpf;
 | 
			
		||||
            if (currentTime <= duration) {
 | 
			
		||||
            }
 | 
			
		||||
            if (currentTime >= 1f && currentTime <= 1.1) {
 | 
			
		||||
                //Start
 | 
			
		||||
                emitter.setNumParticles(50);
 | 
			
		||||
                emitter.setParticlesPerSec(50);
 | 
			
		||||
            }
 | 
			
		||||
            if (currentTime >= duration){
 | 
			
		||||
                emitter.setParticlesPerSec(0);
 | 
			
		||||
            }
 | 
			
		||||
            if (currentTime >= duration+1f) {
 | 
			
		||||
                //Ende
 | 
			
		||||
                spatial.removeFromParent();
 | 
			
		||||
                emitter.emitAllParticles();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * No custom rendering needed; particle behavior is handled in controlUpdate.
 | 
			
		||||
         * @param rm handles rendering, and ViewPort
 | 
			
		||||
         * @param vp defines the view where the scene is displayed.
 | 
			
		||||
         */
 | 
			
		||||
        @Override
 | 
			
		||||
        protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
 | 
			
		||||
            // No rendering-specific behavior needed for this control.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,191 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,147 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.Shell;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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;
 | 
			
		||||
 | 
			
		||||
    private static final float SHELL_DEPTH = 8f;
 | 
			
		||||
 | 
			
		||||
    // 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 visual representation (Spatial) for the given shell, attaching a control for its behavior.
 | 
			
		||||
     * @param shell the shell to visit and visualize
 | 
			
		||||
     * @return the constructed shell node (Spatial)
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell);
 | 
			
		||||
        final Node shellNode = new Node("shell");
 | 
			
		||||
        final Position startPosition = view.modelToView(shell.getCurrentPosition().x,shell.getCurrentPosition().z);
 | 
			
		||||
 | 
			
		||||
        shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(),startPosition.getY(), SHELL_DEPTH, 30, 30, ColorRGBA.Black));
 | 
			
		||||
 | 
			
		||||
        shellNode.addControl(new ShellControl(shell, this.view, shell.getLogic()));
 | 
			
		||||
        return shellNode;
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,203 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,289 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.*;
 | 
			
		||||
 | 
			
		||||
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 DESTROYER = "Models/Destroyer/Destroyer.j3o";
 | 
			
		||||
    private static final String FERRY = "Models/Ferry/13922_Staten_Island_Ferry_V1_l1.obj";
 | 
			
		||||
    private static final String SMALL = "Models/Small/10634_SpeedBoat_v01_LOD3.obj";
 | 
			
		||||
    private static final String BOMB = "Models/Bomb/BombGBU.j3o";
 | 
			
		||||
    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;
 | 
			
		||||
    private ImpactEffectManager effectHandler;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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;
 | 
			
		||||
        effectHandler = new ImpactEffectManager(app);
 | 
			
		||||
        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) : effectHandler.triggerMissEffect(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");
 | 
			
		||||
 | 
			
		||||
        System.out.println(shot.getX() + " " + shot.getY());
 | 
			
		||||
        effectHandler.triggerHitEffect(shipNode, shot);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        final Spatial bombModel = app.getAssetManager().loadModel(BOMB);
 | 
			
		||||
 | 
			
		||||
        // Apply transformations to the bomb model (scale, rotate, etc. if needed)
 | 
			
		||||
        bombModel.scale(0.05f);
 | 
			
		||||
        bombModel.rotate(-HALF_PI, 0f, 0f);
 | 
			
		||||
 | 
			
		||||
        // Set the position of the bomb at the shell's current position
 | 
			
		||||
        bombModel.setLocalTranslation(shell.getCurrentPosition());
 | 
			
		||||
 | 
			
		||||
        // Add a control to animate the bomb's movement (similar to the previous cylinder animation)
 | 
			
		||||
        bombModel.addControl(new ShellControl(shell,shell.getLogic()));
 | 
			
		||||
 | 
			
		||||
        return bombModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the appropriate graphical representation of the specified battleship.
 | 
			
		||||
     * The representation is a detailed model 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);
 | 
			
		||||
        if (ship.getLength() == 4) {
 | 
			
		||||
            return createBattleship(ship);
 | 
			
		||||
        } else if (ship.getLength() == 3) {
 | 
			
		||||
            return createBigShip(ship);
 | 
			
		||||
        } else if (ship.getLength() == 2) {
 | 
			
		||||
            return createMediumShip(ship);
 | 
			
		||||
        } else if (ship.getLength() == 1) {
 | 
			
		||||
            return createSmallShip(ship);
 | 
			
		||||
        }
 | 
			
		||||
        return createBattleship(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;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a "Big Ship"
 | 
			
		||||
     * @param ship The Battleship object with position and rotation info.
 | 
			
		||||
     * @return the spatial representing the "Big Ship" battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createBigShip(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(DESTROYER);
 | 
			
		||||
 | 
			
		||||
        model.move(0,0.4f,0);
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
 | 
			
		||||
        model.scale(0.0001f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a "Medium Ship"
 | 
			
		||||
     * @param ship The Battleship object with position and rotation info.
 | 
			
		||||
     * @return the spatial representing the "Medium Ship" battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createMediumShip(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(FERRY);
 | 
			
		||||
 | 
			
		||||
        model.move(0,0.25f,0);
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
			
		||||
        model.scale(0.00025f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a detailed 3D model to represent a "Small Ship"
 | 
			
		||||
     * @param ship The Battleship object with position and rotation info.
 | 
			
		||||
     * @return the spatial representing the "Small Ship" battleship
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createSmallShip(Battleship ship) {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(SMALL);
 | 
			
		||||
 | 
			
		||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
 | 
			
		||||
        model.scale(0.0009f);
 | 
			
		||||
        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;
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,96 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.game.client.ClientGameLogic;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.battleship.notification.Sound;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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 ShellControl extends AbstractControl {
 | 
			
		||||
 | 
			
		||||
    private final Shell shell;
 | 
			
		||||
    private MapView view;
 | 
			
		||||
    private final ClientGameLogic logic;
 | 
			
		||||
    private final EffectMessage msg;
 | 
			
		||||
    private boolean hasPlayedSound = false;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 shell the Battleship object to control
 | 
			
		||||
     */
 | 
			
		||||
    public ShellControl(Shell shell, ClientGameLogic clientGameLogic) {
 | 
			
		||||
        this.shell = shell;
 | 
			
		||||
        this.logic = clientGameLogic;
 | 
			
		||||
        this.msg = shell.getMsg();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes ShellControl with a shell, map view, and game logic.
 | 
			
		||||
     * @param shell the shell to be controlled
 | 
			
		||||
     * @param view the map view to display the shell
 | 
			
		||||
     * @param clientGameLogic the game logic instance
 | 
			
		||||
     */
 | 
			
		||||
    public ShellControl(Shell shell, MapView view, ClientGameLogic clientGameLogic) {
 | 
			
		||||
        this.shell = shell;
 | 
			
		||||
        this.view = view;
 | 
			
		||||
        this.logic = clientGameLogic;
 | 
			
		||||
        this.msg = shell.getMsg();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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 == null)
 | 
			
		||||
            return;
 | 
			
		||||
        if (shell.isFinished() && !hasPlayedSound) {
 | 
			
		||||
            if (!msg.getShot().isHit())
 | 
			
		||||
                logic.playSound(Sound.SPLASH);
 | 
			
		||||
            else if (msg.getDestroyedShip() == null)
 | 
			
		||||
                logic.playSound(Sound.EXPLOSION);
 | 
			
		||||
            else
 | 
			
		||||
                logic.playSound(Sound.DESTROYED_SHIP);
 | 
			
		||||
            hasPlayedSound = true;
 | 
			
		||||
        }
 | 
			
		||||
        if (view == null) {
 | 
			
		||||
            shell.updatePosition(tpf);
 | 
			
		||||
            spatial.setLocalTranslation(shell.getCurrentPosition());
 | 
			
		||||
        } else {
 | 
			
		||||
            shell.updatePosition(tpf);
 | 
			
		||||
            Position pos2d = view.modelToView(shell.getCurrentPosition().x - 0.3f, shell.getCurrentPosition().z - 0.3f);
 | 
			
		||||
            spatial.setLocalTranslation(pos2d.getY(), pos2d.getX(), 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Called during rendering, but no operations are needed as this control only affects spatial transformation.
 | 
			
		||||
     * @param rm the RenderManager rendering the spatial
 | 
			
		||||
     * @param vp the ViewPort being rendered
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // No rendering logic is needed for this control
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,125 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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 java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
    /**
 | 
			
		||||
     * The current Ship
 | 
			
		||||
     */
 | 
			
		||||
    private final Battleship battleship;
 | 
			
		||||
    /**
 | 
			
		||||
     * Logger for logging messages related to ShipControl operations.
 | 
			
		||||
     */
 | 
			
		||||
    static final Logger LOGGER = System.getLogger(ShipControl.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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) {
 | 
			
		||||
        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 = battleship.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;
 | 
			
		||||
        if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= -0.6f) {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName());
 | 
			
		||||
            spatial.getParent().detachChild(spatial);
 | 
			
		||||
        } else if (battleship.isDestroyed()) {
 | 
			
		||||
            spatial.move(0, -0.05f * tpf, 0);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Update the time within the oscillation cycle
 | 
			
		||||
            time = (time + tpf) % cycle;
 | 
			
		||||
 | 
			
		||||
            // Apply the pitch rotation to the spatial
 | 
			
		||||
            spatial.setLocalRotation(pitch);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,89 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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());
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,204 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.server;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.*;
 | 
			
		||||
import com.jme3.network.serializing.Serializer;
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.game.server.Player;
 | 
			
		||||
import pp.battleship.game.server.ServerGameLogic;
 | 
			
		||||
import pp.battleship.game.server.ServerSender;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
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 pp.battleship.model.Shot;
 | 
			
		||||
 | 
			
		||||
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.BlockingQueue;
 | 
			
		||||
import java.util.concurrent.LinkedBlockingQueue;
 | 
			
		||||
import java.util.logging.LogManager;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Server implementing the visitor pattern as MessageReceiver for ClientMessages
 | 
			
		||||
 */
 | 
			
		||||
public class BattleshipServer implements MessageListener<HostedConnection>, ConnectionListener, ServerSender {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
 | 
			
		||||
    private static final File CONFIG_FILE = new File("server.properties");
 | 
			
		||||
 | 
			
		||||
    private Server myServer;
 | 
			
		||||
    private final ServerGameLogic logic;
 | 
			
		||||
    private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
 | 
			
		||||
 | 
			
		||||
    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());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates the server.
 | 
			
		||||
     */
 | 
			
		||||
    public BattleshipServer() {
 | 
			
		||||
        BattleshipConfig config = new BattleshipConfig();
 | 
			
		||||
        config.readFromIfExists(CONFIG_FILE);
 | 
			
		||||
        LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
 | 
			
		||||
        logic = new ServerGameLogic(this, config);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Starts the server on the given port and continuously processes incoming messages.
 | 
			
		||||
     * @param port the port to start the server on
 | 
			
		||||
     */
 | 
			
		||||
    public void run(int port) {
 | 
			
		||||
        startServer(port);
 | 
			
		||||
        while (true)
 | 
			
		||||
            processNextMessage();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Initializes and starts the server on the specified port, handling exceptions if the server fails to start.
 | 
			
		||||
     * @param port the port to start the server on
 | 
			
		||||
     */
 | 
			
		||||
    private void startServer(int port) {
 | 
			
		||||
        try {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
			
		||||
            myServer = Network.createServer(port);
 | 
			
		||||
            initializeSerializables();
 | 
			
		||||
            myServer.start();
 | 
			
		||||
            registerListeners();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
 | 
			
		||||
            exit(1);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves and processes the next message from the queue, handling interruptions during the wait.
 | 
			
		||||
     */
 | 
			
		||||
    private void processNextMessage() {
 | 
			
		||||
        try {
 | 
			
		||||
            pendingMessages.take().process(logic);
 | 
			
		||||
        }
 | 
			
		||||
        catch (InterruptedException ex) {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
 | 
			
		||||
            Thread.currentThread().interrupt();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes the next message from the queue, handling interruptions during message retrieval.
 | 
			
		||||
     */
 | 
			
		||||
    private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(GameDetails.class);
 | 
			
		||||
        Serializer.registerClass(StartBattleMessage.class);
 | 
			
		||||
        Serializer.registerClass(MapMessage.class);
 | 
			
		||||
        Serializer.registerClass(ShootMessage.class);
 | 
			
		||||
        Serializer.registerClass(EffectMessage.class);
 | 
			
		||||
        Serializer.registerClass(Battleship.class);
 | 
			
		||||
        Serializer.registerClass(IntPoint.class);
 | 
			
		||||
        Serializer.registerClass(Shot.class);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers message and connection listeners for the server.
 | 
			
		||||
     */
 | 
			
		||||
    private void registerListeners() {
 | 
			
		||||
        myServer.addMessageListener(this, MapMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, ShootMessage.class);
 | 
			
		||||
        myServer.addConnectionListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles received messages, logging the source and adding client messages to the pending queue.
 | 
			
		||||
     * @param source the connection the message was received from
 | 
			
		||||
     * @param message the received message
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void messageReceived(HostedConnection source, Message message) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
 | 
			
		||||
        if (message instanceof ClientMessage clientMessage)
 | 
			
		||||
            pendingMessages.add(new ReceivedMessage(clientMessage, source.getId()));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a new connection by logging it and adding a new player to the game logic.
 | 
			
		||||
     * @param server the server receiving the connection
 | 
			
		||||
     * @param hostedConnection the newly added connection
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connectionAdded(Server server, HostedConnection hostedConnection) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
 | 
			
		||||
        logic.addPlayer(hostedConnection.getId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the removal of a connection by logging it, checking if it belongs to an active player, and exiting if necessary.
 | 
			
		||||
     * @param server the server losing the connection
 | 
			
		||||
     * @param hostedConnection the removed connection
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void connectionRemoved(Server server, HostedConnection hostedConnection) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
 | 
			
		||||
        final Player player = logic.getPlayerById(hostedConnection.getId());
 | 
			
		||||
        if (player == null)
 | 
			
		||||
            LOGGER.log(Level.INFO, "closed connection does not belong to an active player"); //NON-NLS
 | 
			
		||||
        else { //NON-NLS
 | 
			
		||||
            LOGGER.log(Level.INFO, "closed connection belongs to {0}", player); //NON-NLS
 | 
			
		||||
            exit(0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Closes all client connections and terminates the server with the given exit value.
 | 
			
		||||
     * @param exitValue the exit code to terminate the application with
 | 
			
		||||
     */
 | 
			
		||||
    private void exit(int exitValue) { //NON-NLS
 | 
			
		||||
        LOGGER.log(Level.INFO, "close request"); //NON-NLS
 | 
			
		||||
        if (myServer != null)
 | 
			
		||||
            for (HostedConnection client : myServer.getConnections()) //NON-NLS
 | 
			
		||||
                if (client != null) client.close("Game over"); //NON-NLS
 | 
			
		||||
        System.exit(exitValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Send the specified message to the specified connection.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id      the connection id
 | 
			
		||||
     * @param message the message
 | 
			
		||||
     */
 | 
			
		||||
    public void send(int id, ServerMessage message) {
 | 
			
		||||
        if (myServer == null || !myServer.isRunning()) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "no server running when trying to send {0}", message); //NON-NLS
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        final HostedConnection connection = myServer.getConnection(id);
 | 
			
		||||
        if (connection != null)
 | 
			
		||||
            connection.send(message);
 | 
			
		||||
        else
 | 
			
		||||
            LOGGER.log(Level.ERROR, "there is no connection with id={0}", id); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,17 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// Programming project code
 | 
			
		||||
// UniBw M, 2022, 2023, 2024
 | 
			
		||||
// www.unibw.de/inf2
 | 
			
		||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
 | 
			
		||||
record ReceivedMessage(ClientMessage message, int from) {
 | 
			
		||||
    void process(ClientInterpreter interpreter) {
 | 
			
		||||
        message.accept(interpreter, from);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
# Blender MTL File: 'untitletttd.blend'
 | 
			
		||||
# Material Count: 1
 | 
			
		||||
 | 
			
		||||
newmtl None
 | 
			
		||||
Ns 0
 | 
			
		||||
Ka 0.000000 0.000000 0.000000
 | 
			
		||||
Kd 0.8 0.8 0.8
 | 
			
		||||
Ks 0.8 0.8 0.8
 | 
			
		||||
d 1
 | 
			
		||||
illum 2
 | 
			
		||||
map_Kd C:\Users\Convidado.Cliente-JMF-PC\Desktop\ghftht.png
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
newmtl Battleship
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.00 0.00 0.00
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
map_Kd BattleshipC.jpg
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn1SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn2SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn3SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn4SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn5SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn6SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn7SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn8SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
| 
		 After Width: | Height: | Size: 360 KiB  | 
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/battleship-v1--611736.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 After Width: | Height: | Size: 35 KiB  | 
@@ -0,0 +1,64 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 13.03.2012 15:35:28
 | 
			
		||||
 | 
			
		||||
newmtl metall
 | 
			
		||||
	Ns 31.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.2431 0.2431 0.2431
 | 
			
		||||
	Kd 0.2431 0.2431 0.2431
 | 
			
		||||
	Ks 0.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl 13922_StatenIslandFerry
 | 
			
		||||
	Ns 50.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.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka 13922_StatenIslandFerry_diffuse.jpg
 | 
			
		||||
	map_Kd 13922_StatenIslandFerry_diffuse.jpg
 | 
			
		||||
 | 
			
		||||
newmtl metall2
 | 
			
		||||
	Ns 50.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.2118 0.2824 0.3451
 | 
			
		||||
	Kd 0.2118 0.2824 0.3451
 | 
			
		||||
	Ks 0.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat
 | 
			
		||||
	Ns 34.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.6588 0.3059 0.1294
 | 
			
		||||
	Kd 0.6588 0.3059 0.1294
 | 
			
		||||
	Ks 0.3600 0.3600 0.3600
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl white
 | 
			
		||||
	Ns 10.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.9686 0.9686 0.9686
 | 
			
		||||
	Kd 0.9686 0.9686 0.9686
 | 
			
		||||
	Ks 0.0000 0.0000 0.0000
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/statenislandferry-v1--603882.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 After Width: | Height: | Size: 235 KiB  | 
| 
		 After Width: | Height: | Size: 94 KiB  | 
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 After Width: | Height: | Size: 717 KiB  | 
@@ -0,0 +1,28 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 08.06.2011 15:26:00
 | 
			
		||||
 | 
			
		||||
newmtl _10634_SpeedBoat_v01_LOD310634_SpeedBoat_v01
 | 
			
		||||
	Ns 53.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.5882 0.5882 0.5882
 | 
			
		||||
	Kd 0.5882 0.5882 0.5882
 | 
			
		||||
	Ks 0.2000 0.2000 0.2000
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka 10634_SpeedBoat_v01.jpg
 | 
			
		||||
	map_Kd 10634_SpeedBoat_v01.jpg
 | 
			
		||||
 | 
			
		||||
newmtl glass
 | 
			
		||||
	Ns 80.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 0.2000
 | 
			
		||||
	Tr 0.8000
 | 
			
		||||
	Tf 0.2000 0.2000 0.2000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.5882 0.5882 0.5882
 | 
			
		||||
	Kd 0.5882 0.5882 0.5882
 | 
			
		||||
	Ks 0.5000 0.5000 0.5000
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/speedboat-v01--840133.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -0,0 +1,4 @@
 | 
			
		||||
based on:
 | 
			
		||||
water 002
 | 
			
		||||
by Katsukagi on 29/11/2018
 | 
			
		||||
https://3dtextures.me/2018/11/29/water-002/
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
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
 | 
			
		||||
     }
 | 
			
		||||
}
 | 
			
		||||
| 
		 After Width: | Height: | Size: 15 KiB  | 
| 
		 After Width: | Height: | Size: 207 KiB  | 
| 
		 After Width: | Height: | Size: 88 KiB  | 
| 
		 After Width: | Height: | Size: 31 KiB  | 
| 
		 After Width: | Height: | Size: 70 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Projekte/battleship/converter/KingGeorgeV.j3o
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										16
									
								
								Projekte/battleship/converter/build.gradle
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,16 @@
 | 
			
		||||
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'
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,69 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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/Destroyer/10619_Battleship.obj", "Destroyer.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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,11 @@
 | 
			
		||||
# Blender MTL File: 'untitletttd.blend'
 | 
			
		||||
# Material Count: 1
 | 
			
		||||
 | 
			
		||||
newmtl None
 | 
			
		||||
Ns 0
 | 
			
		||||
Ka 0.000000 0.000000 0.000000
 | 
			
		||||
Kd 0.8 0.8 0.8
 | 
			
		||||
Ks 0.8 0.8 0.8
 | 
			
		||||
d 1
 | 
			
		||||
illum 2
 | 
			
		||||
map_Kd C:\Users\Convidado.Cliente-JMF-PC\Desktop\ghftht.png
 | 
			
		||||
@@ -0,0 +1,73 @@
 | 
			
		||||
newmtl Battleship
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.00 0.00 0.00
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
map_Kd BattleshipC.jpg
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn1SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn2SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.00 0.00 0.00
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn3SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn4SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn5SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn6SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn7SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
newmtl blinn8SG
 | 
			
		||||
illum 4
 | 
			
		||||
Kd 0.50 0.50 0.50
 | 
			
		||||
Ka 0.00 0.00 0.00
 | 
			
		||||
Tf 1.00 1.00 1.00
 | 
			
		||||
Ni 1.00
 | 
			
		||||
Ks 0.50 0.50 0.50
 | 
			
		||||
Ns 256.00
 | 
			
		||||
| 
		 After Width: | Height: | Size: 360 KiB  | 
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/battleship-v1--611736.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
| 
		 After Width: | Height: | Size: 35 KiB  | 
@@ -0,0 +1,64 @@
 | 
			
		||||
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
 | 
			
		||||
# File Created: 13.03.2012 15:35:28
 | 
			
		||||
 | 
			
		||||
newmtl metall
 | 
			
		||||
	Ns 31.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.2431 0.2431 0.2431
 | 
			
		||||
	Kd 0.2431 0.2431 0.2431
 | 
			
		||||
	Ks 0.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl 13922_StatenIslandFerry
 | 
			
		||||
	Ns 50.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.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
	map_Ka 13922_StatenIslandFerry_diffuse.jpg
 | 
			
		||||
	map_Kd 13922_StatenIslandFerry_diffuse.jpg
 | 
			
		||||
 | 
			
		||||
newmtl metall2
 | 
			
		||||
	Ns 50.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.2118 0.2824 0.3451
 | 
			
		||||
	Kd 0.2118 0.2824 0.3451
 | 
			
		||||
	Ks 0.5850 0.5850 0.5850
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl boat
 | 
			
		||||
	Ns 34.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.6588 0.3059 0.1294
 | 
			
		||||
	Kd 0.6588 0.3059 0.1294
 | 
			
		||||
	Ks 0.3600 0.3600 0.3600
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
 | 
			
		||||
newmtl white
 | 
			
		||||
	Ns 10.0000
 | 
			
		||||
	Ni 1.5000
 | 
			
		||||
	d 1.0000
 | 
			
		||||
	Tr 0.0000
 | 
			
		||||
	Tf 1.0000 1.0000 1.0000 
 | 
			
		||||
	illum 2
 | 
			
		||||
	Ka 0.9686 0.9686 0.9686
 | 
			
		||||
	Kd 0.9686 0.9686 0.9686
 | 
			
		||||
	Ks 0.0000 0.0000 0.0000
 | 
			
		||||
	Ke 0.0000 0.0000 0.0000
 | 
			
		||||
| 
		 After Width: | Height: | Size: 235 KiB  | 
@@ -0,0 +1,18 @@
 | 
			
		||||
# 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
 | 
			
		||||
| 
		 After Width: | Height: | Size: 94 KiB  | 
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/3d-model/wwii-ship-uk-king-george-v-class-battleship-v1--185381.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
@@ -2,11 +2,11 @@
 | 
			
		||||
    id 'buildlogic.java-library-conventions'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
description = 'MDGA common model'
 | 
			
		||||
description = 'Battleship common model'
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    api project(":common")
 | 
			
		||||
    api libs.jme3.networking
 | 
			
		||||
    implementation libs.gson
 | 
			
		||||
    testImplementation libs.mockito.core
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,103 @@
 | 
			
		||||
////////////////////////////////////////
 | 
			
		||||
// 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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||