Compare commits
	
		
			1 Commits
		
	
	
		
			1376da3ba6
			...
			revert-71a
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					9abf7350e4 | 
							
								
								
									
										27
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,27 +0,0 @@
 | 
				
			|||||||
# Gradle
 | 
					 | 
				
			||||||
.gradle
 | 
					 | 
				
			||||||
build
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# VSC
 | 
					 | 
				
			||||||
bin
 | 
					 | 
				
			||||||
.vscode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# IntelliJ
 | 
					 | 
				
			||||||
*.iml
 | 
					 | 
				
			||||||
.idea
 | 
					 | 
				
			||||||
out
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Eclipse
 | 
					 | 
				
			||||||
.classpath
 | 
					 | 
				
			||||||
.project
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
# Libraries
 | 
					 | 
				
			||||||
*.so
 | 
					 | 
				
			||||||
*.dylib
 | 
					 | 
				
			||||||
*.dll
 | 
					 | 
				
			||||||
*.jar
 | 
					 | 
				
			||||||
*.class
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.DS_Store
 | 
					 | 
				
			||||||
!Projekte/gradle/wrapper/gradle-wrapper.jar
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										1
									
								
								Dokumente/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1 +0,0 @@
 | 
				
			|||||||
#
 | 
					 | 
				
			||||||
							
								
								
									
										3
									
								
								Projekte/.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						@@ -1,3 +0,0 @@
 | 
				
			|||||||
*.bat           text eol=crlf
 | 
					 | 
				
			||||||
gradlew         text eol=lf
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
    <configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application"
 | 
					 | 
				
			||||||
                   singleton="false">
 | 
					 | 
				
			||||||
        <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/>
 | 
					 | 
				
			||||||
        <module name="Projekte.battleship.client.main"/>
 | 
					 | 
				
			||||||
        <option name="VM_PARAMETERS" value="-XstartOnFirstThread"/>
 | 
					 | 
				
			||||||
        <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
					 | 
				
			||||||
        <extension name="coverage">
 | 
					 | 
				
			||||||
            <pattern>
 | 
					 | 
				
			||||||
                <option name="PATTERN" value="pp.battleship.client.*"/>
 | 
					 | 
				
			||||||
                <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
            </pattern>
 | 
					 | 
				
			||||||
        </extension>
 | 
					 | 
				
			||||||
        <method v="2">
 | 
					 | 
				
			||||||
            <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
        </method>
 | 
					 | 
				
			||||||
    </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false"
 | 
					 | 
				
			||||||
                 nameIsGenerated="true">
 | 
					 | 
				
			||||||
    <option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/>
 | 
					 | 
				
			||||||
    <module name="Projekte.battleship.client.main"/>
 | 
					 | 
				
			||||||
    <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
 | 
					 | 
				
			||||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
					 | 
				
			||||||
    <extension name="coverage">
 | 
					 | 
				
			||||||
      <pattern>
 | 
					 | 
				
			||||||
        <option name="PATTERN" value="pp.battleship.client.*"/>
 | 
					 | 
				
			||||||
        <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
      </pattern>
 | 
					 | 
				
			||||||
    </extension>
 | 
					 | 
				
			||||||
    <method v="2">
 | 
					 | 
				
			||||||
      <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
    </method>
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="BattleshipServer" type="Application" factoryName="Application"
 | 
					 | 
				
			||||||
                 nameIsGenerated="true">
 | 
					 | 
				
			||||||
    <option name="MAIN_CLASS_NAME" value="pp.battleship.server.BattleshipServer"/>
 | 
					 | 
				
			||||||
    <module name="Projekte.battleship.server.main"/>
 | 
					 | 
				
			||||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
					 | 
				
			||||||
    <extension name="coverage">
 | 
					 | 
				
			||||||
      <pattern>
 | 
					 | 
				
			||||||
        <option name="PATTERN" value="pp.battleship.server.*"/>
 | 
					 | 
				
			||||||
        <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
      </pattern>
 | 
					 | 
				
			||||||
    </extension>
 | 
					 | 
				
			||||||
    <method v="2">
 | 
					 | 
				
			||||||
      <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
    </method>
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
    <configuration default="false" name="MonopolyApp (Mac)" type="Application" factoryName="Application"
 | 
					 | 
				
			||||||
                   singleton="false">
 | 
					 | 
				
			||||||
        <option name="MAIN_CLASS_NAME" value="pp.monopoly.client.MonopolyApp"/>
 | 
					 | 
				
			||||||
        <module name="Projekte.monopoly.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.monopoly.client.*"/>
 | 
					 | 
				
			||||||
                <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
            </pattern>
 | 
					 | 
				
			||||||
        </extension>
 | 
					 | 
				
			||||||
        <method v="2">
 | 
					 | 
				
			||||||
            <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
        </method>
 | 
					 | 
				
			||||||
    </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,18 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="MonopolyApp" type="Application" factoryName="Application" singleton="false"
 | 
					 | 
				
			||||||
                 nameIsGenerated="true">
 | 
					 | 
				
			||||||
    <option name="MAIN_CLASS_NAME" value="pp.monopoly.client.MonopolyApp"/>
 | 
					 | 
				
			||||||
    <module name="Projekte.monopoly.client.main"/>
 | 
					 | 
				
			||||||
    <option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties"/>
 | 
					 | 
				
			||||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
					 | 
				
			||||||
    <extension name="coverage">
 | 
					 | 
				
			||||||
      <pattern>
 | 
					 | 
				
			||||||
        <option name="PATTERN" value="pp.monopoly.client.*"/>
 | 
					 | 
				
			||||||
        <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
      </pattern>
 | 
					 | 
				
			||||||
    </extension>
 | 
					 | 
				
			||||||
    <method v="2">
 | 
					 | 
				
			||||||
      <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
    </method>
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="MonopolyServer" type="Application" factoryName="Application"
 | 
					 | 
				
			||||||
                 nameIsGenerated="true">
 | 
					 | 
				
			||||||
    <option name="MAIN_CLASS_NAME" value="pp.monopoly.server.MonopolyServer"/>
 | 
					 | 
				
			||||||
    <module name="Projekte.monopoly.server.main"/>
 | 
					 | 
				
			||||||
    <option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
 | 
					 | 
				
			||||||
    <extension name="coverage">
 | 
					 | 
				
			||||||
      <pattern>
 | 
					 | 
				
			||||||
        <option name="PATTERN" value="pp.monopoly.server.*"/>
 | 
					 | 
				
			||||||
        <option name="ENABLED" value="true"/>
 | 
					 | 
				
			||||||
      </pattern>
 | 
					 | 
				
			||||||
    </extension>
 | 
					 | 
				
			||||||
    <method v="2">
 | 
					 | 
				
			||||||
      <option name="Make" enabled="true"/>
 | 
					 | 
				
			||||||
    </method>
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,24 +0,0 @@
 | 
				
			|||||||
<component name="ProjectRunConfigurationManager">
 | 
					 | 
				
			||||||
  <configuration default="false" name="Projekte [test]" type="GradleRunConfiguration" factoryName="Gradle">
 | 
					 | 
				
			||||||
    <ExternalSystemSettings>
 | 
					 | 
				
			||||||
      <option name="executionName" />
 | 
					 | 
				
			||||||
      <option name="externalProjectPath" value="$PROJECT_DIR$" />
 | 
					 | 
				
			||||||
      <option name="externalSystemIdString" value="GRADLE" />
 | 
					 | 
				
			||||||
      <option name="scriptParameters" value="--continue" />
 | 
					 | 
				
			||||||
      <option name="taskDescriptions">
 | 
					 | 
				
			||||||
        <list />
 | 
					 | 
				
			||||||
      </option>
 | 
					 | 
				
			||||||
      <option name="taskNames">
 | 
					 | 
				
			||||||
        <list>
 | 
					 | 
				
			||||||
          <option value="test" />
 | 
					 | 
				
			||||||
        </list>
 | 
					 | 
				
			||||||
      </option>
 | 
					 | 
				
			||||||
      <option name="vmOptions" />
 | 
					 | 
				
			||||||
    </ExternalSystemSettings>
 | 
					 | 
				
			||||||
    <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
 | 
					 | 
				
			||||||
    <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
 | 
					 | 
				
			||||||
    <DebugAllEnabled>false</DebugAllEnabled>
 | 
					 | 
				
			||||||
    <RunAsTest>false</RunAsTest>
 | 
					 | 
				
			||||||
    <method v="2" />
 | 
					 | 
				
			||||||
  </configuration>
 | 
					 | 
				
			||||||
</component>
 | 
					 | 
				
			||||||
@@ -1,189 +0,0 @@
 | 
				
			|||||||
# Beispielprogramme des Programmierprojekts
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Hier ist der Quellcode für das in der Einarbeitungsphase genutzte Spiel
 | 
					 | 
				
			||||||
_Battleships_ zu finden. Die Quellen bestehen aus den folgenden
 | 
					 | 
				
			||||||
Gradle-Unterprojekten:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* _:battleship:server_
 | 
					 | 
				
			||||||
* _:battleship:client_
 | 
					 | 
				
			||||||
* _:battleship:model_
 | 
					 | 
				
			||||||
* _:battleship:converter_
 | 
					 | 
				
			||||||
* _:common_
 | 
					 | 
				
			||||||
* _:jme-common_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
_Battleships_ ist ein netzwerkbasiertes Spiel und besteht aus einem Server- und
 | 
					 | 
				
			||||||
einem Clientanteil, die in den Unterprojekten _:battleship:server_ und
 | 
					 | 
				
			||||||
_:battleship:client_ realisiert sind. Beide nutzen das Unterprojekt
 | 
					 | 
				
			||||||
_:battleship:model_, das den gemeinsamen Modellanteil enthält.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Die Unterprojekte _:common_ und _:jme-common_ enthalten Hilfsklassen.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Das Unterprojekt _:battleship:converter_ wird für _Battleships_ selbst nicht
 | 
					 | 
				
			||||||
benötigt, sondern enthält lediglich den Code, um ein im Spiel verwendetes
 | 
					 | 
				
			||||||
3d-Modell eines Schlachtschiffs in eine _J3O_-Datei umzuwandeln, die von jME
 | 
					 | 
				
			||||||
einfacher geladen werden kann.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 1 Vorbereitung
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Für das Programmierprojekt empfehlen wir die Verwendung von Java 20. Unter Linux
 | 
					 | 
				
			||||||
sollte [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20)
 | 
					 | 
				
			||||||
als JDK verwendet werden, andere JDKs können unter Linux Probleme verursachen.
 | 
					 | 
				
			||||||
Auf anderen Betriebssystemen empfehlen wir aber ebenfalls Temurin. Im Folgenden
 | 
					 | 
				
			||||||
ist beschrieben, wie Sie Temurin installieren und die Umgebungsvariable
 | 
					 | 
				
			||||||
**JAVA_HOME** richtig setzen, damit Sie Gradle (siehe unten) verwenden können.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1.1 Installation von Temurin
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Laden Sie [_Eclipse Temurin_](https://adoptium.net/temurin/releases/?version=20)
 | 
					 | 
				
			||||||
entsprechend Ihrem Betriebssystem und Ihrer Prozessorarchitektur herunter und
 | 
					 | 
				
			||||||
entpacken Sie das Archiv in einem Verzeichnis Ihrer Wahl auf Ihrem Rechner.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
### 1.2 Setzen von JAVA_HOME
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Zur Verwendung mit Gradle muss die Umgebungsvariable **JAVA_HOME** richtig
 | 
					 | 
				
			||||||
gesetzt werden. Folgen Sie dazu den nachfolgenden Anweisungen entsprechend Ihrem
 | 
					 | 
				
			||||||
Betriebssystem:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* **Windows:**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Öffnen Sie ihre Powershell (Core) bzw. ihr Windows Terminal mit Powershell
 | 
					 | 
				
			||||||
  (Core). Überprüfen Sie, ob die Umgebungsvariable korrekt gesetzt ist:  
 | 
					 | 
				
			||||||
  `Get-ChildItem -Path Env:JAVA_HOME`  
 | 
					 | 
				
			||||||
  Falls kein oder ein falscher Pfad gesetzt ist, setzen Sie diesen mit dem
 | 
					 | 
				
			||||||
  folgenden Kommando (in einer Zeile):  
 | 
					 | 
				
			||||||
  `[System.Environment]::SetEnvironmentVariable('JAVA_HOME','<Pfad zum SDK>',[System.EnvironmentVariableTarget]::User)`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Alternativ können Sie die GUI verwenden. Unter Windows 10 klicken Sie die
 | 
					 | 
				
			||||||
  Windows-Taste und dann das Zahnrad um die Einstellungen zu öffnen. Dort wählen
 | 
					 | 
				
			||||||
  Sie "System", dann "Info" (links unten) und nun
 | 
					 | 
				
			||||||
  "Erweiterte Systemeinstellungen" (rechts) um den Dialog "Systemeigenschaften"
 | 
					 | 
				
			||||||
  zu starten. Im Reiter "Erweitert" klicken Sie
 | 
					 | 
				
			||||||
  "Umgebungsvariablen..." und klicken dann unter "Benutzervariablen" den Knopf
 | 
					 | 
				
			||||||
  "Neu..." um JAVA_HOME anzulegen oder "Bearbeiten" um ihn zu ändern. Geben Sie
 | 
					 | 
				
			||||||
  als Name `JAVA_HOME` und als Wert den Pfad ein. Schließen Sie mit "OK".
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  > **(!) Beachten Sie, dass Sie die jeweilige Applikation neu starten müssen**,
 | 
					 | 
				
			||||||
  > um von der gesetzten Umgebungsvariablen Notiz zu nehmen.
 | 
					 | 
				
			||||||
  > Dies betrifft auch die Shell, die Sie gerade verwenden.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* **UNIX (Linux/MacOS):**
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Öffnen oder erstellen Sie die Datei `~/.profile` (wenn Sie die Bash verwenden;
 | 
					 | 
				
			||||||
  bei anderen Shells sind es andere Dateien) und ergänzen Sie am Ende der Datei
 | 
					 | 
				
			||||||
  die Zeile:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  `export JAVA_HOME="<Pfad zum entpackten Archiv>"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Ersetzen Sie dabei `<Pfad zum entpackten Archiv>` mit dem entsprechenden Pfad.
 | 
					 | 
				
			||||||
  Zum Beispiel:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  `export JAVA_HOME="/home/user/jdk-20.0.2"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Fügen Sie dann die folgende Zeile hinzu:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  `export PATH="$JAVA_HOME/bin:$PATH"`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 2 Programmstart
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Grundsätzlich kann man das gesamte Projekt einfach in IntelliJ öffnen. Details
 | 
					 | 
				
			||||||
dazu sind im Aufgabenblatt zur Einarbeitungsaufgabe zu finden. Im Folgenden ist
 | 
					 | 
				
			||||||
beschrieben, wie die _Batttleships_ unmittelbar von der Kommandozeile gestartet
 | 
					 | 
				
			||||||
werden können.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Um _Battleships_ spielen zu können, muss man zuerst das Server-Programm auf
 | 
					 | 
				
			||||||
einem Rechner und dann zweimal das Client-Programm auf beliebigen Rechnern
 | 
					 | 
				
			||||||
starten, die TCP/IP-Verbindungen zum Server erlauben. Natürlich ist es auch
 | 
					 | 
				
			||||||
möglich, alle drei Programme auf demselben Rechner zu starten.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Es empfiehlt sich der Start von der Kommandozeile. Will man alle drei Programme
 | 
					 | 
				
			||||||
auf demselben Rechner starten, sollte man dazu drei Shell-Instanzen öffnen und
 | 
					 | 
				
			||||||
in jeder eines der Programme starten. Auf diese Weise können die
 | 
					 | 
				
			||||||
Logging-Ausgaben der drei Programme voneinander unterschieden werden.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Das Server-Programm startet man unmittelbar mit Gradle mit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`./gradlew :battleship:server:run`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Unter Windows kann es je nach Shell (Eingabeaufforderung cmd) erforderlich sein,
 | 
					 | 
				
			||||||
`/` jeweils durch `\ ` zu ersetzen.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Im Verzeichnis `battleship/server` befindet sich die Datei `config.propeties`,
 | 
					 | 
				
			||||||
worüber sich der Server konfigurieren lässt. Mit der Zeile `port=1234` lässt
 | 
					 | 
				
			||||||
sich der verwendete Server-Port (hier 1234) einstellen. Außerdem befindet sich
 | 
					 | 
				
			||||||
dort die Datei `logging.properties`, womit das Logging des Servers konfiguriert
 | 
					 | 
				
			||||||
wird.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Das Client-Programm startet man unmittelbar mit Gradle mit
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`./gradlew :battleship:client:run`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Die Datei `logging.properties` im Verzeichnis `battleship/client` konfiguriert
 | 
					 | 
				
			||||||
das Logging des Clients.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Alternativ kann man auch die Start-Skripte
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
* `./battleship/server/build/install/battleship-server/bin/battleship-server`
 | 
					 | 
				
			||||||
* `./battleship/client/build/install/battleship/bin/battleship`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
direkt in der Kommandozeile starten. Allerdings müssen sie zuvor mittels
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`./gradlew installDist`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
erzeugt worden sein. Beachten Sie aber, dass nur im **aktuellen
 | 
					 | 
				
			||||||
Arbeitsverzeichnis** nach den Dateien `config.properties` und
 | 
					 | 
				
			||||||
`logging.properties` gesucht wird und diese geladen werden. Das heißt, dass die
 | 
					 | 
				
			||||||
vordefinierten Dateien in den Verzeichnissen `battleship/server` und
 | 
					 | 
				
			||||||
`battleship/client` nur dann gelesen werden, wenn Sie diese Verzeichnisse als
 | 
					 | 
				
			||||||
aktuelle Arbeitsverzeichnisse nutzen. Wie üblich müssen Sie dazu in der
 | 
					 | 
				
			||||||
Kommandozeile
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`cd battleship/server`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bzw.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
`cd battleship/client`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
eingeben.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 3 Hinweise zu _Battleships_
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Der _Battleships_-Client hat ein Menü, in das man immer mit der
 | 
					 | 
				
			||||||
Esc-Taste kommt. Aus dem Menü heraus lässt sich das Programm auch schließen.
 | 
					 | 
				
			||||||
Beachte, dass sich beim Laden und Speichern eines Spiels kein Dateidialog
 | 
					 | 
				
			||||||
öffnet. Vielmehr muss man den Dateipfad in das Dialogfeld eingeben. Da
 | 
					 | 
				
			||||||
JSON-Dateien geschrieben werden, empfiehlt sich das Datei-Suffix _.json_.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
## 4 Allgemeine Gradle-Tasks:
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew clean`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Entfernt alle `build`-Verzeichnisse und alle erzeugten Dateien.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew classes`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Übersetzt den Quellcode und legt unter build den Bytecode sowie
 | 
					 | 
				
			||||||
  Ressourcen ab.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew javadoc`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Erzeugt die Dokumentation aus den JavaDoc-Kommentaren im Verzeichnis
 | 
					 | 
				
			||||||
  `build/docs/javadoc` des jeweiligen Unterprojekts.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew test`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Führt die JUnit-Tests durch. Ergebnisse sind im Verzeichnis
 | 
					 | 
				
			||||||
  `build/reports/tests` des jeweiligen Unterprojekts zu finden.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew build`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Führt die JUnit-Tests durch und erstellt in `build/distributions`
 | 
					 | 
				
			||||||
  gepackte Distributionsdateien
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
- `./gradlew installDist`
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  Erstellt unter `battleship/client/build/install` und
 | 
					 | 
				
			||||||
  `battleship/server/build/install` Verzeichnisse, die jeweils eine ausführbare
 | 
					 | 
				
			||||||
  Distribution samt Start-Skripten enthält (siehe oben).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
---
 | 
					 | 
				
			||||||
Juli 2024
 | 
					 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
plugins {
 | 
					 | 
				
			||||||
    id 'buildlogic.jme-application-conventions'
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
description = 'Battleship Client'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dependencies {
 | 
					 | 
				
			||||||
    implementation project(":jme-common")
 | 
					 | 
				
			||||||
    implementation project(":battleship:model")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    implementation libs.jme3.desktop
 | 
					 | 
				
			||||||
    implementation libs.jme3.effects
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    runtimeOnly libs.jme3.awt.dialogs
 | 
					 | 
				
			||||||
    runtimeOnly libs.jme3.plugins
 | 
					 | 
				
			||||||
    runtimeOnly libs.jme3.jogg
 | 
					 | 
				
			||||||
    runtimeOnly libs.jme3.testdata
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
application {
 | 
					 | 
				
			||||||
    mainClass = 'pp.battleship.client.BattleshipApp'
 | 
					 | 
				
			||||||
    applicationName = 'battleship'
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,73 +0,0 @@
 | 
				
			|||||||
########################################
 | 
					 | 
				
			||||||
## Programming project code
 | 
					 | 
				
			||||||
## UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
## www.unibw.de/inf2
 | 
					 | 
				
			||||||
## (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
########################################
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Battleship client configuration
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Specifies the map used by the opponent in single mode.
 | 
					 | 
				
			||||||
# Single mode is activated if this property is set.
 | 
					 | 
				
			||||||
#map.opponent=maps/map2.json
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Specifies the map used by the player in single mode.
 | 
					 | 
				
			||||||
# The player must define their own map if this property is not set.
 | 
					 | 
				
			||||||
map.own=maps/map1.json
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Coordinates of the shots fired by the RobotClient in the order listed.
 | 
					 | 
				
			||||||
# Example:
 | 
					 | 
				
			||||||
#   2, 0,\
 | 
					 | 
				
			||||||
#   2, 1,\
 | 
					 | 
				
			||||||
#   2, 2,\
 | 
					 | 
				
			||||||
#   2, 3
 | 
					 | 
				
			||||||
#  defines four shots, namely at the coordinates
 | 
					 | 
				
			||||||
#  (x=2, y=0), (x=2, y=1), (x=2, y=2), and (x=2, y=3)
 | 
					 | 
				
			||||||
robot.targets=2, 0,\
 | 
					 | 
				
			||||||
              2, 1,\
 | 
					 | 
				
			||||||
              2, 2,\
 | 
					 | 
				
			||||||
              2, 3
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Delay in milliseconds between each shot fired by the RobotClient.
 | 
					 | 
				
			||||||
robot.delay=500
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# The dimensions of the game map used in single mode.
 | 
					 | 
				
			||||||
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows.
 | 
					 | 
				
			||||||
map.width=10
 | 
					 | 
				
			||||||
map.height=10
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# The number of ships of each length available in single mode.
 | 
					 | 
				
			||||||
# The value is a comma-separated list where each element corresponds to the number of ships
 | 
					 | 
				
			||||||
# with a specific length. For example:
 | 
					 | 
				
			||||||
# ship.nums=4, 3, 2, 1
 | 
					 | 
				
			||||||
# This configuration means:
 | 
					 | 
				
			||||||
#   - 4 ships of length 1
 | 
					 | 
				
			||||||
#   - 3 ships of length 2
 | 
					 | 
				
			||||||
#   - 2 ships of length 3
 | 
					 | 
				
			||||||
#   - 1 ship of length 4
 | 
					 | 
				
			||||||
ship.nums=4, 3, 2, 1
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Screen settings
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Color of the text displayed at the top of the overlay.
 | 
					 | 
				
			||||||
# The format is (red, green, blue, alpha) where each value ranges from 0 to 1.
 | 
					 | 
				
			||||||
overlay.top.color=1, 1, 1, 1
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Application settings configuration
 | 
					 | 
				
			||||||
# Determines whether the settings window is shown at startup.
 | 
					 | 
				
			||||||
settings.show=false
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Specifies the width of the application window in pixels.
 | 
					 | 
				
			||||||
settings.resolution.width=1200
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Specifies the height of the application window in pixels.
 | 
					 | 
				
			||||||
settings.resolution.height=800
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Determines whether the application runs in full-screen mode.
 | 
					 | 
				
			||||||
settings.full-screen=false
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Enables or disables gamma correction to improve color accuracy.
 | 
					 | 
				
			||||||
settings.use-gamma-correction=true
 | 
					 | 
				
			||||||
#
 | 
					 | 
				
			||||||
# Indicates whether the statistics window is displayed during gameplay.
 | 
					 | 
				
			||||||
statistics.show=false
 | 
					 | 
				
			||||||
@@ -1,8 +0,0 @@
 | 
				
			|||||||
handlers=java.util.logging.ConsoleHandler
 | 
					 | 
				
			||||||
.level=INFO
 | 
					 | 
				
			||||||
pp.level=FINE
 | 
					 | 
				
			||||||
com.jme3.network.level=INFO
 | 
					 | 
				
			||||||
;com.jme3.util.TangentBinormalGenerator.level=SEVERE
 | 
					 | 
				
			||||||
java.util.logging.ConsoleHandler.level=FINER
 | 
					 | 
				
			||||||
java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter
 | 
					 | 
				
			||||||
;java.util.logging.SimpleFormatter.format=[%4$s %2$s] %5$s%n
 | 
					 | 
				
			||||||
@@ -1,66 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "width": 10,
 | 
					 | 
				
			||||||
  "height": 10,
 | 
					 | 
				
			||||||
  "ships": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 4,
 | 
					 | 
				
			||||||
      "x": 2,
 | 
					 | 
				
			||||||
      "y": 8,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 3,
 | 
					 | 
				
			||||||
      "x": 2,
 | 
					 | 
				
			||||||
      "y": 5,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 3,
 | 
					 | 
				
			||||||
      "x": 5,
 | 
					 | 
				
			||||||
      "y": 6,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 4,
 | 
					 | 
				
			||||||
      "y": 4,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 7,
 | 
					 | 
				
			||||||
      "y": 4,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 4,
 | 
					 | 
				
			||||||
      "y": 2,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 6,
 | 
					 | 
				
			||||||
      "y": 2,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 8,
 | 
					 | 
				
			||||||
      "y": 2,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 6,
 | 
					 | 
				
			||||||
      "y": 0,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 8,
 | 
					 | 
				
			||||||
      "y": 0,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,66 +0,0 @@
 | 
				
			|||||||
{
 | 
					 | 
				
			||||||
  "width": 10,
 | 
					 | 
				
			||||||
  "height": 10,
 | 
					 | 
				
			||||||
  "ships": [
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 4,
 | 
					 | 
				
			||||||
      "x": 0,
 | 
					 | 
				
			||||||
      "y": 5,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 3,
 | 
					 | 
				
			||||||
      "x": 0,
 | 
					 | 
				
			||||||
      "y": 9,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 3,
 | 
					 | 
				
			||||||
      "x": 2,
 | 
					 | 
				
			||||||
      "y": 6,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 4,
 | 
					 | 
				
			||||||
      "y": 8,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 2,
 | 
					 | 
				
			||||||
      "y": 4,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 2,
 | 
					 | 
				
			||||||
      "x": 2,
 | 
					 | 
				
			||||||
      "y": 1,
 | 
					 | 
				
			||||||
      "rot": "DOWN"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 6,
 | 
					 | 
				
			||||||
      "y": 2,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 8,
 | 
					 | 
				
			||||||
      "y": 2,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 6,
 | 
					 | 
				
			||||||
      "y": 0,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      "length": 1,
 | 
					 | 
				
			||||||
      "x": 8,
 | 
					 | 
				
			||||||
      "y": 0,
 | 
					 | 
				
			||||||
      "rot": "RIGHT"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,440 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.app.DebugKeysAppState;
 | 
					 | 
				
			||||||
import com.jme3.app.SimpleApplication;
 | 
					 | 
				
			||||||
import com.jme3.app.StatsAppState;
 | 
					 | 
				
			||||||
import com.jme3.font.BitmapFont;
 | 
					 | 
				
			||||||
import com.jme3.font.BitmapText;
 | 
					 | 
				
			||||||
import com.jme3.input.KeyInput;
 | 
					 | 
				
			||||||
import com.jme3.input.MouseInput;
 | 
					 | 
				
			||||||
import com.jme3.input.controls.ActionListener;
 | 
					 | 
				
			||||||
import com.jme3.input.controls.KeyTrigger;
 | 
					 | 
				
			||||||
import com.jme3.input.controls.MouseButtonTrigger;
 | 
					 | 
				
			||||||
import com.jme3.system.AppSettings;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.GuiGlobals;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.style.BaseStyles;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.BattleAppState;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.EditorAppState;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.GameMusic;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.SeaAppState;
 | 
					 | 
				
			||||||
import pp.battleship.game.client.BattleshipClient;
 | 
					 | 
				
			||||||
import pp.battleship.game.client.ClientGameLogic;
 | 
					 | 
				
			||||||
import pp.battleship.game.client.ServerConnection;
 | 
					 | 
				
			||||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
					 | 
				
			||||||
import pp.battleship.game.singlemode.ServerConnectionMockup;
 | 
					 | 
				
			||||||
import pp.battleship.notification.ClientStateEvent;
 | 
					 | 
				
			||||||
import pp.battleship.notification.GameEventListener;
 | 
					 | 
				
			||||||
import pp.battleship.notification.InfoTextEvent;
 | 
					 | 
				
			||||||
import pp.dialog.DialogBuilder;
 | 
					 | 
				
			||||||
import pp.dialog.DialogManager;
 | 
					 | 
				
			||||||
import pp.graphics.Draw;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.File;
 | 
					 | 
				
			||||||
import java.io.FileInputStream;
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
import java.util.concurrent.ExecutorService;
 | 
					 | 
				
			||||||
import java.util.concurrent.Executors;
 | 
					 | 
				
			||||||
import java.util.logging.LogManager;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.Resources.lookup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The main class for the Battleship client application.
 | 
					 | 
				
			||||||
 * It manages the initialization, input setup, GUI setup, and game states for the client.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class BattleshipApp extends SimpleApplication implements BattleshipClient, GameEventListener {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Logger for logging messages within the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final Logger LOGGER = System.getLogger(BattleshipApp.class.getName());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Path to the styles script for GUI elements.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final String STYLES_SCRIPT = "Interface/Lemur/pp-styles.groovy"; //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Path to the font resource used in the GUI.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final String FONT = "Interface/Fonts/Default.fnt"; //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Path to the client configuration file, if one exists.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final File CONFIG_FILE = new File("client.properties");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Input mapping name for mouse clicks.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static final String CLICK = "CLICK";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Input mapping name for the Escape key.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final String ESC = "ESC";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Manager for handling dialogs within the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final DialogManager dialogManager = new DialogManager(this);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The server connection instance, used for communicating with the game server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final ServerConnection serverConnection;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Instance of the {@link Draw} class for rendering graphics.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Draw draw;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Text display at the top of the GUI for showing information to the user.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private BitmapText topText;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Executor service for handling asynchronous tasks within the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private ExecutorService executor;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handler for managing the client's game logic.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final ClientGameLogic logic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Configuration settings for the Battleship client application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final BattleshipAppConfig config;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Listener for handling actions triggered by the Escape key.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    static {
 | 
					 | 
				
			||||||
        // Configure logging
 | 
					 | 
				
			||||||
        LogManager manager = LogManager.getLogManager();
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            manager.readConfiguration(new FileInputStream("logging.properties"));
 | 
					 | 
				
			||||||
            LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (IOException e) {
 | 
					 | 
				
			||||||
            LOGGER.log(Level.INFO, e.getMessage());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Starts the Battleship application.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param args Command-line arguments for launching the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static void main(String[] args) {
 | 
					 | 
				
			||||||
        new BattleshipApp().start();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new {@code BattleshipApp} instance.
 | 
					 | 
				
			||||||
     * Initializes the configuration, server connection, and game logic listeners.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private BattleshipApp() {
 | 
					 | 
				
			||||||
        config = new BattleshipAppConfig();
 | 
					 | 
				
			||||||
        config.readFromIfExists(CONFIG_FILE);
 | 
					 | 
				
			||||||
        serverConnection = makeServerConnection();
 | 
					 | 
				
			||||||
        logic = new ClientGameLogic(serverConnection);
 | 
					 | 
				
			||||||
        logic.addListener(this);
 | 
					 | 
				
			||||||
        setShowSettings(config.getShowSettings());
 | 
					 | 
				
			||||||
        setSettings(makeSettings());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates and configures application settings from the client configuration.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return A configured {@link AppSettings} object.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private AppSettings makeSettings() {
 | 
					 | 
				
			||||||
        final AppSettings settings = new AppSettings(true);
 | 
					 | 
				
			||||||
        settings.setTitle(lookup("battleship.name"));
 | 
					 | 
				
			||||||
        settings.setResolution(config.getResolutionWidth(), config.getResolutionHeight());
 | 
					 | 
				
			||||||
        settings.setFullscreen(config.fullScreen());
 | 
					 | 
				
			||||||
        settings.setUseRetinaFrameBuffer(config.useRetinaFrameBuffer());
 | 
					 | 
				
			||||||
        settings.setGammaCorrection(config.useGammaCorrection());
 | 
					 | 
				
			||||||
        return settings;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Factory method for creating a server connection based on the current
 | 
					 | 
				
			||||||
     * client configuration.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return A {@link ServerConnection} instance, which could be a real or mock server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private ServerConnection makeServerConnection() {
 | 
					 | 
				
			||||||
        if (config.isSingleMode())
 | 
					 | 
				
			||||||
            return new ServerConnectionMockup(this);
 | 
					 | 
				
			||||||
        return new NetworkSupport(this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the dialog manager responsible for managing in-game dialogs.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The {@link DialogManager} instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    DialogManager getDialogManager() {
 | 
					 | 
				
			||||||
        return dialogManager;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the game logic handler for the client.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The {@link ClientGameLogic} instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public ClientGameLogic getGameLogic() {
 | 
					 | 
				
			||||||
        return logic;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the current configuration settings for the Battleship client.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The {@link BattleshipClientConfig} instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public BattleshipAppConfig getConfig() {
 | 
					 | 
				
			||||||
        return config;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the application.
 | 
					 | 
				
			||||||
     * Sets up input mappings, GUI, game states, and connects to the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void simpleInitApp() {
 | 
					 | 
				
			||||||
        setPauseOnLostFocus(false);
 | 
					 | 
				
			||||||
        draw = new Draw(assetManager);
 | 
					 | 
				
			||||||
        setupInput();
 | 
					 | 
				
			||||||
        setupStates();
 | 
					 | 
				
			||||||
        setupGui();
 | 
					 | 
				
			||||||
        serverConnection.connect();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets up the graphical user interface (GUI) for the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupGui() {
 | 
					 | 
				
			||||||
        GuiGlobals.initialize(this);
 | 
					 | 
				
			||||||
        BaseStyles.loadStyleResources(STYLES_SCRIPT);
 | 
					 | 
				
			||||||
        GuiGlobals.getInstance().getStyles().setDefaultStyle("pp"); //NON-NLS
 | 
					 | 
				
			||||||
        final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
 | 
					 | 
				
			||||||
        topText = new BitmapText(normalFont);
 | 
					 | 
				
			||||||
        final int height = context.getSettings().getHeight();
 | 
					 | 
				
			||||||
        topText.setLocalTranslation(10f, height - 10f, 0f);
 | 
					 | 
				
			||||||
        topText.setColor(config.getTopColor());
 | 
					 | 
				
			||||||
        guiNode.attachChild(topText);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Configures input mappings and sets up listeners for user interactions.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupInput() {
 | 
					 | 
				
			||||||
        inputManager.deleteMapping(INPUT_MAPPING_EXIT);
 | 
					 | 
				
			||||||
        inputManager.setCursorVisible(false);
 | 
					 | 
				
			||||||
        inputManager.addMapping(ESC, new KeyTrigger(KeyInput.KEY_ESCAPE));
 | 
					 | 
				
			||||||
        inputManager.addMapping(CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
 | 
					 | 
				
			||||||
        inputManager.addListener(escapeListener, ESC);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes and attaches the necessary application states for the game.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupStates() {
 | 
					 | 
				
			||||||
        if (config.getShowStatistics()) {
 | 
					 | 
				
			||||||
            final BitmapFont normalFont = assetManager.loadFont(FONT); //NON-NLS
 | 
					 | 
				
			||||||
            final StatsAppState stats = new StatsAppState(guiNode, normalFont);
 | 
					 | 
				
			||||||
            stateManager.attach(stats);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        flyCam.setEnabled(false);
 | 
					 | 
				
			||||||
        stateManager.detach(stateManager.getState(StatsAppState.class));
 | 
					 | 
				
			||||||
        stateManager.detach(stateManager.getState(DebugKeysAppState.class));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        attachGameSound();
 | 
					 | 
				
			||||||
        attachGameMusic();
 | 
					 | 
				
			||||||
        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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Attaches the background music state and sets its initial enabled state.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void attachGameMusic() {
 | 
					 | 
				
			||||||
        final GameMusic gameSound = new GameMusic();
 | 
					 | 
				
			||||||
        gameSound.setEnabled(GameMusic.enabledInPreferences());
 | 
					 | 
				
			||||||
        stateManager.attach(gameSound);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the application state every frame.
 | 
					 | 
				
			||||||
     * This method is called once per frame during the game loop.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf Time per frame in seconds.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void simpleUpdate(float tpf) {
 | 
					 | 
				
			||||||
        super.simpleUpdate(tpf);
 | 
					 | 
				
			||||||
        dialogManager.update(tpf);
 | 
					 | 
				
			||||||
        logic.update(tpf);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the Escape key action to either close the top dialog or show the main menu.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param isPressed Indicates whether the Escape key is pressed.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void escape(boolean isPressed) {
 | 
					 | 
				
			||||||
        if (!isPressed) return;
 | 
					 | 
				
			||||||
        if (dialogManager.showsDialog())
 | 
					 | 
				
			||||||
            dialogManager.escape();
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            new Menu(this).open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the {@link Draw} instance used for rendering graphical elements in the game.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The {@link Draw} instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public Draw getDraw() {
 | 
					 | 
				
			||||||
        return draw;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles a request to close the application.
 | 
					 | 
				
			||||||
     * If the request is initiated by pressing ESC, this parameter is true.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param esc If true, the request is due to the ESC key being pressed.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void requestClose(boolean esc) { /* do nothing */ }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Closes the application, displaying a confirmation dialog if the client is connected to a server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void closeApp() {
 | 
					 | 
				
			||||||
        if (serverConnection.isConnected())
 | 
					 | 
				
			||||||
            confirmDialog(lookup("confirm.leaving"), this::close);
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Closes the application, disconnecting from the server and stopping the application.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void close() {
 | 
					 | 
				
			||||||
        serverConnection.disconnect();
 | 
					 | 
				
			||||||
        stop();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the informational text displayed in the GUI.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param text The information text to display.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    void setInfoText(String text) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.DEBUG, "setInfoText {0}", text); //NON-NLS
 | 
					 | 
				
			||||||
        topText.setText(text);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the informational text in the GUI based on the key received in an {@link InfoTextEvent}.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param event The {@link InfoTextEvent} containing the key for the text to display.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void receivedEvent(InfoTextEvent event) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.DEBUG, "received info text {0}", event.key()); //NON-NLS
 | 
					 | 
				
			||||||
        setInfoText(lookup(event.key()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles client state events to update the game states accordingly.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param event The {@link ClientStateEvent} representing the state change.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void receivedEvent(ClientStateEvent event) {
 | 
					 | 
				
			||||||
        stateManager.getState(EditorAppState.class).setEnabled(logic.showEditor());
 | 
					 | 
				
			||||||
        stateManager.getState(BattleAppState.class).setEnabled(logic.showBattle());
 | 
					 | 
				
			||||||
        stateManager.getState(SeaAppState.class).setEnabled(logic.showBattle());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the executor service used for handling multithreaded tasks.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The {@link ExecutorService} instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ExecutorService getExecutor() {
 | 
					 | 
				
			||||||
        if (executor == null)
 | 
					 | 
				
			||||||
            executor = Executors.newCachedThreadPool();
 | 
					 | 
				
			||||||
        return executor;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Stops the application, shutting down the executor service and halting execution.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param waitFor If true, waits for the application to stop before returning.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void stop(boolean waitFor) {
 | 
					 | 
				
			||||||
        if (executor != null) executor.shutdownNow();
 | 
					 | 
				
			||||||
        super.stop(waitFor);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Displays a confirmation dialog with a specified question and action for the "Yes" button.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param question  The question to display in the dialog.
 | 
					 | 
				
			||||||
     * @param yesAction The action to perform if "Yes" is selected.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    void confirmDialog(String question, Runnable yesAction) {
 | 
					 | 
				
			||||||
        DialogBuilder.simple(dialogManager)
 | 
					 | 
				
			||||||
                     .setTitle(lookup("dialog.question"))
 | 
					 | 
				
			||||||
                     .setText(question)
 | 
					 | 
				
			||||||
                     .setOkButton(lookup("button.yes"), yesAction)
 | 
					 | 
				
			||||||
                     .setNoButton(lookup("button.no"))
 | 
					 | 
				
			||||||
                     .build()
 | 
					 | 
				
			||||||
                     .open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Displays an error dialog with the specified error message.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param errorMessage The error message to display in the dialog.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    void errorDialog(String errorMessage) {
 | 
					 | 
				
			||||||
        DialogBuilder.simple(dialogManager)
 | 
					 | 
				
			||||||
                     .setTitle(lookup("dialog.error"))
 | 
					 | 
				
			||||||
                     .setText(errorMessage)
 | 
					 | 
				
			||||||
                     .setOkButton(lookup("button.ok"))
 | 
					 | 
				
			||||||
                     .build()
 | 
					 | 
				
			||||||
                     .open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,196 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import pp.battleship.game.singlemode.BattleshipClientConfig;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Provides access to the Battleship application configuration.
 | 
					 | 
				
			||||||
 * Extends {@link BattleshipClientConfig} to include additional properties specific to the client,
 | 
					 | 
				
			||||||
 * particularly those related to screen settings and visual customization.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 | 
				
			||||||
 * <b>Note:</b> Attributes of this class should not be marked as {@code final}
 | 
					 | 
				
			||||||
 * to ensure proper functionality when reading from a properties file.
 | 
					 | 
				
			||||||
 * </p>
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class BattleshipAppConfig extends BattleshipClientConfig {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Converts a string value found in the properties file into an object of the specified type.
 | 
					 | 
				
			||||||
     * Extends the superclass method to support conversion to {@link ColorRGBA}.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param value      the string value to be converted
 | 
					 | 
				
			||||||
     * @param targetType the target type into which the value string is converted
 | 
					 | 
				
			||||||
     * @return the converted object of the specified type
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected Object convertToType(String value, Class<?> targetType) {
 | 
					 | 
				
			||||||
        if (targetType == ColorRGBA.class)
 | 
					 | 
				
			||||||
            return makeColorRGBA(value);
 | 
					 | 
				
			||||||
        return super.convertToType(value, targetType);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Converts the specified string value to a corresponding {@link ColorRGBA} object.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param value the color in the format "red, green, blue, alpha" with all values in the range [0..1]
 | 
					 | 
				
			||||||
     * @return a {@link ColorRGBA} object representing the color
 | 
					 | 
				
			||||||
     * @throws IllegalArgumentException if the input string is not in the expected format
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static ColorRGBA makeColorRGBA(String value) {
 | 
					 | 
				
			||||||
        String[] split = value.split(",", -1);
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            if (split.length == 4)
 | 
					 | 
				
			||||||
                return new ColorRGBA(Float.parseFloat(split[0]),
 | 
					 | 
				
			||||||
                                     Float.parseFloat(split[1]),
 | 
					 | 
				
			||||||
                                     Float.parseFloat(split[2]),
 | 
					 | 
				
			||||||
                                     Float.parseFloat(split[3]));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (NumberFormatException e) {
 | 
					 | 
				
			||||||
            // deliberately left empty
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        throw new IllegalArgumentException(value + " should consist of exactly 4 numbers");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The width of the game view resolution in pixels.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.resolution.width") //NON-NLS
 | 
					 | 
				
			||||||
    private int resolutionWidth = 1200;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The height of the game view resolution in pixels.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.resolution.height") //NON-NLS
 | 
					 | 
				
			||||||
    private int resolutionHeight = 800;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Specifies whether the game should start in full-screen mode.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.full-screen") //NON-NLS
 | 
					 | 
				
			||||||
    private boolean fullScreen = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Specifies whether gamma correction should be enabled.
 | 
					 | 
				
			||||||
     * If enabled, the main framebuffer is configured for sRGB colors,
 | 
					 | 
				
			||||||
     * and sRGB images are linearized.
 | 
					 | 
				
			||||||
     * <p>
 | 
					 | 
				
			||||||
     * Requires a GPU that supports GL_ARB_framebuffer_sRGB; otherwise, this setting will be ignored.
 | 
					 | 
				
			||||||
     * </p>
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.use-gamma-correction") //NON-NLS
 | 
					 | 
				
			||||||
    private boolean useGammaCorrection = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Specifies whether full resolution framebuffers should be used on Retina displays.
 | 
					 | 
				
			||||||
     * This setting is ignored on non-Retina platforms.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.use-retina-framebuffer") //NON-NLS
 | 
					 | 
				
			||||||
    private boolean useRetinaFrameBuffer = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Specifies whether the settings window should be shown for configuring the game.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("settings.show") //NON-NLS
 | 
					 | 
				
			||||||
    private boolean showSettings = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Specifies whether the JME statistics window should be shown in the lower left corner of the screen.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("statistics.show") //NON-NLS
 | 
					 | 
				
			||||||
    private boolean showStatistics = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The color of the top text during gameplay, represented as a {@link ColorRGBA} object.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Property("overlay.top.color") //NON-NLS
 | 
					 | 
				
			||||||
    private ColorRGBA topColor = ColorRGBA.White;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a default {@code BattleshipAppConfig} with predefined values.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public BattleshipAppConfig() {
 | 
					 | 
				
			||||||
        // Default constructor
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the width of the game view resolution in pixels.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the width of the game view resolution in pixels
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public int getResolutionWidth() {
 | 
					 | 
				
			||||||
        return resolutionWidth;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the height of the game view resolution in pixels.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the height of the game view resolution in pixels
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public int getResolutionHeight() {
 | 
					 | 
				
			||||||
        return resolutionHeight;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns whether the game should start in full-screen mode.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if the game should start in full-screen mode; {@code false} otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean fullScreen() {
 | 
					 | 
				
			||||||
        return fullScreen;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns whether gamma correction is enabled.
 | 
					 | 
				
			||||||
     * If enabled, the main framebuffer is configured for sRGB colors,
 | 
					 | 
				
			||||||
     * and sRGB images are linearized.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if gamma correction is enabled; {@code false} otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean useGammaCorrection() {
 | 
					 | 
				
			||||||
        return useGammaCorrection;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns whether full resolution framebuffers should be used on Retina displays.
 | 
					 | 
				
			||||||
     * This setting is ignored on non-Retina platforms.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if full resolution framebuffers should be used on Retina displays; {@code false} otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean useRetinaFrameBuffer() {
 | 
					 | 
				
			||||||
        return useRetinaFrameBuffer;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns whether the settings window should be shown for configuring the game.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if the settings window should be shown; {@code false} otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean getShowSettings() {
 | 
					 | 
				
			||||||
        return showSettings;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns whether the JME statistics window should be shown in the lower left corner of the screen.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if the statistics window should be shown; {@code false} otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean getShowStatistics() {
 | 
					 | 
				
			||||||
        return showStatistics;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the color of the top text during gameplay as a {@link ColorRGBA} object.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the color of the top text during gameplay
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ColorRGBA getTopColor() {
 | 
					 | 
				
			||||||
        return topColor;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,102 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.app.Application;
 | 
					 | 
				
			||||||
import com.jme3.app.state.AbstractAppState;
 | 
					 | 
				
			||||||
import com.jme3.app.state.AppStateManager;
 | 
					 | 
				
			||||||
import pp.battleship.game.client.ClientGameLogic;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Abstract class representing a state in the Battleship game.
 | 
					 | 
				
			||||||
 * Extends the AbstractAppState from jMonkeyEngine to manage state behavior.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public abstract class BattleshipAppState extends AbstractAppState {
 | 
					 | 
				
			||||||
    private BattleshipApp app;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a new BattleshipAppState that is initially disabled.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @see #setEnabled(boolean)
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected BattleshipAppState() {
 | 
					 | 
				
			||||||
        setEnabled(false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the state manager and application.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param stateManager The state manager
 | 
					 | 
				
			||||||
     * @param application  The application instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void initialize(AppStateManager stateManager, Application application) {
 | 
					 | 
				
			||||||
        super.initialize(stateManager, application);
 | 
					 | 
				
			||||||
        this.app = (BattleshipApp) application;
 | 
					 | 
				
			||||||
        if (isEnabled()) enableState();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the BattleshipApp instance associated with this BattleshipAppState.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return The BattleshipApp instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public BattleshipApp getApp() {
 | 
					 | 
				
			||||||
        return app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the client game logic handler.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the client game logic handler
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ClientGameLogic getGameLogic() {
 | 
					 | 
				
			||||||
        return app.getGameLogic();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Checks if any dialog is currently displayed.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return true if any dialog is currently shown, false otherwise
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public boolean showsDialog() {
 | 
					 | 
				
			||||||
        return app.getDialogManager().showsDialog();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets the enabled state of the BattleshipAppState.
 | 
					 | 
				
			||||||
     * If the new state is the same as the current state, the method returns.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param enabled The new enabled state.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void setEnabled(boolean enabled) {
 | 
					 | 
				
			||||||
        if (isEnabled() == enabled) return;
 | 
					 | 
				
			||||||
        super.setEnabled(enabled);
 | 
					 | 
				
			||||||
        if (app != null) {
 | 
					 | 
				
			||||||
            if (enabled)
 | 
					 | 
				
			||||||
                enableState();
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                disableState();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This method is called when the state is enabled.
 | 
					 | 
				
			||||||
     * It is meant to be overridden by subclasses to perform
 | 
					 | 
				
			||||||
     * specific actions when the state is enabled.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected abstract void enableState();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This method is called when the state is disabled.
 | 
					 | 
				
			||||||
     * It is meant to be overridden by subclasses to perform
 | 
					 | 
				
			||||||
     * specific actions when the state is disabled.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected abstract void disableState();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,152 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.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 shellFlyingSound;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Checks if sound is enabled in the preferences.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return {@code true} if sound is enabled, {@code false} otherwise.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static boolean enabledInPreferences() {
 | 
					 | 
				
			||||||
        return PREFERENCES.getBoolean(ENABLED_PREF, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Toggles the game sound on or off.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void toggleSound() {
 | 
					 | 
				
			||||||
        setEnabled(!isEnabled());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets the enabled state of this AppState.
 | 
					 | 
				
			||||||
     * Overrides {@link com.jme3.app.state.AbstractAppState#setEnabled(boolean)}
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param enabled {@code true} to enable the AppState, {@code false} to disable it.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void setEnabled(boolean enabled) {
 | 
					 | 
				
			||||||
        if (isEnabled() == enabled) return;
 | 
					 | 
				
			||||||
        super.setEnabled(enabled);
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
 | 
					 | 
				
			||||||
        PREFERENCES.putBoolean(ENABLED_PREF, enabled);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the sound effects for the game.
 | 
					 | 
				
			||||||
     * Overrides {@link AbstractAppState#initialize(AppStateManager, Application)}
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param stateManager The state manager
 | 
					 | 
				
			||||||
     * @param app          The application
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void initialize(AppStateManager stateManager, Application app) {
 | 
					 | 
				
			||||||
        super.initialize(stateManager, app);
 | 
					 | 
				
			||||||
        shipDestroyedSound = loadSound(app, "Sound/Effects/sunken.wav"); //NON-NLS
 | 
					 | 
				
			||||||
        splashSound = loadSound(app, "Sound/Effects/splash.wav"); //NON-NLS
 | 
					 | 
				
			||||||
        explosionSound = loadSound(app, "Sound/Effects/explosion.wav"); //NON-NLS
 | 
					 | 
				
			||||||
        shellFlyingSound = loadSound(app, "Sound/Effects/shell_flying.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 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();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Plays the shell flying sound effect.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void shellFly() {
 | 
					 | 
				
			||||||
        if (isEnabled() && shellFlyingSound != null) {
 | 
					 | 
				
			||||||
            shellFlyingSound.playInstance();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles a recieved {@code SoundEvent} and plays the according sound.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @param event the Sound event to be processed
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void receivedEvent(SoundEvent event) {
 | 
					 | 
				
			||||||
        switch (event.sound()) {
 | 
					 | 
				
			||||||
            case EXPLOSION -> explosion();
 | 
					 | 
				
			||||||
            case SPLASH -> splash();
 | 
					 | 
				
			||||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
					 | 
				
			||||||
            case SHELL_FLYING -> shellFly();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,154 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.File;
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.util.prefs.Preferences;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Button;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Checkbox;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Label;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.style.ElementId;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.Resources.lookup;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.GameMusic;
 | 
					 | 
				
			||||||
import pp.dialog.Dialog;
 | 
					 | 
				
			||||||
import pp.dialog.StateCheckboxModel;
 | 
					 | 
				
			||||||
import pp.dialog.TextInputDialog;
 | 
					 | 
				
			||||||
import pp.battleship.client.gui.VolumeSlider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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 VolumeSlider slider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs the Menu dialog for the Battleship application.+
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param app the BattleshipApp instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public Menu(BattleshipApp app) {
 | 
					 | 
				
			||||||
        super(app.getDialogManager());
 | 
					 | 
				
			||||||
        this.app = app;
 | 
					 | 
				
			||||||
        slider = new VolumeSlider(app.getStateManager().getState(GameMusic.class));
 | 
					 | 
				
			||||||
        addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS
 | 
					 | 
				
			||||||
        addChild(new Checkbox(lookup("menu.sound-enabled"), new StateCheckboxModel(app, GameSound.class)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addChild(new Checkbox(lookup("menu.background-sound-enabled"), new StateCheckboxModel(app, GameMusic.class)));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        addChild(slider);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        addChild(loadButton).addClickCommands(s -> ifTopDialog(this::loadDialog));
 | 
					 | 
				
			||||||
        addChild(saveButton).addClickCommands(s -> ifTopDialog(this::saveDialog));
 | 
					 | 
				
			||||||
        addChild(new Button(lookup("menu.return-to-game"))).addClickCommands(s -> ifTopDialog(this::close));
 | 
					 | 
				
			||||||
        addChild(new Button(lookup("menu.quit"))).addClickCommands(s -> ifTopDialog(app::closeApp));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        update();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the state of the load and save buttons based on the game logic.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void update() {
 | 
					 | 
				
			||||||
        loadButton.setEnabled(app.getGameLogic().mayLoadMap());
 | 
					 | 
				
			||||||
        saveButton.setEnabled(app.getGameLogic().maySaveMap());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void update(float delta) {
 | 
					 | 
				
			||||||
        slider.update();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * As an escape action, this method closes the menu if it is the top dialog.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void escape() {
 | 
					 | 
				
			||||||
        close();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Functional interface for file actions.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @FunctionalInterface
 | 
					 | 
				
			||||||
    private interface FileAction {
 | 
					 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
         * Executes a file action.
 | 
					 | 
				
			||||||
         *
 | 
					 | 
				
			||||||
         * @param file the file to be processed
 | 
					 | 
				
			||||||
         * @throws IOException if an I/O error occurs
 | 
					 | 
				
			||||||
         */
 | 
					 | 
				
			||||||
        void run(File file) throws IOException;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the file action for the provided dialog.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param fileAction the file action to be executed
 | 
					 | 
				
			||||||
     * @param dialog     the dialog providing the file input
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void handle(FileAction fileAction, TextInputDialog dialog) {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            final String path = dialog.getInput().getText();
 | 
					 | 
				
			||||||
            PREFERENCES.put(LAST_PATH, path);
 | 
					 | 
				
			||||||
            fileAction.run(new File(path));
 | 
					 | 
				
			||||||
            dialog.close();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (IOException e) {
 | 
					 | 
				
			||||||
            app.errorDialog(e.getLocalizedMessage());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Shows a file dialog for loading or saving files.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param fileAction the action to perform with the selected file
 | 
					 | 
				
			||||||
     * @param label      the label for the dialog
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void fileDialog(FileAction fileAction, String label) {
 | 
					 | 
				
			||||||
        final TextInputDialog dialog =
 | 
					 | 
				
			||||||
                TextInputDialog.builder(app.getDialogManager())
 | 
					 | 
				
			||||||
                               .setLabel(lookup("label.file"))
 | 
					 | 
				
			||||||
                               .setFocus(TextInputDialog::getInput)
 | 
					 | 
				
			||||||
                               .setTitle(label)
 | 
					 | 
				
			||||||
                               .setOkButton(lookup("button.ok"), d -> handle(fileAction, d))
 | 
					 | 
				
			||||||
                               .setNoButton(lookup("button.cancel"))
 | 
					 | 
				
			||||||
                               .setOkClose(false)
 | 
					 | 
				
			||||||
                               .build();
 | 
					 | 
				
			||||||
        final String path = PREFERENCES.get(LAST_PATH, null);
 | 
					 | 
				
			||||||
        if (path != null)
 | 
					 | 
				
			||||||
            dialog.getInput().setText(path.trim());
 | 
					 | 
				
			||||||
        dialog.open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Shows the load dialog for loading maps.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void loadDialog() {
 | 
					 | 
				
			||||||
        fileDialog(app.getGameLogic()::loadMap, lookup("menu.map.load"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Shows the save dialog for saving maps.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void saveDialog() {
 | 
					 | 
				
			||||||
        fileDialog(app.getGameLogic()::saveMap, lookup("menu.map.save"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,177 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
import java.util.concurrent.ExecutionException;
 | 
					 | 
				
			||||||
import java.util.concurrent.Future;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Button;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Container;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Label;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.TextField;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.component.SpringGridLayout;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.Resources.lookup;
 | 
					 | 
				
			||||||
import pp.battleship.server.BattleshipServer;
 | 
					 | 
				
			||||||
import pp.dialog.Dialog;
 | 
					 | 
				
			||||||
import pp.dialog.DialogBuilder;
 | 
					 | 
				
			||||||
import pp.dialog.SimpleDialog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * 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 = "42069"; //NON-NLS
 | 
					 | 
				
			||||||
    private final NetworkSupport network;
 | 
					 | 
				
			||||||
    private final TextField host = new TextField(LOCALHOST);
 | 
					 | 
				
			||||||
    private final TextField port = new TextField(DEFAULT_PORT);
 | 
					 | 
				
			||||||
    // private final Button serverButton = new Button(lookup("client.server-star"));
 | 
					 | 
				
			||||||
    private final Button serverButton = new Button(lookup("client.server-start"));
 | 
					 | 
				
			||||||
    private String hostname;
 | 
					 | 
				
			||||||
    private int portNumber;
 | 
					 | 
				
			||||||
    private Future<Object> connectionFuture;
 | 
					 | 
				
			||||||
    private Dialog progressDialog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new NetworkDialog.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param network The NetworkSupport instance to be used for network operations.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    NetworkDialog(NetworkSupport network) {
 | 
					 | 
				
			||||||
        super(network.getApp().getDialogManager());
 | 
					 | 
				
			||||||
        this.network = network;
 | 
					 | 
				
			||||||
        host.setSingleLine(true);
 | 
					 | 
				
			||||||
        host.setPreferredWidth(400f);
 | 
					 | 
				
			||||||
        port.setSingleLine(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final BattleshipApp app = network.getApp();
 | 
					 | 
				
			||||||
        final Container input = new Container(new SpringGridLayout());
 | 
					 | 
				
			||||||
        input.addChild(new Label(lookup("host.name") + ":  "));
 | 
					 | 
				
			||||||
        input.addChild(host, 1);
 | 
					 | 
				
			||||||
        input.addChild(new Label(lookup("port.number") + ":  "));
 | 
					 | 
				
			||||||
        input.addChild(port, 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        DialogBuilder.simple(app.getDialogManager())
 | 
					 | 
				
			||||||
                     .setTitle(lookup("server.dialog"))
 | 
					 | 
				
			||||||
                     .setExtension(d -> d.addChild(input))
 | 
					 | 
				
			||||||
                     .setOkButton(lookup("button.connect"), d -> connect())
 | 
					 | 
				
			||||||
                     .setNoButton(lookup("button.cancel"), app::closeApp)
 | 
					 | 
				
			||||||
                     .setOkClose(false)
 | 
					 | 
				
			||||||
                     .setNoClose(false)
 | 
					 | 
				
			||||||
                     .build(this);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        //Add the button to start the sever
 | 
					 | 
				
			||||||
        addChild(serverButton).addClickCommands(s -> ifTopDialog(this::startServerInThread));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the action for the connect button in the connection dialog.
 | 
					 | 
				
			||||||
     * Tries to parse the port number and initiate connection to the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void connect() {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "connect to host={0}, port={1}", host, port); //NON-NLS
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            hostname = host.getText().trim().isEmpty() ? LOCALHOST : host.getText();
 | 
					 | 
				
			||||||
            portNumber = Integer.parseInt(port.getText());
 | 
					 | 
				
			||||||
            openProgressDialog();
 | 
					 | 
				
			||||||
            connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (NumberFormatException e) {
 | 
					 | 
				
			||||||
            network.getApp().errorDialog(lookup("port.must.be.integer"));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a dialog indicating that the connection is in progress.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void openProgressDialog() {
 | 
					 | 
				
			||||||
        progressDialog = DialogBuilder.simple(network.getApp().getDialogManager())
 | 
					 | 
				
			||||||
                                      .setText(lookup("label.connecting"))
 | 
					 | 
				
			||||||
                                      .build();
 | 
					 | 
				
			||||||
        progressDialog.open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Tries to initialize the network connection.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @throws RuntimeException If an error occurs when creating the client.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Object initNetwork() {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            network.initNetwork(hostname, portNumber);
 | 
					 | 
				
			||||||
            return null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        catch (Exception e) {
 | 
					 | 
				
			||||||
            throw new RuntimeException(e);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This method is called by {@linkplain pp.dialog.DialogManager#update(float)} for periodically
 | 
					 | 
				
			||||||
     * updating this dialog. T
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void update(float delta) {
 | 
					 | 
				
			||||||
        if (connectionFuture != null && connectionFuture.isDone())
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                connectionFuture.get();
 | 
					 | 
				
			||||||
                success();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (ExecutionException e) {
 | 
					 | 
				
			||||||
                failure(e.getCause());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (InterruptedException e) {
 | 
					 | 
				
			||||||
                LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
 | 
					 | 
				
			||||||
                Thread.currentThread().interrupt();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles a successful connection to the game server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void success() {
 | 
					 | 
				
			||||||
        connectionFuture = null;
 | 
					 | 
				
			||||||
        progressDialog.close();
 | 
					 | 
				
			||||||
        this.close();
 | 
					 | 
				
			||||||
        network.getApp().setInfoText(lookup("wait.for.an.opponent"));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles a failed connection attempt.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param e The cause of the failure.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void failure(Throwable e) {
 | 
					 | 
				
			||||||
        connectionFuture = null;
 | 
					 | 
				
			||||||
        progressDialog.close();
 | 
					 | 
				
			||||||
        network.getApp().errorDialog(lookup("server.connection.failed"));
 | 
					 | 
				
			||||||
        network.getApp().setInfoText(e.getLocalizedMessage());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Starts the server in a separate thread.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void startServerInThread() {
 | 
					 | 
				
			||||||
        serverButton.setEnabled(false);
 | 
					 | 
				
			||||||
        Thread serverThread = new Thread(() -> {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
                BattleshipServer.main(null);
 | 
					 | 
				
			||||||
            } catch (Exception e) {
 | 
					 | 
				
			||||||
                serverButton.setEnabled(true);
 | 
					 | 
				
			||||||
                LOGGER.log(Level.ERROR, "Server could not be started", e);
 | 
					 | 
				
			||||||
                network.getApp().errorDialog("Could not start server: " + e.getMessage());
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        serverThread.start();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,152 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.network.Client;
 | 
					 | 
				
			||||||
import com.jme3.network.ClientStateListener;
 | 
					 | 
				
			||||||
import com.jme3.network.Message;
 | 
					 | 
				
			||||||
import com.jme3.network.MessageListener;
 | 
					 | 
				
			||||||
import com.jme3.network.Network;
 | 
					 | 
				
			||||||
import pp.battleship.game.client.ServerConnection;
 | 
					 | 
				
			||||||
import pp.battleship.message.client.ClientMessage;
 | 
					 | 
				
			||||||
import pp.battleship.message.server.ServerMessage;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.Resources.lookup;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Manages the network connection for the Battleship application.
 | 
					 | 
				
			||||||
 * Handles connecting to and disconnecting from the server, and sending messages.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class NetworkSupport implements MessageListener<Client>, ClientStateListener, ServerConnection {
 | 
					 | 
				
			||||||
    private static final Logger LOGGER = System.getLogger(NetworkSupport.class.getName());
 | 
					 | 
				
			||||||
    private final BattleshipApp app;
 | 
					 | 
				
			||||||
    private Client client;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a NetworkSupport instance for the given Battleship application.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param app The Battleship application instance.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public NetworkSupport(BattleshipApp app) {
 | 
					 | 
				
			||||||
        this.app = app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Returns the Battleship application instance.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return Battleship application instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    BattleshipApp getApp() {
 | 
					 | 
				
			||||||
        return app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Checks if there is a connection to the game server.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return true if there is a connection to the game server, false otherwise.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public boolean isConnected() {
 | 
					 | 
				
			||||||
        return client != null && client.isConnected();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Attempts to join the game if there is no connection yet.
 | 
					 | 
				
			||||||
     * Opens a dialog for the user to enter the host and port information.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void connect() {
 | 
					 | 
				
			||||||
        if (client == null)
 | 
					 | 
				
			||||||
            new NetworkDialog(this).open();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Closes the client connection.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void disconnect() {
 | 
					 | 
				
			||||||
        if (client == null) return;
 | 
					 | 
				
			||||||
        client.close();
 | 
					 | 
				
			||||||
        client = null;
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "client closed"); //NON-NLS
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the network connection.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param host The server's address.
 | 
					 | 
				
			||||||
     * @param port The server's port.
 | 
					 | 
				
			||||||
     * @throws IOException If an I/O error occurs when creating the client.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    void initNetwork(String host, int port) throws IOException {
 | 
					 | 
				
			||||||
        if (client != null)
 | 
					 | 
				
			||||||
            throw new IllegalStateException("trying to join a game again");
 | 
					 | 
				
			||||||
        client = Network.connectToServer(host, port);
 | 
					 | 
				
			||||||
        client.start();
 | 
					 | 
				
			||||||
        client.addMessageListener(this);
 | 
					 | 
				
			||||||
        client.addClientStateListener(this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Called when a message is received from the server.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param client  The client instance that received the message.
 | 
					 | 
				
			||||||
     * @param message The message received from the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void messageReceived(Client client, Message message) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "message received from server: {0}", message); //NON-NLS
 | 
					 | 
				
			||||||
        if (message instanceof ServerMessage serverMessage)
 | 
					 | 
				
			||||||
            app.enqueue(() -> serverMessage.accept(app.getGameLogic()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Called when the client has successfully connected to the server.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param client The client that connected to the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void clientConnected(Client client) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "Client connected: {0}", client); //NON-NLS
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Called when the client is disconnected from the server.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param client         The client that was disconnected.
 | 
					 | 
				
			||||||
     * @param disconnectInfo Information about the disconnection.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void clientDisconnected(Client client, DisconnectInfo disconnectInfo) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "Client {0} disconnected: {1}", client, disconnectInfo); //NON-NLS
 | 
					 | 
				
			||||||
        if (this.client != client)
 | 
					 | 
				
			||||||
            throw new IllegalArgumentException("parameter value must be client");
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "client still connected: {0}", client.isConnected()); //NON-NLS
 | 
					 | 
				
			||||||
        this.client = null;
 | 
					 | 
				
			||||||
        disconnect();
 | 
					 | 
				
			||||||
        app.enqueue(() -> app.setInfoText(lookup("lost.connection.to.server")));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sends the specified message to the server.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param message The message to be sent to the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void send(ClientMessage message) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "sending {0}", message); //NON-NLS
 | 
					 | 
				
			||||||
        if (client == null)
 | 
					 | 
				
			||||||
            app.errorDialog(lookup("lost.connection.to.server"));
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            client.send(message);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,122 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.input.controls.ActionListener;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.system.AppSettings;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipAppState;
 | 
					 | 
				
			||||||
import pp.battleship.model.IntPoint;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.client.BattleshipApp.CLICK;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Represents the state responsible for managing the battle interface within the Battleship game.
 | 
					 | 
				
			||||||
 * This state handles the display and interaction of the battle map, including the opponent's map.
 | 
					 | 
				
			||||||
 * It manages GUI components, input events, and the layout of the interface when this state is enabled.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class BattleAppState extends BattleshipAppState {
 | 
					 | 
				
			||||||
    private static final Logger LOGGER = System.getLogger(BattleAppState.class.getName());
 | 
					 | 
				
			||||||
    private static final float DEPTH = 0f;
 | 
					 | 
				
			||||||
    private static final float GAP = 20f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * A listener for handling click events in the battle interface.
 | 
					 | 
				
			||||||
     * When a click is detected, it triggers the corresponding actions on the opponent's map.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The root node for all GUI components in the battle state.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final Node battleNode = new Node("Battle"); //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * A view representing the opponent's map in the GUI.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private MapView opponentMapView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Enables the battle state by initializing, laying out, and adding GUI components.
 | 
					 | 
				
			||||||
     * Attaches the components to the GUI node and registers input listeners.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void enableState() {
 | 
					 | 
				
			||||||
        battleNode.detachAllChildren();
 | 
					 | 
				
			||||||
        initializeGuiComponents();
 | 
					 | 
				
			||||||
        layoutGuiComponents();
 | 
					 | 
				
			||||||
        addGuiComponents();
 | 
					 | 
				
			||||||
        getApp().getGuiNode().attachChild(battleNode);
 | 
					 | 
				
			||||||
        getApp().getInputManager().addListener(clickListener, CLICK);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Disables the battle state by removing GUI components and unregistering input listeners.
 | 
					 | 
				
			||||||
     * Also handles cleanup of resources, such as the opponent's map view.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void disableState() {
 | 
					 | 
				
			||||||
        getApp().getGuiNode().detachChild(battleNode);
 | 
					 | 
				
			||||||
        getApp().getInputManager().removeListener(clickListener);
 | 
					 | 
				
			||||||
        if (opponentMapView != null) {
 | 
					 | 
				
			||||||
            opponentMapView.unregister();
 | 
					 | 
				
			||||||
            opponentMapView = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the GUI components used in the battle state.
 | 
					 | 
				
			||||||
     * Creates the opponent's map view and adds a grid overlay to it.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void initializeGuiComponents() {
 | 
					 | 
				
			||||||
        opponentMapView = new MapView(getGameLogic().getOpponentMap(), getApp());
 | 
					 | 
				
			||||||
        opponentMapView.addGrid();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds the initialized GUI components to the battle node.
 | 
					 | 
				
			||||||
     * Currently, it attaches the opponent's map view to the node.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void addGuiComponents() {
 | 
					 | 
				
			||||||
        battleNode.attachChild(opponentMapView.getNode());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Lays out the GUI components within the window, positioning them appropriately.
 | 
					 | 
				
			||||||
     * The opponent's map view is positioned based on the window's dimensions and a specified gap.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void layoutGuiComponents() {
 | 
					 | 
				
			||||||
        final AppSettings s = getApp().getContext().getSettings();
 | 
					 | 
				
			||||||
        final float mapWidth = opponentMapView.getWidth();
 | 
					 | 
				
			||||||
        final float mapHeight = opponentMapView.getHeight();
 | 
					 | 
				
			||||||
        final float windowWidth = s.getWidth();
 | 
					 | 
				
			||||||
        final float windowHeight = s.getHeight();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        opponentMapView.getNode().setLocalTranslation(windowWidth - mapWidth - GAP,
 | 
					 | 
				
			||||||
                                                      windowHeight - mapHeight - GAP,
 | 
					 | 
				
			||||||
                                                      DEPTH);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles click events in the battle interface. If the event indicates a click (not a release),
 | 
					 | 
				
			||||||
     * it translates the cursor position to the model's coordinate system and triggers the game logic
 | 
					 | 
				
			||||||
     * for interacting with the opponent's map.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param isPressed whether the mouse button is currently pressed (true) or released (false)
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void click(boolean isPressed) {
 | 
					 | 
				
			||||||
        if (!isPressed || showsDialog())
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        final IntPoint cursorPos = opponentMapView.mouseToModel(getApp().getInputManager().getCursorPosition());
 | 
					 | 
				
			||||||
        LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS
 | 
					 | 
				
			||||||
        getGameLogic().clickOpponentMap(cursorPos);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,173 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.input.controls.ActionListener;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector2f;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.system.AppSettings;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Button;
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Container;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipAppState;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.battleship.Resources.lookup;
 | 
					 | 
				
			||||||
import static pp.battleship.client.BattleshipApp.CLICK;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * EditorState manages the editor mode in the Battleship game,
 | 
					 | 
				
			||||||
 * allowing players to place and rotate ships.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class EditorAppState extends BattleshipAppState {
 | 
					 | 
				
			||||||
    private static final Logger LOGGER = System.getLogger(EditorAppState.class.getName());
 | 
					 | 
				
			||||||
    private static final float DEPTH = 0f;
 | 
					 | 
				
			||||||
    private static final float GAP = 20f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final ActionListener clickListener = (name, isPressed, tpf) -> click(isPressed);
 | 
					 | 
				
			||||||
    private final Node editorNode = new Node("Editor"); //NON-NLS
 | 
					 | 
				
			||||||
    private Container buttonContainer;
 | 
					 | 
				
			||||||
    private Button rotateButton;
 | 
					 | 
				
			||||||
    private Button readyButton;
 | 
					 | 
				
			||||||
    private MapView ownMapView;
 | 
					 | 
				
			||||||
    private MapView harborView;
 | 
					 | 
				
			||||||
    private Vector2f oldCursorPosition;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Enables the editor state by attaching necessary nodes and listeners.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void enableState() {
 | 
					 | 
				
			||||||
        editorNode.detachAllChildren();
 | 
					 | 
				
			||||||
        initializeGuiComponents();
 | 
					 | 
				
			||||||
        addGuiComponents();
 | 
					 | 
				
			||||||
        layoutGuiComponents();
 | 
					 | 
				
			||||||
        getApp().getGuiNode().attachChild(editorNode);
 | 
					 | 
				
			||||||
        getApp().getInputManager().addListener(clickListener, CLICK);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Disables the editor state by detaching nodes and removing listeners.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void disableState() {
 | 
					 | 
				
			||||||
        getApp().getGuiNode().detachChild(editorNode);
 | 
					 | 
				
			||||||
        getApp().getInputManager().removeListener(clickListener);
 | 
					 | 
				
			||||||
        if (ownMapView != null) {
 | 
					 | 
				
			||||||
            ownMapView.unregister();
 | 
					 | 
				
			||||||
            ownMapView = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (harborView != null) {
 | 
					 | 
				
			||||||
            harborView.unregister();
 | 
					 | 
				
			||||||
            harborView = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the editor state, handling cursor movement and enabling buttons.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf Time per frame
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void update(float tpf) {
 | 
					 | 
				
			||||||
        super.update(tpf);
 | 
					 | 
				
			||||||
        cursorMovement();
 | 
					 | 
				
			||||||
        enableButtons();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Enables or disables buttons based on the logic state.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void enableButtons() {
 | 
					 | 
				
			||||||
        readyButton.setEnabled(getGameLogic().isMapComplete());
 | 
					 | 
				
			||||||
        rotateButton.setEnabled(getGameLogic().movingShip());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles cursor movement for previewing ship placement.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void cursorMovement() {
 | 
					 | 
				
			||||||
        if (!getGameLogic().movingShip() || ownMapView == null || showsDialog())
 | 
					 | 
				
			||||||
            return;
 | 
					 | 
				
			||||||
        final Vector2f cursorPosition = getApp().getInputManager().getCursorPosition();
 | 
					 | 
				
			||||||
        if (!cursorPosition.equals(oldCursorPosition)) {
 | 
					 | 
				
			||||||
            oldCursorPosition = new Vector2f(cursorPosition);
 | 
					 | 
				
			||||||
            getGameLogic().movePreview(ownMapView.mouseToModel(cursorPosition));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the GUI components for the editor.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void initializeGuiComponents() {
 | 
					 | 
				
			||||||
        ownMapView = new MapView(getGameLogic().getOwnMap(), getApp());
 | 
					 | 
				
			||||||
        harborView = new MapView(getGameLogic().getHarbor(), getApp());
 | 
					 | 
				
			||||||
        ownMapView.addGrid();
 | 
					 | 
				
			||||||
        rotateButton = new Button(lookup("button.rotate"));
 | 
					 | 
				
			||||||
        readyButton = new Button(lookup("button.ready"));
 | 
					 | 
				
			||||||
        rotateButton.addClickCommands(e -> {
 | 
					 | 
				
			||||||
            if (!showsDialog())
 | 
					 | 
				
			||||||
                getGameLogic().rotateShip();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        readyButton.addClickCommands(e -> {
 | 
					 | 
				
			||||||
            if (!showsDialog())
 | 
					 | 
				
			||||||
                getGameLogic().mapFinished();
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        buttonContainer = new Container();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds the GUI components to the editor node.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void addGuiComponents() {
 | 
					 | 
				
			||||||
        buttonContainer.addChild(rotateButton, 0, 0);
 | 
					 | 
				
			||||||
        buttonContainer.addChild(readyButton, 0, 1);
 | 
					 | 
				
			||||||
        editorNode.attachChild(ownMapView.getNode());
 | 
					 | 
				
			||||||
        editorNode.attachChild(harborView.getNode());
 | 
					 | 
				
			||||||
        editorNode.attachChild(buttonContainer);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Lays out the GUI components on the screen.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void layoutGuiComponents() {
 | 
					 | 
				
			||||||
        final AppSettings s = getApp().getContext().getSettings();
 | 
					 | 
				
			||||||
        final float harborWidth = harborView.getWidth();
 | 
					 | 
				
			||||||
        final float harborHeight = harborView.getHeight();
 | 
					 | 
				
			||||||
        final float ownMapWidth = ownMapView.getWidth();
 | 
					 | 
				
			||||||
        final float ownMapHeight = ownMapView.getHeight();
 | 
					 | 
				
			||||||
        final float windowWidth = s.getWidth();
 | 
					 | 
				
			||||||
        final float windowHeight = s.getHeight();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        ownMapView.getNode()
 | 
					 | 
				
			||||||
                  .setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP),
 | 
					 | 
				
			||||||
                                       0.5f * (windowHeight - ownMapHeight),
 | 
					 | 
				
			||||||
                                       DEPTH);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        harborView.getNode()
 | 
					 | 
				
			||||||
                  .setLocalTranslation(0.5f * (windowWidth - harborWidth + ownMapWidth + GAP),
 | 
					 | 
				
			||||||
                                       0.5f * (windowHeight - harborHeight),
 | 
					 | 
				
			||||||
                                       DEPTH);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        buttonContainer.setLocalTranslation(0.5f * (windowWidth - harborWidth - ownMapWidth - GAP),
 | 
					 | 
				
			||||||
                                            0.5f * (windowHeight - ownMapHeight - GAP),
 | 
					 | 
				
			||||||
                                            DEPTH);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles click events to place or rotate ships on the maps.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void click(boolean isPressed) {
 | 
					 | 
				
			||||||
        if (!isPressed || showsDialog()) return;
 | 
					 | 
				
			||||||
        final Vector2f cursorPos = getApp().getInputManager().getCursorPosition();
 | 
					 | 
				
			||||||
        LOGGER.log(Level.DEBUG, "click: {0}", cursorPos); //NON-NLS
 | 
					 | 
				
			||||||
        getGameLogic().clickHarbor(harborView.mouseToModel(cursorPos));
 | 
					 | 
				
			||||||
        getGameLogic().clickOwnMap(ownMapView.mouseToModel(cursorPos));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,122 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.util.PreferencesUtils.getPreferences;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.lang.System.Logger;
 | 
					 | 
				
			||||||
import java.lang.System.Logger.Level;
 | 
					 | 
				
			||||||
import java.util.prefs.Preferences;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Handles the background music beeing played. Is able to start and stop the music. Set the Volume of the Audio.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class GameMusic extends AbstractAppState{
 | 
					 | 
				
			||||||
    private static final Logger LOGGER = System.getLogger(GameMusic.class.getName());
 | 
					 | 
				
			||||||
    private static final Preferences PREFERENCES = getPreferences(GameMusic.class);
 | 
					 | 
				
			||||||
    private static final String ENABLED_PREF = "enabled"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String VOLUME_PREF = "volume"; //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private AudioNode music;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
     * Checks if sound is enabled in the preferences.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return float to which the volume is set
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static float volumeInPreferences() {
 | 
					 | 
				
			||||||
        return PREFERENCES.getFloat(VOLUME_PREF, 0.5f);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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);
 | 
					 | 
				
			||||||
        music = loadSound(app, "Sound/background.ogg");
 | 
					 | 
				
			||||||
        setVolume(volumeInPreferences());
 | 
					 | 
				
			||||||
        music.setLooping(true);
 | 
					 | 
				
			||||||
        if (isEnabled() && music != null) {
 | 
					 | 
				
			||||||
            music.play();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (music != null) {
 | 
					 | 
				
			||||||
            if (enabled) {
 | 
					 | 
				
			||||||
                music.play();
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                music.stop();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        super.setEnabled(enabled);
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "Sound enabled: {0}", enabled); //NON-NLS
 | 
					 | 
				
			||||||
        PREFERENCES.putBoolean(ENABLED_PREF, enabled);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Toggles the game sound on or off.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void toggleSound() {
 | 
					 | 
				
			||||||
        setEnabled(!isEnabled());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets the volume of music
 | 
					 | 
				
			||||||
     * @param vol the volume to which the music should be set
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void setVolume(float vol){
 | 
					 | 
				
			||||||
        music.setVolume(vol);
 | 
					 | 
				
			||||||
        PREFERENCES.putFloat(VOLUME_PREF, vol);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,191 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.material.Material;
 | 
					 | 
				
			||||||
import com.jme3.material.RenderState.BlendMode;
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector2f;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector3f;
 | 
					 | 
				
			||||||
import com.jme3.scene.Geometry;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.scene.Spatial.CullHint;
 | 
					 | 
				
			||||||
import com.jme3.scene.shape.Quad;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipApp;
 | 
					 | 
				
			||||||
import pp.battleship.model.IntPoint;
 | 
					 | 
				
			||||||
import pp.battleship.model.ShipMap;
 | 
					 | 
				
			||||||
import pp.util.FloatPoint;
 | 
					 | 
				
			||||||
import pp.util.Position;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Represents the visual view of a {@link ShipMap}, used to display the map structure such as the player's map, harbor,
 | 
					 | 
				
			||||||
 * and opponent's map. This class handles the graphical representation of the map, including background setup, grid lines,
 | 
					 | 
				
			||||||
 * and interaction between the model and the view.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class MapView {
 | 
					 | 
				
			||||||
    public static final float FIELD_SIZE = 40f;
 | 
					 | 
				
			||||||
    private static final float GRID_LINE_WIDTH = 2f;
 | 
					 | 
				
			||||||
    private static final float BACKGROUND_DEPTH = -4f;
 | 
					 | 
				
			||||||
    private static final float GRID_DEPTH = -1f;
 | 
					 | 
				
			||||||
    private static final ColorRGBA BACKGROUND_COLOR = new ColorRGBA(0, 0.05f, 0.05f, 0.5f);
 | 
					 | 
				
			||||||
    private static final ColorRGBA GRID_COLOR = ColorRGBA.Green;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Reference to the main application and the ship map being visualized
 | 
					 | 
				
			||||||
    private final BattleshipApp app;
 | 
					 | 
				
			||||||
    private final Node mapNode = new Node("map"); // NON-NLS
 | 
					 | 
				
			||||||
    private final ShipMap map;
 | 
					 | 
				
			||||||
    private final MapViewSynchronizer synchronizer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new MapView for a given {@link ShipMap} and {@link BattleshipApp}.
 | 
					 | 
				
			||||||
     * Initializes the view by setting up the background and registering a synchronizer to listen to changes in the map.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param map the ship map to visualize
 | 
					 | 
				
			||||||
     * @param app the main application instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    MapView(ShipMap map, BattleshipApp app) {
 | 
					 | 
				
			||||||
        this.map = map;
 | 
					 | 
				
			||||||
        this.app = app;
 | 
					 | 
				
			||||||
        this.synchronizer = new MapViewSynchronizer(this);
 | 
					 | 
				
			||||||
        setupBackground();
 | 
					 | 
				
			||||||
        app.getGameLogic().addListener(synchronizer);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Unregisters the {@link MapViewSynchronizer} from the listener list of the ClientGameLogic,
 | 
					 | 
				
			||||||
     * stopping the view from receiving updates when the underlying {@link ShipMap} changes.
 | 
					 | 
				
			||||||
     * After calling this method, this MapView instance should no longer be used.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    void unregister() {
 | 
					 | 
				
			||||||
        app.getGameLogic().removeListener(synchronizer);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Gets the {@link ShipMap} associated with this view.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the ship map
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ShipMap getMap() {
 | 
					 | 
				
			||||||
        return map;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Gets the {@link BattleshipApp} instance associated with this view.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the main application instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public BattleshipApp getApp() {
 | 
					 | 
				
			||||||
        return app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets up the background of the map view using a quad geometry.
 | 
					 | 
				
			||||||
     * The background is configured with a semi-transparent color and placed at a specific depth.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupBackground() {
 | 
					 | 
				
			||||||
        final Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); // NON-NLS
 | 
					 | 
				
			||||||
        mat.setColor("Color", BACKGROUND_COLOR); // NON-NLS
 | 
					 | 
				
			||||||
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
					 | 
				
			||||||
        final Position corner = modelToView(map.getWidth(), map.getHeight());
 | 
					 | 
				
			||||||
        final Geometry background = new Geometry("MapBackground", new Quad(corner.getX(), corner.getY()));
 | 
					 | 
				
			||||||
        background.setMaterial(mat);
 | 
					 | 
				
			||||||
        background.setLocalTranslation(0f, 0f, BACKGROUND_DEPTH);
 | 
					 | 
				
			||||||
        background.setCullHint(CullHint.Never);
 | 
					 | 
				
			||||||
        mapNode.attachChild(background);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds grid lines to the map view to visually separate the fields within the map.
 | 
					 | 
				
			||||||
     * The grid lines are drawn based on the dimensions of the ship map.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void addGrid() {
 | 
					 | 
				
			||||||
        for (int x = 0; x <= map.getWidth(); x++) {
 | 
					 | 
				
			||||||
            final Position f = modelToView(x, 0);
 | 
					 | 
				
			||||||
            final Position t = modelToView(x, map.getHeight());
 | 
					 | 
				
			||||||
            mapNode.attachChild(gridLine(f, t));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        for (int y = 0; y <= map.getHeight(); y++) {
 | 
					 | 
				
			||||||
            final Position f = modelToView(0, y);
 | 
					 | 
				
			||||||
            final Position t = modelToView(map.getWidth(), y);
 | 
					 | 
				
			||||||
            mapNode.attachChild(gridLine(f, t));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Gets the root node containing all visual elements in this map view.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the root node for the map view
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public Node getNode() {
 | 
					 | 
				
			||||||
        return mapNode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Gets the total width of the map in view coordinates.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the width of the map in view coordinates
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public float getWidth() {
 | 
					 | 
				
			||||||
        return FIELD_SIZE * map.getWidth();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Gets the total height of the map in view coordinates.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @return the height of the map in view coordinates
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public float getHeight() {
 | 
					 | 
				
			||||||
        return FIELD_SIZE * map.getHeight();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Converts coordinates from view coordinates to model coordinates.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param x the x-coordinate in view space
 | 
					 | 
				
			||||||
     * @param y the y-coordinate in view space
 | 
					 | 
				
			||||||
     * @return the corresponding model coordinates as an {@link IntPoint}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public IntPoint viewToModel(float x, float y) {
 | 
					 | 
				
			||||||
        return new IntPoint((int) Math.floor(x / FIELD_SIZE), (int) Math.floor(y / FIELD_SIZE));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Converts coordinates from model coordinates to view coordinates.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param x the x-coordinate in model space
 | 
					 | 
				
			||||||
     * @param y the y-coordinate in model space
 | 
					 | 
				
			||||||
     * @return the corresponding view coordinates as a {@link Position}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public Position modelToView(float x, float y) {
 | 
					 | 
				
			||||||
        return new FloatPoint(x * FIELD_SIZE, y * FIELD_SIZE);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Converts the mouse position to model coordinates.
 | 
					 | 
				
			||||||
     * This method takes into account the map's transformation in the 3D scene.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param pos the 2D vector representing the mouse position in the view
 | 
					 | 
				
			||||||
     * @return the corresponding model coordinates as an {@link IntPoint}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public IntPoint mouseToModel(Vector2f pos) {
 | 
					 | 
				
			||||||
        final Vector3f world = new Vector3f(pos.getX(), pos.getY(), 0f);
 | 
					 | 
				
			||||||
        final Vector3f view = mapNode.getWorldTransform().transformInverseVector(world, null);
 | 
					 | 
				
			||||||
        return viewToModel(view.getX(), view.getY());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a visual representation of a grid line between two positions.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param p1 the start position of the grid line
 | 
					 | 
				
			||||||
     * @param p2 the end position of the grid line
 | 
					 | 
				
			||||||
     * @return a {@link Geometry} representing the grid line
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Geometry gridLine(Position p1, Position p2) {
 | 
					 | 
				
			||||||
        return app.getDraw().makeFatLine(p1, p2, GRID_DEPTH, GRID_COLOR, GRID_LINE_WIDTH);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,154 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.material.Material;
 | 
					 | 
				
			||||||
import com.jme3.material.RenderState;
 | 
					 | 
				
			||||||
import com.jme3.material.RenderState.BlendMode;
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import com.jme3.scene.Geometry;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.scene.Spatial;
 | 
					 | 
				
			||||||
import com.jme3.scene.shape.Sphere;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import pp.battleship.model.Battleship;
 | 
					 | 
				
			||||||
import pp.battleship.model.Shell;
 | 
					 | 
				
			||||||
import pp.battleship.model.Shot;
 | 
					 | 
				
			||||||
import pp.util.Position;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.jme3.material.Materials.UNSHADED;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Synchronizes the visual representation of the ship map with the game model.
 | 
					 | 
				
			||||||
 * It handles the rendering of ships and shots on the map view, updating the view
 | 
					 | 
				
			||||||
 * whenever changes occur in the model.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class MapViewSynchronizer extends ShipMapSynchronizer {
 | 
					 | 
				
			||||||
    // Constants for rendering properties
 | 
					 | 
				
			||||||
    private static final float SHIP_LINE_WIDTH = 6f;
 | 
					 | 
				
			||||||
    private static final float SHOT_DEPTH = -2f;
 | 
					 | 
				
			||||||
    private static final float SHIP_DEPTH = 0f;
 | 
					 | 
				
			||||||
    private static final float INDENT = 4f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Colors used for different visual elements
 | 
					 | 
				
			||||||
    private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
 | 
					 | 
				
			||||||
    private static final ColorRGBA MISS_COLOR = ColorRGBA.Blue;
 | 
					 | 
				
			||||||
    private static final ColorRGBA SHIP_BORDER_COLOR = ColorRGBA.White;
 | 
					 | 
				
			||||||
    private static final ColorRGBA PREVIEW_COLOR = ColorRGBA.Gray;
 | 
					 | 
				
			||||||
    private static final ColorRGBA ERROR_COLOR = ColorRGBA.Red;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // The MapView associated with this synchronizer
 | 
					 | 
				
			||||||
    private final MapView view;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new MapViewSynchronizer for the given MapView.
 | 
					 | 
				
			||||||
     * Initializes the synchronizer and adds existing elements from the model to the view.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param view the MapView to synchronize with the game model
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public MapViewSynchronizer(MapView view) {
 | 
					 | 
				
			||||||
        super(view.getMap(), view.getNode());
 | 
					 | 
				
			||||||
        this.view = view;
 | 
					 | 
				
			||||||
        addExisting();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a visual representation of a shot on the map.
 | 
					 | 
				
			||||||
     * A hit shot is represented in red, while a miss is represented in blue.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param shot the Shot object representing the shot in the model
 | 
					 | 
				
			||||||
     * @return a Spatial representing the shot on the map
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Spatial visit(Shot shot) {
 | 
					 | 
				
			||||||
        // Convert the shot's model coordinates to view coordinates
 | 
					 | 
				
			||||||
        final Position p1 = view.modelToView(shot.getX(), shot.getY());
 | 
					 | 
				
			||||||
        final Position p2 = view.modelToView(shot.getX() + 1, shot.getY() + 1);
 | 
					 | 
				
			||||||
        final ColorRGBA color = shot.isHit() ? HIT_COLOR : MISS_COLOR;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Create and return a rectangle representing the shot
 | 
					 | 
				
			||||||
        return view.getApp().getDraw().makeRectangle(p1.getX(), p1.getY(),
 | 
					 | 
				
			||||||
                                                     SHOT_DEPTH,
 | 
					 | 
				
			||||||
                                                     p2.getX() - p1.getX(), p2.getY() - p1.getY(),
 | 
					 | 
				
			||||||
                                                     color);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a visual representation of a battleship on the map.
 | 
					 | 
				
			||||||
     * The ship's border color depends on its status: normal, valid preview, or invalid preview.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the Battleship object representing the ship in the model
 | 
					 | 
				
			||||||
     * @return a Spatial representing the ship on the map
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Spatial visit(Battleship ship) {
 | 
					 | 
				
			||||||
        // Create a node to represent the ship
 | 
					 | 
				
			||||||
        final Node shipNode = new Node("ship"); //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Convert the ship's model coordinates to view coordinates
 | 
					 | 
				
			||||||
        final Position p1 = view.modelToView(ship.getMinX(), ship.getMinY());
 | 
					 | 
				
			||||||
        final Position p2 = view.modelToView(ship.getMaxX() + 1, ship.getMaxY() + 1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Calculate the coordinates for the ship's bounding box
 | 
					 | 
				
			||||||
        final float x1 = p1.getX() + INDENT;
 | 
					 | 
				
			||||||
        final float y1 = p1.getY() + INDENT;
 | 
					 | 
				
			||||||
        final float x2 = p2.getX() - INDENT;
 | 
					 | 
				
			||||||
        final float y2 = p2.getY() - INDENT;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Determine the color based on the ship's status
 | 
					 | 
				
			||||||
        final ColorRGBA color = switch (ship.getStatus()) {
 | 
					 | 
				
			||||||
            case NORMAL -> SHIP_BORDER_COLOR;
 | 
					 | 
				
			||||||
            case VALID_PREVIEW -> PREVIEW_COLOR;
 | 
					 | 
				
			||||||
            case INVALID_PREVIEW -> ERROR_COLOR;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Add the ship's borders to the node
 | 
					 | 
				
			||||||
        shipNode.attachChild(shipLine(x1, y1, x2, y1, color));
 | 
					 | 
				
			||||||
        shipNode.attachChild(shipLine(x1, y2, x2, y2, color));
 | 
					 | 
				
			||||||
        shipNode.attachChild(shipLine(x1, y1, x1, y2, color));
 | 
					 | 
				
			||||||
        shipNode.attachChild(shipLine(x2, y1, x2, y2, color));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Return the complete ship representation
 | 
					 | 
				
			||||||
        return shipNode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a line geometry representing part of the ship's border.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param x1    the starting x-coordinate of the line
 | 
					 | 
				
			||||||
     * @param y1    the starting y-coordinate of the line
 | 
					 | 
				
			||||||
     * @param x2    the ending x-coordinate of the line
 | 
					 | 
				
			||||||
     * @param y2    the ending y-coordinate of the line
 | 
					 | 
				
			||||||
     * @param color the color of the line
 | 
					 | 
				
			||||||
     * @return a Geometry representing the line
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) {
 | 
					 | 
				
			||||||
        return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        /**
 | 
					 | 
				
			||||||
     * Creates and returns a Spatial representation of the given {@code Shell} object
 | 
					 | 
				
			||||||
     * for 2D visualization in the game. The shell is represented as a circle.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param shell The {@code Shell} object to be visualized.
 | 
					 | 
				
			||||||
     * @return A {@code Spatial} object representing the shell on the map.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Spatial visit(Shell shell) {
 | 
					 | 
				
			||||||
        final ColorRGBA color = ColorRGBA.Black;
 | 
					 | 
				
			||||||
        Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.FIELD_SIZE / 2 * 0.8f));
 | 
					 | 
				
			||||||
        Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS
 | 
					 | 
				
			||||||
        mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
					 | 
				
			||||||
        mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
 | 
					 | 
				
			||||||
        mat.setColor("Color", color);
 | 
					 | 
				
			||||||
        ellipse.setMaterial(mat);
 | 
					 | 
				
			||||||
        ellipse.addControl(new Shell2DControl(view, shell));
 | 
					 | 
				
			||||||
        return ellipse;
 | 
					 | 
				
			||||||
    }      
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,285 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.effect.ParticleEmitter;
 | 
					 | 
				
			||||||
import com.jme3.effect.ParticleMesh.Type;
 | 
					 | 
				
			||||||
import com.jme3.effect.shapes.EmitterSphereShape;
 | 
					 | 
				
			||||||
import com.jme3.material.Material;
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import com.jme3.math.FastMath;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector3f;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipApp;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Factory class responsible for creating particle effects used in the game.
 | 
					 | 
				
			||||||
 * This centralizes the creation of various types of particle emitters.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class ParticleEffectFactory {
 | 
					 | 
				
			||||||
    private static final int COUNT_FACTOR = 1;
 | 
					 | 
				
			||||||
    private static final float COUNT_FACTOR_F = 1f;
 | 
					 | 
				
			||||||
    private static final boolean POINT_SPRITE = true;
 | 
					 | 
				
			||||||
    private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    private final BattleshipApp app;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ParticleEffectFactory(BattleshipApp app) {
 | 
					 | 
				
			||||||
        this.app = app;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a flame particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured flame particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createFlame() {
 | 
					 | 
				
			||||||
        ParticleEmitter flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        flame.setSelectRandomImage(true);
 | 
					 | 
				
			||||||
        flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F)));
 | 
					 | 
				
			||||||
        flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f));
 | 
					 | 
				
			||||||
        flame.setStartSize(0.1f);
 | 
					 | 
				
			||||||
        flame.setEndSize(0.5f);
 | 
					 | 
				
			||||||
        flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
 | 
					 | 
				
			||||||
        flame.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        flame.setGravity(0, -5, 0);
 | 
					 | 
				
			||||||
        flame.setLowLife(.4f);
 | 
					 | 
				
			||||||
        flame.setHighLife(.5f);
 | 
					 | 
				
			||||||
        flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0));
 | 
					 | 
				
			||||||
        flame.getParticleInfluencer().setVelocityVariation(1f);
 | 
					 | 
				
			||||||
        flame.setImagesX(2);
 | 
					 | 
				
			||||||
        flame.setImagesY(2);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flame.png"));
 | 
					 | 
				
			||||||
        mat.setBoolean("PointSprite", POINT_SPRITE);
 | 
					 | 
				
			||||||
        flame.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return flame;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a flash particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured flash particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createFlash() {
 | 
					 | 
				
			||||||
        ParticleEmitter flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        flash.setSelectRandomImage(true);
 | 
					 | 
				
			||||||
        flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
 | 
					 | 
				
			||||||
        flash.setStartSize(.1f);
 | 
					 | 
				
			||||||
        flash.setEndSize(0.5f);
 | 
					 | 
				
			||||||
        flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f));
 | 
					 | 
				
			||||||
        flash.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        flash.setGravity(0, 0, 0);
 | 
					 | 
				
			||||||
        flash.setLowLife(.2f);
 | 
					 | 
				
			||||||
        flash.setHighLife(.2f);
 | 
					 | 
				
			||||||
        flash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 5f, 0));
 | 
					 | 
				
			||||||
        flash.getParticleInfluencer().setVelocityVariation(1);
 | 
					 | 
				
			||||||
        flash.setImagesX(2);
 | 
					 | 
				
			||||||
        flash.setImagesY(2);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/flash.png"));
 | 
					 | 
				
			||||||
        mat.setBoolean("PointSprite", POINT_SPRITE);
 | 
					 | 
				
			||||||
        flash.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return flash;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a round spark particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured round spark particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createRoundSpark() {
 | 
					 | 
				
			||||||
        ParticleEmitter roundSpark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        roundSpark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F)));
 | 
					 | 
				
			||||||
        roundSpark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        roundSpark.setStartSize(0.2f);
 | 
					 | 
				
			||||||
        roundSpark.setEndSize(0.8f);
 | 
					 | 
				
			||||||
        roundSpark.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f));
 | 
					 | 
				
			||||||
        roundSpark.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        roundSpark.setGravity(0, -.5f, 0);
 | 
					 | 
				
			||||||
        roundSpark.setLowLife(1.8f);
 | 
					 | 
				
			||||||
        roundSpark.setHighLife(2f);
 | 
					 | 
				
			||||||
        roundSpark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0));
 | 
					 | 
				
			||||||
        roundSpark.getParticleInfluencer().setVelocityVariation(.5f);
 | 
					 | 
				
			||||||
        roundSpark.setImagesX(1);
 | 
					 | 
				
			||||||
        roundSpark.setImagesY(1);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/roundspark.png"));
 | 
					 | 
				
			||||||
        mat.setBoolean("PointSprite", POINT_SPRITE);
 | 
					 | 
				
			||||||
        roundSpark.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return roundSpark;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a spark particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured spark particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createSpark() {
 | 
					 | 
				
			||||||
        ParticleEmitter spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
 | 
					 | 
				
			||||||
        spark.setStartSize(.5f);
 | 
					 | 
				
			||||||
        spark.setEndSize(.5f);
 | 
					 | 
				
			||||||
        spark.setFacingVelocity(true);
 | 
					 | 
				
			||||||
        spark.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        spark.setGravity(0, 5, 0);
 | 
					 | 
				
			||||||
        spark.setLowLife(1.1f);
 | 
					 | 
				
			||||||
        spark.setHighLife(1.5f);
 | 
					 | 
				
			||||||
        spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0));
 | 
					 | 
				
			||||||
        spark.getParticleInfluencer().setVelocityVariation(1);
 | 
					 | 
				
			||||||
        spark.setImagesX(1);
 | 
					 | 
				
			||||||
        spark.setImagesY(1);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/spark.png"));
 | 
					 | 
				
			||||||
        spark.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return spark;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a smoke trail particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured smoke trail particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createSmokeTrail() {
 | 
					 | 
				
			||||||
        ParticleEmitter smokeTrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        smokeTrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        smokeTrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f));
 | 
					 | 
				
			||||||
        smokeTrail.setStartSize(.2f);
 | 
					 | 
				
			||||||
        smokeTrail.setEndSize(1f);
 | 
					 | 
				
			||||||
        smokeTrail.setFacingVelocity(true);
 | 
					 | 
				
			||||||
        smokeTrail.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        smokeTrail.setGravity(0, 1, 0);
 | 
					 | 
				
			||||||
        smokeTrail.setLowLife(.4f);
 | 
					 | 
				
			||||||
        smokeTrail.setHighLife(.5f);
 | 
					 | 
				
			||||||
        smokeTrail.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 12, 0));
 | 
					 | 
				
			||||||
        smokeTrail.getParticleInfluencer().setVelocityVariation(1);
 | 
					 | 
				
			||||||
        smokeTrail.setImagesX(1);
 | 
					 | 
				
			||||||
        smokeTrail.setImagesY(3);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/smoketrail.png"));
 | 
					 | 
				
			||||||
        smokeTrail.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return smokeTrail;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a debris particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured debris particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createDebris() {
 | 
					 | 
				
			||||||
        ParticleEmitter debris = new ParticleEmitter("Debris", Type.Triangle, 15 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        debris.setSelectRandomImage(true);
 | 
					 | 
				
			||||||
        debris.setRandomAngle(true);
 | 
					 | 
				
			||||||
        debris.setRotateSpeed(FastMath.TWO_PI * 4);
 | 
					 | 
				
			||||||
        debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f));
 | 
					 | 
				
			||||||
        debris.setStartSize(.10f);
 | 
					 | 
				
			||||||
        debris.setEndSize(.15f);
 | 
					 | 
				
			||||||
        debris.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        debris.setGravity(0, 12f, 0);
 | 
					 | 
				
			||||||
        debris.setLowLife(1.4f);
 | 
					 | 
				
			||||||
        debris.setHighLife(1.5f);
 | 
					 | 
				
			||||||
        debris.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 15, 0));
 | 
					 | 
				
			||||||
        debris.getParticleInfluencer().setVelocityVariation(.60f);
 | 
					 | 
				
			||||||
        debris.setImagesX(3);
 | 
					 | 
				
			||||||
        debris.setImagesY(3);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/Debris.png"));
 | 
					 | 
				
			||||||
        debris.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return debris;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a shockwave particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured shockwave particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createShockwave() {
 | 
					 | 
				
			||||||
        ParticleEmitter shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR);
 | 
					 | 
				
			||||||
        shockwave.setFaceNormal(Vector3f.UNIT_Y);
 | 
					 | 
				
			||||||
        shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F));
 | 
					 | 
				
			||||||
        shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f));
 | 
					 | 
				
			||||||
        shockwave.setStartSize(0f);
 | 
					 | 
				
			||||||
        shockwave.setEndSize(3f);
 | 
					 | 
				
			||||||
        shockwave.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        shockwave.setGravity(0, 0, 0);
 | 
					 | 
				
			||||||
        shockwave.setLowLife(0.5f);
 | 
					 | 
				
			||||||
        shockwave.setHighLife(0.5f);
 | 
					 | 
				
			||||||
        shockwave.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 0, 0));
 | 
					 | 
				
			||||||
        shockwave.getParticleInfluencer().setVelocityVariation(0f);
 | 
					 | 
				
			||||||
        shockwave.setImagesX(1);
 | 
					 | 
				
			||||||
        shockwave.setImagesY(1);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Explosion/shockwave.png"));
 | 
					 | 
				
			||||||
        shockwave.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return shockwave;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a moving smoke emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured smoke emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    ParticleEmitter createMovingSmokeEmitter() {
 | 
					 | 
				
			||||||
        ParticleEmitter smokeEmitter = new ParticleEmitter("SmokeEmitter", Type.Triangle, 300);
 | 
					 | 
				
			||||||
        smokeEmitter.setGravity(0, 0, 0);
 | 
					 | 
				
			||||||
        smokeEmitter.getParticleInfluencer().setVelocityVariation(1);
 | 
					 | 
				
			||||||
        smokeEmitter.setLowLife(1);
 | 
					 | 
				
			||||||
        smokeEmitter.setHighLife(1);
 | 
					 | 
				
			||||||
        smokeEmitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, .5f, 0));
 | 
					 | 
				
			||||||
        smokeEmitter.setImagesX(15); // Assuming the smoke texture is a sprite sheet with 15 frames
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Smoke/Smoke.png"));
 | 
					 | 
				
			||||||
        smokeEmitter.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return smokeEmitter;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a one-time water splash particle emitter.
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @return a configured one-time water splash particle emitter
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ParticleEmitter createWaterSplash() {
 | 
					 | 
				
			||||||
        // Create a new particle emitter for the splash effect
 | 
					 | 
				
			||||||
        ParticleEmitter waterSplash = new ParticleEmitter("WaterSplash", Type.Triangle, 30);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Set the shape of the emitter, making particles emit from a point or small area
 | 
					 | 
				
			||||||
        waterSplash.setShape(new EmitterSphereShape(Vector3f.ZERO, 0.2f));
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Start and end colors for water (blue, fading out)
 | 
					 | 
				
			||||||
        waterSplash.setStartColor(new ColorRGBA(0.4f, 0.4f, 1f, 1f));  // Light blue at start
 | 
					 | 
				
			||||||
        waterSplash.setEndColor(new ColorRGBA(0.4f, 0.4f, 1f, 0f));    // Transparent at the end
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Particle size: small at start, larger before fading out
 | 
					 | 
				
			||||||
        waterSplash.setStartSize(0.1f);
 | 
					 | 
				
			||||||
        waterSplash.setEndSize(0.3f);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Particle lifespan (how long particles live)
 | 
					 | 
				
			||||||
        waterSplash.setLowLife(0.5f);
 | 
					 | 
				
			||||||
        waterSplash.setHighLife(1f);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Gravity: Pull the water particles downwards
 | 
					 | 
				
			||||||
        waterSplash.setGravity(0, -9.81f, 0);  // Earth's gravity simulation
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Velocity: Give particles an initial burst upward (simulates splash)
 | 
					 | 
				
			||||||
        waterSplash.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 3, 0));
 | 
					 | 
				
			||||||
        waterSplash.getParticleInfluencer().setVelocityVariation(0.6f);  // Add randomness to splash
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Set how many particles are emitted per second (0 to emit all particles at once)
 | 
					 | 
				
			||||||
        waterSplash.setParticlesPerSec(0);
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        // Load a texture for the water splash (assuming a texture exists at this path)
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md");
 | 
					 | 
				
			||||||
        mat.setTexture("Texture", app.getAssetManager().loadTexture("Effects/Splash/splash.png"));
 | 
					 | 
				
			||||||
        waterSplash.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return waterSplash;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,203 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.app.Application;
 | 
					 | 
				
			||||||
import com.jme3.app.state.AppStateManager;
 | 
					 | 
				
			||||||
import com.jme3.asset.AssetManager;
 | 
					 | 
				
			||||||
import com.jme3.light.AmbientLight;
 | 
					 | 
				
			||||||
import com.jme3.light.DirectionalLight;
 | 
					 | 
				
			||||||
import com.jme3.material.Material;
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector2f;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector3f;
 | 
					 | 
				
			||||||
import com.jme3.renderer.Camera;
 | 
					 | 
				
			||||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 | 
					 | 
				
			||||||
import com.jme3.scene.Geometry;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.scene.Spatial;
 | 
					 | 
				
			||||||
import com.jme3.scene.shape.Box;
 | 
					 | 
				
			||||||
import com.jme3.shadow.DirectionalLightShadowRenderer;
 | 
					 | 
				
			||||||
import com.jme3.shadow.EdgeFilteringMode;
 | 
					 | 
				
			||||||
import com.jme3.texture.Texture;
 | 
					 | 
				
			||||||
import com.jme3.util.SkyFactory;
 | 
					 | 
				
			||||||
import com.jme3.util.TangentBinormalGenerator;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipAppState;
 | 
					 | 
				
			||||||
import pp.battleship.model.ShipMap;
 | 
					 | 
				
			||||||
import pp.util.FloatMath;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.TWO_PI;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.cos;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.sin;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.sqrt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Manages the rendering and visual aspects of the sea and sky in the Battleship game.
 | 
					 | 
				
			||||||
 * This state is responsible for setting up and updating the sea, sky, and lighting
 | 
					 | 
				
			||||||
 * conditions, and controls the camera to create a dynamic view of the game environment.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class SeaAppState extends BattleshipAppState {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The path to the sea texture material.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private static final String SEA_TEXTURE = "Textures/Terrain/Water/Water.j3m"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final float ABOVE_SEA_LEVEL = 4f;
 | 
					 | 
				
			||||||
    private static final float INCLINATION = 2.5f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The root node for all visual elements in this state.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final Node viewNode = new Node("view"); //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The node containing the scene elements, such as the sea surface.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final Node sceneNode = new Node("scene"); //NON-NLS
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Synchronizes the sea's visual representation with the game logic.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private SeaSynchronizer synchronizer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The current angle of the camera around the center of the map.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private float cameraAngle;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Initializes the state by setting up the sky, lights, and other visual components.
 | 
					 | 
				
			||||||
     * This method is called when the state is first attached to the state manager.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param stateManager the state manager
 | 
					 | 
				
			||||||
     * @param application  the application
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void initialize(AppStateManager stateManager, Application application) {
 | 
					 | 
				
			||||||
        super.initialize(stateManager, application);
 | 
					 | 
				
			||||||
        viewNode.attachChild(sceneNode);
 | 
					 | 
				
			||||||
        setupLights();
 | 
					 | 
				
			||||||
        setupSky();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Enables the sea and sky state, setting up the scene and registering any necessary listeners.
 | 
					 | 
				
			||||||
     * This method is called when the state is set to active.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void enableState() {
 | 
					 | 
				
			||||||
        sceneNode.detachAllChildren();
 | 
					 | 
				
			||||||
        setupScene();
 | 
					 | 
				
			||||||
        if (synchronizer == null) {
 | 
					 | 
				
			||||||
            synchronizer = new SeaSynchronizer(getApp(), sceneNode, getGameLogic().getOwnMap());
 | 
					 | 
				
			||||||
            getGameLogic().addListener(synchronizer);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        getApp().getRootNode().attachChild(viewNode);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Disables the sea and sky state, removing visual elements from the scene and unregistering listeners.
 | 
					 | 
				
			||||||
     * This method is called when the state is set to inactive.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void disableState() {
 | 
					 | 
				
			||||||
        getApp().getRootNode().detachChild(viewNode);
 | 
					 | 
				
			||||||
        if (synchronizer != null) {
 | 
					 | 
				
			||||||
            getGameLogic().removeListener(synchronizer);
 | 
					 | 
				
			||||||
            synchronizer = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the state each frame, moving the camera to simulate it circling around the map.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf the time per frame (seconds)
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void update(float tpf) {
 | 
					 | 
				
			||||||
        super.update(tpf);
 | 
					 | 
				
			||||||
        cameraAngle += TWO_PI * 0.05f * tpf;
 | 
					 | 
				
			||||||
        adjustCamera();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets up the lighting for the scene, including directional and ambient lights.
 | 
					 | 
				
			||||||
     * Also configures shadows to enhance the visual depth of the scene.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupLights() {
 | 
					 | 
				
			||||||
        final AssetManager assetManager = getApp().getAssetManager();
 | 
					 | 
				
			||||||
        final DirectionalLightShadowRenderer shRend = new DirectionalLightShadowRenderer(assetManager, 2048, 3);
 | 
					 | 
				
			||||||
        shRend.setLambda(0.55f);
 | 
					 | 
				
			||||||
        shRend.setShadowIntensity(0.6f);
 | 
					 | 
				
			||||||
        shRend.setEdgeFilteringMode(EdgeFilteringMode.Bilinear);
 | 
					 | 
				
			||||||
        getApp().getViewPort().addProcessor(shRend);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final DirectionalLight sun = new DirectionalLight();
 | 
					 | 
				
			||||||
        sun.setDirection(new Vector3f(-1f, -0.7f, -1f).normalizeLocal());
 | 
					 | 
				
			||||||
        viewNode.addLight(sun);
 | 
					 | 
				
			||||||
        shRend.setLight(sun);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.3f, 0.3f, 0.3f, 0f));
 | 
					 | 
				
			||||||
        viewNode.addLight(ambientLight);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets up the sky in the scene using a skybox with textures for all six directions.
 | 
					 | 
				
			||||||
     * This creates a realistic and immersive environment for the sea.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupSky() {
 | 
					 | 
				
			||||||
        final AssetManager assetManager = getApp().getAssetManager();
 | 
					 | 
				
			||||||
        final Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Texture north = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_north.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Texture south = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_south.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Texture up = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_up.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Texture down = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_down.jpg"); //NON-NLS
 | 
					 | 
				
			||||||
        final Spatial sky = SkyFactory.createSky(assetManager, west, east, north, south, up, down);
 | 
					 | 
				
			||||||
        sky.rotate(0f, FloatMath.PI, 0f);
 | 
					 | 
				
			||||||
        viewNode.attachChild(sky);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Sets up the sea surface in the scene. This includes creating the sea mesh,
 | 
					 | 
				
			||||||
     * applying textures, and enabling shadows.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void setupScene() {
 | 
					 | 
				
			||||||
        final ShipMap ownMap = getGameLogic().getOwnMap();
 | 
					 | 
				
			||||||
        final float x = 0.5f * ownMap.getWidth();
 | 
					 | 
				
			||||||
        final float y = 0.5f * ownMap.getHeight();
 | 
					 | 
				
			||||||
        final Box seaMesh = new Box(y + 0.5f, 0.1f, x + 0.5f);
 | 
					 | 
				
			||||||
        final Geometry seaGeo = new Geometry("sea", seaMesh); //NON-NLS
 | 
					 | 
				
			||||||
        seaGeo.setLocalTranslation(new Vector3f(y, -0.1f, x));
 | 
					 | 
				
			||||||
        seaMesh.scaleTextureCoordinates(new Vector2f(4f, 4f));
 | 
					 | 
				
			||||||
        final Material seaMat = getApp().getAssetManager().loadMaterial(SEA_TEXTURE);
 | 
					 | 
				
			||||||
        seaGeo.setMaterial(seaMat);
 | 
					 | 
				
			||||||
        seaGeo.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
        TangentBinormalGenerator.generate(seaGeo);
 | 
					 | 
				
			||||||
        sceneNode.attachChild(seaGeo);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adjusts the camera position and orientation to create a circular motion around
 | 
					 | 
				
			||||||
     * the center of the map. This provides a dynamic view of the sea and surrounding environment.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void adjustCamera() {
 | 
					 | 
				
			||||||
        final ShipMap ownMap = getGameLogic().getOwnMap();
 | 
					 | 
				
			||||||
        final float mx = 0.5f * ownMap.getWidth();
 | 
					 | 
				
			||||||
        final float my = 0.5f * ownMap.getHeight();
 | 
					 | 
				
			||||||
        final float radius = 2f * sqrt(mx * mx + my + my);
 | 
					 | 
				
			||||||
        final float cos = radius * cos(cameraAngle);
 | 
					 | 
				
			||||||
        final float sin = radius * sin(cameraAngle);
 | 
					 | 
				
			||||||
        final float x = mx - cos;
 | 
					 | 
				
			||||||
        final float y = my - sin;
 | 
					 | 
				
			||||||
        final Camera camera = getApp().getCamera();
 | 
					 | 
				
			||||||
        camera.setLocation(new Vector3f(y, ABOVE_SEA_LEVEL, x));
 | 
					 | 
				
			||||||
        camera.getRotation().lookAt(new Vector3f(sin, -INCLINATION, cos),
 | 
					 | 
				
			||||||
                                    Vector3f.UNIT_Y);
 | 
					 | 
				
			||||||
        camera.update();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,378 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.effect.ParticleEmitter;
 | 
					 | 
				
			||||||
import com.jme3.material.Material;
 | 
					 | 
				
			||||||
import com.jme3.material.RenderState.BlendMode;
 | 
					 | 
				
			||||||
import com.jme3.math.ColorRGBA;
 | 
					 | 
				
			||||||
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
 | 
					 | 
				
			||||||
import com.jme3.scene.Geometry;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.scene.Spatial;
 | 
					 | 
				
			||||||
import com.jme3.scene.shape.Box;
 | 
					 | 
				
			||||||
import com.jme3.scene.shape.Cylinder;
 | 
					 | 
				
			||||||
import pp.battleship.client.BattleshipApp;
 | 
					 | 
				
			||||||
import pp.battleship.model.Battleship;
 | 
					 | 
				
			||||||
import pp.battleship.model.Rotation;
 | 
					 | 
				
			||||||
import pp.battleship.model.Shell;
 | 
					 | 
				
			||||||
import pp.battleship.model.ShipMap;
 | 
					 | 
				
			||||||
import pp.battleship.model.Shot;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static java.util.Objects.requireNonNull;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.HALF_PI;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.PI;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The {@code SeaSynchronizer} class is responsible for synchronizing the graphical
 | 
					 | 
				
			||||||
 * representation of the ships and shots on the sea map with the underlying data model.
 | 
					 | 
				
			||||||
 * It extends the {@link ShipMapSynchronizer} to provide specific synchronization
 | 
					 | 
				
			||||||
 * logic for the sea map.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class SeaSynchronizer extends ShipMapSynchronizer {
 | 
					 | 
				
			||||||
    private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String BOAT_SMALL_MODEL = "Models/BoatSmall/12219_boat_v2_L2.j3o"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String CV_MODEL = "Models/CV/CV.j3o"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String BATTLE_MODEL = "Models/Battle/Battle.j3o"; //NON-NLS
 | 
					 | 
				
			||||||
    private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md";
 | 
					 | 
				
			||||||
    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 final ParticleEffectFactory particleFactory;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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;
 | 
					 | 
				
			||||||
        this.particleFactory = new ParticleEffectFactory(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) : handleMiss(shot);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles a miss by representing it with a blue cylinder
 | 
					 | 
				
			||||||
     * and attaching a water splash effect to it.
 | 
					 | 
				
			||||||
     * @param shot the shot to be processed
 | 
					 | 
				
			||||||
     * @return a Spatial simulating a miss with water splash effect
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial handleMiss(Shot shot) {
 | 
					 | 
				
			||||||
        Node shotNode = new Node("ShotNode");
 | 
					 | 
				
			||||||
        Geometry shotCylinder = createCylinder(shot);
 | 
					 | 
				
			||||||
        shotNode.attachChild(shotCylinder);
 | 
					 | 
				
			||||||
        ParticleEmitter waterSplash = particleFactory.createWaterSplash();
 | 
					 | 
				
			||||||
        waterSplash.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
					 | 
				
			||||||
        shotNode.attachChild(waterSplash);
 | 
					 | 
				
			||||||
        waterSplash.emitAllParticles();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return shotNode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the sinking animation and removal of ship if destroyed
 | 
					 | 
				
			||||||
     * @param ship the ship to be sunk
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private void sinkAndRemoveShip(Battleship ship) {
 | 
					 | 
				
			||||||
        Battleship wilkeningklaunichtmeinencode = ship;
 | 
					 | 
				
			||||||
        final Node shipNode = (Node) getSpatial(wilkeningklaunichtmeinencode);
 | 
					 | 
				
			||||||
        if (shipNode == null) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Add sinking control to animate the sinking
 | 
					 | 
				
			||||||
        shipNode.addControl(new SinkingControl(shipNode));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Add particle effects
 | 
					 | 
				
			||||||
        ParticleEmitter bubbles = particleFactory.createWaterSplash();
 | 
					 | 
				
			||||||
        bubbles.setLocalTranslation(shipNode.getLocalTranslation());
 | 
					 | 
				
			||||||
        shipNode.attachChild(bubbles);
 | 
					 | 
				
			||||||
        bubbles.emitAllParticles();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Create a new node specifically for the hit effects
 | 
					 | 
				
			||||||
        Node hitEffectNode = new Node("HitEffectNode");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Create particle effects
 | 
					 | 
				
			||||||
        ParticleEmitter flame = particleFactory.createFlame();
 | 
					 | 
				
			||||||
        ParticleEmitter flash = particleFactory.createFlash();
 | 
					 | 
				
			||||||
        ParticleEmitter spark = particleFactory.createSpark();
 | 
					 | 
				
			||||||
        ParticleEmitter roundSpark = particleFactory.createRoundSpark();
 | 
					 | 
				
			||||||
        ParticleEmitter smokeTrail = particleFactory.createSmokeTrail();
 | 
					 | 
				
			||||||
        ParticleEmitter debris = particleFactory.createDebris();
 | 
					 | 
				
			||||||
        ParticleEmitter shockwave = particleFactory.createShockwave();
 | 
					 | 
				
			||||||
        ParticleEmitter movingSmoke = particleFactory.createMovingSmokeEmitter();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Attach all effects to the hitEffectNode
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(flame);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(flash);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(spark);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(roundSpark);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(smokeTrail);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(debris);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(shockwave);
 | 
					 | 
				
			||||||
        hitEffectNode.attachChild(movingSmoke);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Set the local translation for the hit effect to the point of impact
 | 
					 | 
				
			||||||
        hitEffectNode.setLocalTranslation(shot.getY() + 0.5f - shipNode.getLocalTranslation().x,
 | 
					 | 
				
			||||||
                                          0.5f, // Adjust as needed for height above the ship
 | 
					 | 
				
			||||||
                                          shot.getX() + 0.5f - shipNode.getLocalTranslation().z);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Attach the hitEffectNode to the shipNode so it moves with the ship
 | 
					 | 
				
			||||||
        shipNode.attachChild(hitEffectNode);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Emit particles when the hit happens
 | 
					 | 
				
			||||||
        flash.emitAllParticles();
 | 
					 | 
				
			||||||
        spark.emitAllParticles();
 | 
					 | 
				
			||||||
        smokeTrail.emitAllParticles();
 | 
					 | 
				
			||||||
        debris.emitAllParticles();
 | 
					 | 
				
			||||||
        shockwave.emitAllParticles();
 | 
					 | 
				
			||||||
        flame.emitAllParticles();
 | 
					 | 
				
			||||||
        roundSpark.emitAllParticles();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //Checks if ship is destroyed and triggers animation accordingly
 | 
					 | 
				
			||||||
        if (ship.isDestroyed()) {
 | 
					 | 
				
			||||||
            sinkAndRemoveShip(ship);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a cylinder geometry representing the specified shot.
 | 
					 | 
				
			||||||
     * The appearance of the cylinder depends on whether the shot is a hit or a miss.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param shot the shot to be represented
 | 
					 | 
				
			||||||
     * @return the geometry representing the shot
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Geometry createCylinder(Shot shot) {
 | 
					 | 
				
			||||||
        final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR;
 | 
					 | 
				
			||||||
        final float height = shot.isHit() ? 1.2f : 0.1f;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true);
 | 
					 | 
				
			||||||
        final Geometry geometry = new Geometry(SHOT, cylinder);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        geometry.setMaterial(createColoredMaterial(color));
 | 
					 | 
				
			||||||
        geometry.rotate(HALF_PI, 0f, 0f);
 | 
					 | 
				
			||||||
        // compute the center of the shot in world coordinates
 | 
					 | 
				
			||||||
        geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return geometry;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Visits a {@link Battleship} and creates a graphical representation of it.
 | 
					 | 
				
			||||||
     * The representation is either a 3D model or a simple box depending on the
 | 
					 | 
				
			||||||
     * type of battleship.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the node containing the graphical representation of the battleship
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Spatial visit(Battleship ship) {
 | 
					 | 
				
			||||||
        final Node node = new Node(SHIP);
 | 
					 | 
				
			||||||
        node.attachChild(createShip(ship));
 | 
					 | 
				
			||||||
        // compute the center of the ship in world coordinates
 | 
					 | 
				
			||||||
        final float x = 0.5f * (ship.getMinY() + ship.getMaxY() + 1f);
 | 
					 | 
				
			||||||
        final float z = 0.5f * (ship.getMinX() + ship.getMaxX() + 1f);
 | 
					 | 
				
			||||||
        node.setLocalTranslation(x, 0f, z);
 | 
					 | 
				
			||||||
        node.addControl(new ShipControl(ship));
 | 
					 | 
				
			||||||
        return node;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates the appropriate graphical representation of the specified battleship.
 | 
					 | 
				
			||||||
     * The representation is either a detailed model or a simple box based on the length of the ship.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the spatial representing the battleship
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createShip(Battleship ship) {
 | 
					 | 
				
			||||||
        switch (ship.getLength()) {
 | 
					 | 
				
			||||||
            case 4: return createBattleship(ship);
 | 
					 | 
				
			||||||
            case 3: return createCV(ship);
 | 
					 | 
				
			||||||
            case 2: return createBattle(ship);
 | 
					 | 
				
			||||||
            case 1: return createSmallship(ship);
 | 
					 | 
				
			||||||
            default: return createBox(ship);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a simple box to represent a battleship that is not of the "King George V" type.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the geometry representing the battleship as a box
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createBox(Battleship ship) {
 | 
					 | 
				
			||||||
        final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f,
 | 
					 | 
				
			||||||
                                0.3f,
 | 
					 | 
				
			||||||
                                0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f);
 | 
					 | 
				
			||||||
        final Geometry geometry = new Geometry(SHIP, box);
 | 
					 | 
				
			||||||
        geometry.setMaterial(createColoredMaterial(BOX_COLOR));
 | 
					 | 
				
			||||||
        geometry.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return geometry;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a new {@link Material} with the specified color.
 | 
					 | 
				
			||||||
     * If the color includes transparency (i.e., alpha value less than 1),
 | 
					 | 
				
			||||||
     * the material's render state is set to use alpha blending, allowing for
 | 
					 | 
				
			||||||
     * semi-transparent rendering.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param color the {@link ColorRGBA} to be applied to the material. If the alpha value
 | 
					 | 
				
			||||||
     *              of the color is less than 1, the material will support transparency.
 | 
					 | 
				
			||||||
     * @return a {@link Material} instance configured with the specified color and,
 | 
					 | 
				
			||||||
     * if necessary, alpha blending enabled.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Material createColoredMaterial(ColorRGBA color) {
 | 
					 | 
				
			||||||
        final Material material = new Material(app.getAssetManager(), UNSHADED);
 | 
					 | 
				
			||||||
        if (color.getAlpha() < 1f)
 | 
					 | 
				
			||||||
            material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
 | 
					 | 
				
			||||||
        material.setColor(COLOR, color);
 | 
					 | 
				
			||||||
        return material;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a detailed 3D model to represent a "King George V" battleship.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the spatial representing the "King George V" battleship
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createBattleship(Battleship ship) {
 | 
					 | 
				
			||||||
        final Spatial model = app.getAssetManager().loadModel(KING_GEORGE_V_MODEL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
					 | 
				
			||||||
        model.scale(1.48f);
 | 
					 | 
				
			||||||
        // model.scale(0.0007f);
 | 
					 | 
				
			||||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return model;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a detailed 3D model to represent a small tug boat.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the spatial representing a small tug boat
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createSmallship(Battleship ship) {
 | 
					 | 
				
			||||||
        final Spatial model = app.getAssetManager().loadModel(BOAT_SMALL_MODEL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
					 | 
				
			||||||
        model.scale(0.0005f);
 | 
					 | 
				
			||||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return model;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a detailed 3D model to represent a "German WWII UBoat".
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the spatial representing the "German WWII UBoat"
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createCV(Battleship ship) {
 | 
					 | 
				
			||||||
        final Spatial model = app.getAssetManager().loadModel(CV_MODEL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model.rotate(0f, calculateRotationAngle(ship.getRot()), 0f);
 | 
					 | 
				
			||||||
        model.move(0f, 0.25f, 0f);
 | 
					 | 
				
			||||||
        model.scale(0.85f);
 | 
					 | 
				
			||||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return model;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates a detailed 3D model to represent a battleship.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the battleship to be represented
 | 
					 | 
				
			||||||
     * @return the spatial representing a battleship
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private Spatial createBattle(Battleship ship) {
 | 
					 | 
				
			||||||
        final Spatial model = app.getAssetManager().loadModel(BATTLE_MODEL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
 | 
					 | 
				
			||||||
        model.move(0f, -0.06f, 0f);
 | 
					 | 
				
			||||||
        model.scale(0.27f);
 | 
					 | 
				
			||||||
        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;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates and returns a 3D model representation of the given {@code Shell} object
 | 
					 | 
				
			||||||
     * for visualization in the game.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param shell The {@code Shell} object to be visualized.
 | 
					 | 
				
			||||||
     * @return A {@code Spatial} object representing the 3D model of the shell.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public Spatial visit(Shell shell) {
 | 
					 | 
				
			||||||
        final Spatial model = app.getAssetManager().loadModel("Models/Shell/shell.j3o");
 | 
					 | 
				
			||||||
        model.setLocalScale(.05f);
 | 
					 | 
				
			||||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
					 | 
				
			||||||
        Material mat = new Material(app.getAssetManager(), LIGHTING);
 | 
					 | 
				
			||||||
        mat.setTexture("DiffuseMap", app.getAssetManager().loadTexture("Models/Shell/shell_color.png"));
 | 
					 | 
				
			||||||
        mat.setReceivesShadows(true);
 | 
					 | 
				
			||||||
        model.setMaterial(mat);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        model.addControl(new ShellControl(shell));
 | 
					 | 
				
			||||||
        return model;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,57 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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.Shell;
 | 
					 | 
				
			||||||
import pp.util.Position;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Controls the 2D representation of a {@code Shell} in the game, updating its position
 | 
					 | 
				
			||||||
 * based on the shell's current state in the game model. The {@code Shell2DControl} class
 | 
					 | 
				
			||||||
 * is responsible for translating the shell's 3D position to a 2D view position within
 | 
					 | 
				
			||||||
 * the game's map view.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class Shell2DControl extends AbstractControl {
 | 
					 | 
				
			||||||
    private final Shell shell;
 | 
					 | 
				
			||||||
    private final MapView view;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new {@code Shell2DControl} to manage the 2D visualization of the given {@code Shell}.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param view  The {@code MapView} used to get information about the map to display.
 | 
					 | 
				
			||||||
     * @param shell The {@code Shell} being visualized.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public Shell2DControl(MapView view, Shell shell){
 | 
					 | 
				
			||||||
        this.shell = shell;
 | 
					 | 
				
			||||||
        this.view = view;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the position of the shell's 2D representation based on the shell's current
 | 
					 | 
				
			||||||
     * 3D position in the game model. The position is mapped from model space to view space
 | 
					 | 
				
			||||||
     * coordinates and translated to the appropriate location within the {@code MapView}.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf Time per frame, representing the time elapsed since the last frame.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void controlUpdate(float tpf) {
 | 
					 | 
				
			||||||
        Vector3f shellPos = shell.getPosition();
 | 
					 | 
				
			||||||
        Position viewPos = view.modelToView(shellPos.x, shellPos.z);
 | 
					 | 
				
			||||||
        spatial.setLocalTranslation(viewPos.getX() + MapView.FIELD_SIZE / 2, viewPos.getY() + MapView.FIELD_SIZE / 2, 0);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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-specific behavior required for this control
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,58 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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.Shell;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.PI;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Controls the 3D representation of a {@code Shell} in the game, updating its position
 | 
					 | 
				
			||||||
 * and rotation based on the shell's current state in the game model. The {@code ShellControl}
 | 
					 | 
				
			||||||
 * class ensures that the spatial associated with the shell is positioned and oriented correctly
 | 
					 | 
				
			||||||
 * within the world.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class ShellControl extends AbstractControl {
 | 
					 | 
				
			||||||
    private final Shell shell;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new {@code ShellControl} to manage the 3D visualization of the given {@code Shell}.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param shell The {@code Shell} being visualized and controlled.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ShellControl(Shell shell){
 | 
					 | 
				
			||||||
        super();
 | 
					 | 
				
			||||||
        this.shell = shell;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the 3D position and rotation of the shell based on its current state.
 | 
					 | 
				
			||||||
     * Converts map coordinates to world coordinates and applies the shell's orientation.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf Time per frame, representing the elapsed time since the last update.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void controlUpdate(float tpf) {
 | 
					 | 
				
			||||||
        Vector3f pos = shell.getPosition();
 | 
					 | 
				
			||||||
        Vector3f fixed = new Vector3f(pos.z + 0.5f, pos.y, pos.x + 0.5f);
 | 
					 | 
				
			||||||
        fixed.setY(pos.y);
 | 
					 | 
				
			||||||
        spatial.setLocalTranslation(fixed);
 | 
					 | 
				
			||||||
        spatial.setLocalRotation(shell.getRotation());
 | 
					 | 
				
			||||||
        spatial.rotate(PI/2,0,0);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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-specific behavior required for this control
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,106 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.math.Quaternion;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector3f;
 | 
					 | 
				
			||||||
import com.jme3.renderer.RenderManager;
 | 
					 | 
				
			||||||
import com.jme3.renderer.ViewPort;
 | 
					 | 
				
			||||||
import com.jme3.scene.control.AbstractControl;
 | 
					 | 
				
			||||||
import pp.battleship.model.Battleship;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.DEG_TO_RAD;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.TWO_PI;
 | 
					 | 
				
			||||||
import static pp.util.FloatMath.sin;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Controls the oscillating pitch motion of a battleship model in the game.
 | 
					 | 
				
			||||||
 * The ship oscillates to simulate a realistic movement on water, based on its orientation and length.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class ShipControl extends AbstractControl {
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The axis of rotation for the ship's pitch (tilting forward and backward).
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final Vector3f axis;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The duration of one complete oscillation cycle in seconds.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final float cycle;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The amplitude of the pitch oscillation in radians, determining how much the ship tilts.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final float amplitude;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * A quaternion representing the ship's current pitch rotation.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private final Quaternion pitch = new Quaternion();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * The current time within the oscillation cycle, used to calculate the ship's pitch angle.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    private float time;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new ShipControl instance for the specified Battleship.
 | 
					 | 
				
			||||||
     * The ship's orientation determines the axis of rotation, while its length influences
 | 
					 | 
				
			||||||
     * the cycle duration and amplitude of the oscillation.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param ship the Battleship object to control
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public ShipControl(Battleship ship) {
 | 
					 | 
				
			||||||
        // Determine the axis of rotation based on the ship's orientation
 | 
					 | 
				
			||||||
        axis = switch (ship.getRot()) {
 | 
					 | 
				
			||||||
            case LEFT, RIGHT -> Vector3f.UNIT_X;
 | 
					 | 
				
			||||||
            case UP, DOWN -> Vector3f.UNIT_Z;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Set the cycle duration and amplitude based on the ship's length
 | 
					 | 
				
			||||||
        cycle = ship.getLength() * 2f;
 | 
					 | 
				
			||||||
        amplitude = 5f * DEG_TO_RAD / ship.getLength();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted
 | 
					 | 
				
			||||||
     * to create a continuous tilting motion, simulating the effect of waves.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param tpf time per frame (in seconds), used to calculate the new pitch angle
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void controlUpdate(float tpf) {
 | 
					 | 
				
			||||||
        // If spatial is null, do nothing
 | 
					 | 
				
			||||||
        if (spatial == null) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Update the time within the oscillation cycle
 | 
					 | 
				
			||||||
        time = (time + tpf) % cycle;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Calculate the current angle of the oscillation
 | 
					 | 
				
			||||||
        final float angle = amplitude * sin(time * TWO_PI / cycle);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Update the pitch Quaternion with the new angle
 | 
					 | 
				
			||||||
        pitch.fromAngleAxis(angle, axis);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Apply the pitch rotation to the spatial
 | 
					 | 
				
			||||||
        spatial.setLocalRotation(pitch);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * This method is called during the rendering phase, but it does not perform any
 | 
					 | 
				
			||||||
     * operations in this implementation as the control only influences the spatial's
 | 
					 | 
				
			||||||
     * transformation, not its rendering process.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param rm the RenderManager rendering the controlled Spatial (not null)
 | 
					 | 
				
			||||||
     * @param vp the ViewPort being rendered (not null)
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
					 | 
				
			||||||
        // No rendering logic is needed for this control
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,89 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
import com.jme3.scene.Spatial;
 | 
					 | 
				
			||||||
import pp.battleship.model.Item;
 | 
					 | 
				
			||||||
import pp.battleship.model.ShipMap;
 | 
					 | 
				
			||||||
import pp.battleship.model.Visitor;
 | 
					 | 
				
			||||||
import pp.battleship.notification.GameEventListener;
 | 
					 | 
				
			||||||
import pp.battleship.notification.ItemAddedEvent;
 | 
					 | 
				
			||||||
import pp.battleship.notification.ItemRemovedEvent;
 | 
					 | 
				
			||||||
import pp.view.ModelViewSynchronizer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Abstract base class for synchronizing the visual representation of a {@link ShipMap} with its model state.
 | 
					 | 
				
			||||||
 * This class handles the addition and removal of items from the ship map, ensuring that changes in the model
 | 
					 | 
				
			||||||
 * are accurately reflected in the view.
 | 
					 | 
				
			||||||
 * <p>
 | 
					 | 
				
			||||||
 * Subclasses are responsible for providing the specific implementation of how each item in the map
 | 
					 | 
				
			||||||
 * is represented visually by implementing the {@link Visitor} interface.
 | 
					 | 
				
			||||||
 * </p>
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
abstract class ShipMapSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener {
 | 
					 | 
				
			||||||
    // The ship map that this synchronizer is responsible for
 | 
					 | 
				
			||||||
    private final ShipMap shipMap;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a new ShipMapSynchronizer.
 | 
					 | 
				
			||||||
     * Initializes the synchronizer with the provided ship map and the root node for attaching view representations.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param map  the ship map to be synchronized
 | 
					 | 
				
			||||||
     * @param root the root node to which the view representations of the ship map items are attached
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected ShipMapSynchronizer(ShipMap map, Node root) {
 | 
					 | 
				
			||||||
        super(root);
 | 
					 | 
				
			||||||
        this.shipMap = map;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Translates a model item into its corresponding visual representation.
 | 
					 | 
				
			||||||
     * The specific visual representation is determined by the concrete implementation of the {@link Visitor} interface.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param item the item from the model to be translated
 | 
					 | 
				
			||||||
     * @return the visual representation of the item as a {@link Spatial}
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected Spatial translate(Item item) {
 | 
					 | 
				
			||||||
        return item.accept(this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Adds the existing items from the ship map to the view.
 | 
					 | 
				
			||||||
     * This method should be called during initialization to ensure that all current items in the ship map
 | 
					 | 
				
			||||||
     * are visually represented.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    protected void addExisting() {
 | 
					 | 
				
			||||||
        shipMap.getItems().forEach(this::add);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the event when an item is removed from the ship map.
 | 
					 | 
				
			||||||
     * Removes the visual representation of the item from the view if it belongs to the synchronized ship map.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param event the event indicating that an item has been removed from the ship map
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void receivedEvent(ItemRemovedEvent event) {
 | 
					 | 
				
			||||||
        if (shipMap == event.map())
 | 
					 | 
				
			||||||
            delete(event.item());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Handles the event when an item is added to the ship map.
 | 
					 | 
				
			||||||
     * Adds the visual representation of the new item to the view if it belongs to the synchronized ship map.
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @param event the event indicating that an item has been added to the ship map
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void receivedEvent(ItemAddedEvent event) {
 | 
					 | 
				
			||||||
        if (shipMap == event.map())
 | 
					 | 
				
			||||||
            add(event.item());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,61 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.scene.control.AbstractControl;
 | 
					 | 
				
			||||||
import com.jme3.math.Vector3f;
 | 
					 | 
				
			||||||
import com.jme3.renderer.RenderManager;
 | 
					 | 
				
			||||||
import com.jme3.renderer.ViewPort;
 | 
					 | 
				
			||||||
import com.jme3.scene.Node;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Control that handles the sinking effect for destroyed ships.
 | 
					 | 
				
			||||||
 * It will gradually move the ship downwards and then remove it from the scene.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
class SinkingControl extends AbstractControl {
 | 
					 | 
				
			||||||
    private static final float SINK_DURATION = 5f;  // Duration of the sinking animation
 | 
					 | 
				
			||||||
    private static final float SINK_SPEED = 0.1f;   // Speed at which the ship sinks
 | 
					 | 
				
			||||||
    private float elapsedTime = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final Node shipNode;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs a {@code SinkingControl} object with the shipNode to be to be sunk
 | 
					 | 
				
			||||||
     * @param shipNode the node to handeld
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public SinkingControl(Node shipNode) {
 | 
					 | 
				
			||||||
        this.shipNode = shipNode;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Updated the Map to sink the ship
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @param tpf time per frame
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    protected void controlUpdate(float tpf) {
 | 
					 | 
				
			||||||
        // Update the sinking effect
 | 
					 | 
				
			||||||
        elapsedTime += tpf;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Move the ship down over time
 | 
					 | 
				
			||||||
        Vector3f currentPos = shipNode.getLocalTranslation();
 | 
					 | 
				
			||||||
        shipNode.setLocalTranslation(currentPos.x, currentPos.y - SINK_SPEED * tpf, currentPos.z);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Check if sinking duration has passed
 | 
					 | 
				
			||||||
        if (elapsedTime >= SINK_DURATION) {
 | 
					 | 
				
			||||||
            // Remove the ship from the scene
 | 
					 | 
				
			||||||
            shipNode.removeFromParent();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 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-related code needed
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,36 +0,0 @@
 | 
				
			|||||||
package pp.battleship.client.gui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.simsilica.lemur.Slider;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * The VolumeSlider class represents the Volume Slider in the Menu.
 | 
					 | 
				
			||||||
 * It extends the Slider class and provides functionalities for setting the music volume,
 | 
					 | 
				
			||||||
 * with the help of the Slider in the GUI 
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class VolumeSlider extends Slider {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private final GameMusic music;
 | 
					 | 
				
			||||||
    private double vol;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Constructs the Volume Slider for the Menu dialog
 | 
					 | 
				
			||||||
     * @param music the music instance
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public VolumeSlider(GameMusic music) {
 | 
					 | 
				
			||||||
        super();
 | 
					 | 
				
			||||||
        this.music = music;
 | 
					 | 
				
			||||||
        vol = GameMusic.volumeInPreferences();
 | 
					 | 
				
			||||||
        getModel().setPercent(vol);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * when triggered it updates the volume to the value set with the slider
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public void update() {
 | 
					 | 
				
			||||||
        if (vol != getModel().getPercent()) {
 | 
					 | 
				
			||||||
            vol = getModel().getPercent();
 | 
					 | 
				
			||||||
            music.setVolume( (float) vol);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,182 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.server;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.jme3.network.ConnectionListener;
 | 
					 | 
				
			||||||
import com.jme3.network.HostedConnection;
 | 
					 | 
				
			||||||
import com.jme3.network.Message;
 | 
					 | 
				
			||||||
import com.jme3.network.MessageListener;
 | 
					 | 
				
			||||||
import com.jme3.network.Network;
 | 
					 | 
				
			||||||
import com.jme3.network.Server;
 | 
					 | 
				
			||||||
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.AnimationFinishedMessage;
 | 
					 | 
				
			||||||
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 final BattleshipConfig config = new BattleshipConfig();
 | 
					 | 
				
			||||||
    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());
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Starts the Battleships server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    public static void main(String[] args) {
 | 
					 | 
				
			||||||
        new BattleshipServer().run();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * Creates the server.
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    BattleshipServer() {
 | 
					 | 
				
			||||||
        config.readFromIfExists(CONFIG_FILE);
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
 | 
					 | 
				
			||||||
        logic = new ServerGameLogic(this, config);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    public void run() {
 | 
					 | 
				
			||||||
        startServer();
 | 
					 | 
				
			||||||
        while (true)
 | 
					 | 
				
			||||||
            processNextMessage();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void startServer() {
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
 | 
					 | 
				
			||||||
            myServer = Network.createServer(config.getPort());
 | 
					 | 
				
			||||||
            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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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(AnimationFinishedMessage.class);
 | 
					 | 
				
			||||||
        Serializer.registerClass(Battleship.class);
 | 
					 | 
				
			||||||
        Serializer.registerClass(IntPoint.class);
 | 
					 | 
				
			||||||
        Serializer.registerClass(Shot.class);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private void registerListeners() {
 | 
					 | 
				
			||||||
        myServer.addMessageListener(this, MapMessage.class);
 | 
					 | 
				
			||||||
        myServer.addMessageListener(this, ShootMessage.class);
 | 
					 | 
				
			||||||
        myServer.addMessageListener(this, AnimationFinishedMessage.class);
 | 
					 | 
				
			||||||
        myServer.addConnectionListener(this);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @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()));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Override
 | 
					 | 
				
			||||||
    public void connectionAdded(Server server, HostedConnection hostedConnection) {
 | 
					 | 
				
			||||||
        LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
 | 
					 | 
				
			||||||
        logic.addPlayer(hostedConnection.getId());
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @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);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    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
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,17 +0,0 @@
 | 
				
			|||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
// Programming project code
 | 
					 | 
				
			||||||
// UniBw M, 2022, 2023, 2024
 | 
					 | 
				
			||||||
// www.unibw.de/inf2
 | 
					 | 
				
			||||||
// (c) Mark Minas (mark.minas@unibw.de)
 | 
					 | 
				
			||||||
////////////////////////////////////////
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package pp.battleship.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);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 46 KiB  | 
| 
		 Before Width: | Height: | Size: 63 KiB  | 
| 
		 Before Width: | Height: | Size: 2.0 KiB  | 
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 32 KiB  | 
| 
		 Before Width: | Height: | Size: 1.8 KiB  | 
| 
		 Before Width: | Height: | Size: 28 KiB  | 
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 168 KiB  | 
| 
		 Before Width: | Height: | Size: 56 KiB  | 
| 
		 Before Width: | Height: | Size: 166 KiB  | 
| 
		 Before Width: | Height: | Size: 98 KiB  | 
| 
		 Before Width: | Height: | Size: 35 KiB  | 
| 
		 Before Width: | Height: | Size: 54 KiB  | 
| 
		 Before Width: | Height: | Size: 9.3 KiB  | 
| 
		 Before Width: | Height: | Size: 66 KiB  | 
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 30 KiB  | 
| 
		 Before Width: | Height: | Size: 10 KiB  | 
| 
		 Before Width: | Height: | Size: 9.4 KiB  | 
| 
		 Before Width: | Height: | Size: 264 KiB  | 
| 
		 Before Width: | Height: | Size: 80 KiB  | 
| 
		 Before Width: | Height: | Size: 91 KiB  | 
| 
		 Before Width: | Height: | Size: 33 KiB  | 
| 
		 Before Width: | Height: | Size: 98 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 78 B  | 
| 
		 Before Width: | Height: | Size: 79 B  | 
| 
		 Before Width: | Height: | Size: 96 KiB  |