Compare commits
	
		
			1 Commits
		
	
	
		
			4a9eba255b
			...
			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" 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 | 
| Before Width: | Height: | Size: 82 KiB |