17 Commits

Author SHA1 Message Date
Lukas Bauer
66798eb3e4 Improved code documentation 2024-10-11 15:22:20 +02:00
Lukas Bauer
edff9e1ad9 Improved code documentation and clarify comments for better readability and understanding 2024-10-11 15:06:09 +02:00
Lukas Bauer
bf16a18b71 Aufgabe 13
edited class BattleState in order to implement the 2D map feature
edited class GameSound to add the Missile launch sound
edited class MapViewSynchronizer in order to implement the 2D map feature
edited class ImpactEffectManager removed unused import
edited class SeaSynchronizer removed unused code
edited class Shell in order to implement the 2D map feature
edited class ShellControl in order to implement the 2D map feature
edited Sound added Missile launch enum
edited FloatMath to improve the animation for the 2D projectile
added missileLaunch.wav
2024-10-11 14:23:49 +02:00
Lukas Bauer
6100e95e76 Aufgabe 13
added class Shell for the feature Airstrike
added class ShellControl for the feature Airstrike
added new Model Bomb to implement the feature Airstrike
added README´S to the Models to show the free to use license
edited class ShipMap for the feature Airstrike
edited class ImpactEffectManager to improve the Fire and Water Particles
edited class MapViewSynchronizer for the feature Airstrike
edited class BattleState for the feature Airstrike
edited class BattleshipApp for the feature Airstrike
2024-10-09 21:10:31 +02:00
Lukas Bauer
1b7b842ca7 JavaDoc adjustments 2024-10-08 19:46:23 +02:00
Lukas Bauer
865b0fc33c Aufgabe 12
added class ImpactEffectManager to implement the funktionality with hit or miss particles
added map4 for particle testing purposes
edited class SeaSynchronizer in order to implement the particle feature
edited class ShipControl in order to implement the particle feature
edited settings.gradle in order to implement the particle feature
2024-10-08 19:43:07 +02:00
Lukas Bauer
5a9b0b1de0 fix Aufgabe 11
edited class NetworkDialog
to change the logic that the checkbox works now as intended
moved BattleshipServer and ReceivedMessage into own folders to separate server from client
2024-10-07 20:11:15 +02:00
Lukas Bauer
8650fa5b58 fix edited class EditorState
fix edited battleship.properties with the right Key to fix an exception
fix edited battleship_de.properties with the right Key to fix an exception
2024-10-05 13:09:05 +02:00
Lukas Bauer
113da79df6 fix edited class NetworkDialog changed Button to Checklist 2024-10-04 18:42:47 +02:00
Lukas Bauer
e41d21584a Aufgabe 11.
added class BattleshipServer, ReceivedMessage
to implement the feature that a client is able to start an server
edited NetworkDialog to implement the logic to start an server and overhauled the GUI to be appropriate for the new features
2024-10-04 18:33:13 +02:00
Lukas Bauer
5e773ca1ba Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:11:53 +02:00
Lukas Bauer
b2b3bc23bf Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:10:36 +02:00
Lukas Bauer
ce490fb877 Aufgabe 10.
added class BackgroundMusic
edited BattleshipApp, GameSound, Menu
implemented a Slider for the Gui in order to change the Volume
Quality of Life
implemented feature to Safe the Sound settings to keep them even after shutting down the game
implemented feature to mute Sounds/effects
implemented feature to mute Music
2024-10-03 19:07:27 +02:00
Lukas Bauer
ce5e908349 edited SeaSynchronizer , EditorState, ServerGameLogic
added JavaDocs for the new and adapted functions
added a new invalid map for testing purposes
fixed implemented the logic to check for invalid maps in the method received in ServerGameLogic
2024-10-03 13:28:41 +02:00
Lukas Bauer
07e922d01e Aufgabe 9.
edited SeaSynchronizer
added 3 Ship Models Small Ship, Medium Ship and Big Ship and tweaked the scaling of all Models
2024-10-02 20:23:05 +02:00
Lukas Bauer
42b442e937 Aufgabe 8.
edited class EditorState and ServerGameLogic
 to check for invalid maps in which ships overlap or are out of the map boundries for clientside and serverside
2024-10-02 18:24:27 +02:00
Lukas Bauer
93ade34282 Aufgabe 7.
fix: in class ShipMap
function remove
hierbei wurde nach dem entfernen eines Items dass Event ItemAddedEvent ausgelöst hierbei sollte aber logischerweiße dass Event ItemRemovedEvent ausgelöst werden.

fix: in class BattleState
function receivedEffect
hierbei wurde die eigene map für die Bedingung isGameOver verglichen wobei man für isGameOver die OpponentMap auf remainingShips untersuchen muss.
2024-10-02 00:31:40 +02:00
120 changed files with 805546 additions and 703382 deletions

View File

@@ -1,18 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application"
singleton="false">
<option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp"/>
<module name="Projekte.battleship.client.main"/>
<option name="VM_PARAMETERS" value="-XstartOnFirstThread"/>
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$"/>
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.battleship.client.*"/>
<option name="ENABLED" value="true"/>
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true"/>
</method>
</configuration>
<configuration default="false" name="BattleshipApp (Mac)" type="Application" factoryName="Application" singleton="false">
<option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp" />
<module name="Projekte.battleship.client.main" />
<option name="VM_PARAMETERS" value="-XstartOnFirstThread" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.battleship.client.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -1,18 +1,19 @@
<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$"/>
<configuration default="false" name="BattleshipApp" type="Application" factoryName="Application" singleton="false" nameIsGenerated="true">
<option name="ALTERNATIVE_JRE_PATH" value="temurin-20" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="pp.battleship.client.BattleshipApp" />
<module name="Gruppe-01" />
<option name="VM_PARAMETERS" value="-Djava.util.logging.config.file=logging.properties" />
<option name="WORKING_DIRECTORY" value="$MODULE_WORKING_DIR$" />
<extension name="coverage">
<pattern>
<option name="PATTERN" value="pp.battleship.client.*"/>
<option name="ENABLED" value="true"/>
<option name="PATTERN" value="pp.battleship.client.server.*" />
<option name="ENABLED" value="true" />
</pattern>
</extension>
<method v="2">
<option name="Make" enabled="true"/>
<option name="Make" enabled="true" />
</method>
</configuration>
</component>

View File

@@ -9,7 +9,7 @@
#
# Specifies the map used by the opponent in single mode.
# Single mode is activated if this property is set.
map.opponent=maps/map2.json
#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.
@@ -23,10 +23,10 @@ map.own=maps/map1.json
# 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, 3,\
2, 4,\
2, 5,\
2, 8
robot.targets=2, 0,\
2, 1,\
2, 2,\
2, 3
#
# Delay in milliseconds between each shot fired by the RobotClient.
robot.delay=500

View File

@@ -0,0 +1,66 @@
{
"width": 10,
"height": 10,
"ships": [
{
"length": 4,
"x": 2,
"y": 8,
"rot": "RIGHT"
},
{
"length": 3,
"x": 2,
"y": 5,
"rot": "DOWN"
},
{
"length": 3,
"x": 5,
"y": 6,
"rot": "RIGHT"
},
{
"length": 2,
"x": 4,
"y": 4,
"rot": "RIGHT"
},
{
"length": 2,
"x": 7,
"y": 4,
"rot": "RIGHT"
},
{
"length": 2,
"x": 7,
"y": 4,
"rot": "RIGHT"
},
{
"length": 1,
"x": 6,
"y": 2,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 2,
"rot": "RIGHT"
},
{
"length": 1,
"x": 6,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 0,
"rot": "RIGHT"
}
]
}

View File

@@ -0,0 +1,66 @@
{
"width": 10,
"height": 10,
"ships": [
{
"length": 1,
"x": 9,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 9,
"y": 1,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 0,
"rot": "RIGHT"
},
{
"length": 1,
"x": 8,
"y": 1,
"rot": "RIGHT"
},
{
"length": 2,
"x": 8,
"y": 2,
"rot": "RIGHT"
},
{
"length": 2,
"x": 6,
"y": 0,
"rot": "RIGHT"
},
{
"length": 2,
"x": 6,
"y": 1,
"rot": "RIGHT"
},
{
"length": 3,
"x": 5,
"y": 2,
"rot": "RIGHT"
},
{
"length": 3,
"x": 7,
"y": 3,
"rot": "RIGHT"
},
{
"length": 4,
"x": 6,
"y": 4,
"rot": "RIGHT"
}
]
}

View File

@@ -1,242 +1,95 @@
package pp.battleship.client;
import com.jme3.app.Application;
import com.jme3.audio.AudioData.DataType;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource.Status;
import pp.battleship.notification.Music;
import pp.battleship.notification.MusicEvent;
import pp.battleship.notification.GameEventListener;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
public class BackgroundMusic implements GameEventListener {
public class BackgroundMusic {
private static final String VOLUME_PREF = "volume";
private static final String MUSIC_ENABLED_PREF = "musicEnabled";
private static final Preferences PREFS = Preferences.userNodeForPackage(BackgroundMusic.class);
static final Logger LOGGER = System.getLogger(BackgroundMusic.class.getName());
private static final String MENU_MUSIC = "Music/MainMenu/Dark_Intro.ogg";
private static final String BATTLE_MUSIC = "Music/BattleTheme/boss_battle_#2_metal_loop.wav";
private static final String GAME_OVER_MUSIC_L = "Music/GameOver/Lose/Lose.ogg";
private static final String GAME_OVER_MUSIC_V = "Music/GameOver/Victory/Victory.wav";
private final AudioNode menuMusic;
private final AudioNode battleMusic;
private final AudioNode gameOverMusicL;
private final AudioNode gameOverMusicV;
private String lastNodePlayed;
private Preferences prefs = Preferences.userNodeForPackage(BackgroundMusic.class);
private final AudioNode backgroundMusic;
private boolean musicEnabled;
private float volume;
private final BattleshipApp app;
/**
* Initializes and controls the BackgroundMusic
*
* @param app The main Application
* Constuctor for the BackgroundMusic
* @param app for synchronising and saving data with the application
* @param musicFilePath the filepath for the BackgroundMusic
*/
public BackgroundMusic(BattleshipApp app) {
this.volume = PREFS.getFloat(VOLUME_PREF, 1.0f);
this.musicEnabled = PREFS.getBoolean(MUSIC_ENABLED_PREF, true);
this.app = app;
public BackgroundMusic(Application app, String musicFilePath) {
this.volume = prefs.getFloat(VOLUME_PREF, 1.0f);
this.musicEnabled = prefs.getBoolean(MUSIC_ENABLED_PREF, true);
menuMusic = createAudioNode(MENU_MUSIC);
battleMusic = createAudioNode(BATTLE_MUSIC);
gameOverMusicL = createAudioNode(GAME_OVER_MUSIC_L);
gameOverMusicV = createAudioNode(GAME_OVER_MUSIC_V);
stop(battleMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
backgroundMusic = new AudioNode(app.getAssetManager(), musicFilePath, DataType.Stream);
backgroundMusic.setLooping(true);
backgroundMusic.setPositional(false);
backgroundMusic.setVolume(1.0f);
lastNodePlayed = menuMusic.getName();
if(musicEnabled) {
play(menuMusic);
if (musicEnabled) {
play();
}
}
/**
* This method will be used to create the audio node containing the music
*
* @param musicFilePath the file path to the music
* @return the created audio node
* Checks if the condition is met to play BackgroundMusic
*/
private AudioNode createAudioNode(String musicFilePath) {
AudioNode audioNode = new AudioNode(app.getAssetManager(), musicFilePath, DataType.Stream);
audioNode.setVolume(volume * app.getMainVolumeControl().getMainVolume());
audioNode.setPositional(false);
audioNode.setLooping(true);
audioNode.setName(musicFilePath);
return audioNode;
}
/**
* sets the give audio node to play
*
* @param audioNode the audio node which should start to play
*/
public void play(AudioNode audioNode) {
if (musicEnabled && (audioNode.getStatus() == Status.Stopped || audioNode.getStatus() == Status.Paused)) {
audioNode.play();
lastNodePlayed = audioNode.getName();
public void play() {
if (musicEnabled && (backgroundMusic.getStatus() == Status.Stopped || backgroundMusic.getStatus() == Status.Paused)) {
backgroundMusic.play();
}
}
/**
* stops the given audio node from playing
*
* @param audioNode the audio node to be stopped
* function to stop the BackgroundMusic
*/
public void stop(AudioNode audioNode) {
if (audioNode.getStatus() == Status.Playing) {
audioNode.stop();
public void stop() {
if (backgroundMusic.getStatus() == Status.Playing) {
backgroundMusic.stop();
}
}
/**
* pauses the given audi node
*
* @param audioNode the audio node to be paused
* function to toggle the Backgroundmusic also sets the volume to the previous session
*/
public void pause(AudioNode audioNode) {
if (audioNode.getStatus() == Status.Playing) {
audioNode.pause();
}
}
/**
* Toggle Method to control the music to switch it on or off
*/
public void toggleMusic() {
public void toogleMusic() {
this.musicEnabled = !this.musicEnabled;
if (musicEnabled) {
switch (lastNodePlayed){
case MENU_MUSIC:
play(menuMusic);
break;
case BATTLE_MUSIC:
play(battleMusic);
break;
case GAME_OVER_MUSIC_L:
play(gameOverMusicL);
break;
case GAME_OVER_MUSIC_V:
play(gameOverMusicV);
break;
}
play();
} else {
pause(menuMusic);
pause(battleMusic);
pause(gameOverMusicL);
pause(gameOverMusicV);
stop();
}
PREFS.putBoolean(MUSIC_ENABLED_PREF, musicEnabled);
prefs.putFloat(VOLUME_PREF, volume);
}
/**
* this method is used when the main volume changes
*/
public void setVolume(){
setVolume(PREFS.getFloat(VOLUME_PREF, 1.0f));
}
/**
* Method to set the volume for the music
*
* @param volume float to transfer the new volume
* Setter for the Volume also safes the volume from the previous session
* @param volume variable for the volume of the BackgroundMusic
*/
public void setVolume(float volume) {
this.volume = volume;
float mainVolume = app.getMainVolumeControl().getMainVolume();
menuMusic.setVolume(volume * mainVolume);
battleMusic.setVolume(volume * mainVolume);
gameOverMusicL.setVolume(volume * mainVolume);
gameOverMusicV.setVolume(volume * mainVolume);
PREFS.putFloat(VOLUME_PREF, volume);
backgroundMusic.setVolume(volume);
prefs.putFloat(VOLUME_PREF, volume);
}
/**
* This method retuns the volume
*
* @return the current volume as a float
* Getter for Volume
* @return a float value of the volume
*/
public float getVolume() {
return volume;
}
/**
* Returns if music should be played or not
*
* @return boolean value in music should be played
* Getter for musicEnabled
* @return if the music is enabled return true, false otherwise
*/
public boolean isMusicEnabled() {
return musicEnabled;
}
/**
* changes the music to the specified music if it isn't already playing
*
* @param music the music to play
*/
public void changeMusic(Music music) {
if(music == Music.MENU_THEME && !lastNodePlayed.equals(MENU_MUSIC)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(battleMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
play(menuMusic);
lastNodePlayed = menuMusic.getName();
} else if (music == Music.BATTLE_THEME && !lastNodePlayed.equals(BATTLE_MUSIC)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(gameOverMusicL);
stop(gameOverMusicV);
play(battleMusic);
lastNodePlayed = battleMusic.getName();
} else if (music == Music.GAME_OVER_THEME_L && !lastNodePlayed.equals(GAME_OVER_MUSIC_L)) {
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(battleMusic);
stop(gameOverMusicV);
play(gameOverMusicL);
lastNodePlayed = gameOverMusicL.getName();
} else if (music == Music.GAME_OVER_THEME_V && !lastNodePlayed.equals(GAME_OVER_MUSIC_V)){
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
stop(menuMusic);
stop(battleMusic);
stop(gameOverMusicL);
play(gameOverMusicV);
lastNodePlayed = gameOverMusicV.getName();
}
}
/**
* the method which receives the Event
*
* @param music the received Event
*/
@Override
public void receivedEvent (MusicEvent music){
LOGGER.log(Level.INFO, "Received Music change Event {0}", music.toString());
switch (music.music()){
case MENU_THEME:
changeMusic(Music.MENU_THEME);
break;
case BATTLE_THEME:
changeMusic(Music.BATTLE_THEME);
break;
case GAME_OVER_THEME_L:
changeMusic(Music.GAME_OVER_THEME_L);
break;
case GAME_OVER_THEME_V:
changeMusic(Music.GAME_OVER_THEME_V);
break;
}
}
}

View File

@@ -121,17 +121,11 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
* Listener for handling actions triggered by the Escape key.
*/
private final ActionListener escapeListener = (name, isPressed, tpf) -> escape(isPressed);
/**
* The Object which handles the background music
*
*/
private BackgroundMusic backgroundMusic;
/**
* The object that handles the main volume
*/
private MainVolume mainVolume;
static {
// Configure logging
LogManager manager = LogManager.getLogManager();
@@ -151,6 +145,7 @@ public class BattleshipApp extends SimpleApplication implements BattleshipClient
*/
public static void main(String[] args) {
new BattleshipApp().start();
}
/**
@@ -235,10 +230,7 @@ public void simpleInitApp() {
setupStates();
setupGui();
serverConnection.connect();
mainVolume = new MainVolume(this);
backgroundMusic = new BackgroundMusic(this);
logic.addListener(backgroundMusic);
backgroundMusic = new BackgroundMusic(this, "Sound/Effects/BackgroundMusic/boss_battle_#2_metal_opening.wav");
}
/**
@@ -442,20 +434,10 @@ void errorDialog(String errorMessage) {
}
/**
* this method returns the object which handles the background music
*
* @return BackgroundMusic
* Getter for the BackgroundMusic
* @return the BackgroundMusic
*/
public BackgroundMusic getBackgroundMusic(){
return backgroundMusic;
}
/**
* this method returns the object which handles the main volume
*
* @return an object of MainVolume
*/
public MainVolume getMainVolumeControl(){
return mainVolume;
}
}

View File

@@ -14,7 +14,6 @@
import com.jme3.asset.AssetNotFoundException;
import com.jme3.audio.AudioData;
import com.jme3.audio.AudioNode;
import com.jme3.audio.AudioSource;
import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.SoundEvent;
@@ -28,19 +27,14 @@
* An application state that plays sounds.
*/
public class GameSound extends AbstractAppState implements GameEventListener {
static final Logger LOGGER = System.getLogger(GameSound.class.getName());
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 static final String SOUND_VOLUME_PREF = "volume";
private float volume;
private AudioNode splashSound;
private AudioNode shipDestroyedSound;
private AudioNode explosionSound;
private AudioNode rocketSound;
private BattleshipApp app;
private AudioNode missileLaunch;
/**
* Checks if sound is enabled in the preferences.
@@ -51,13 +45,6 @@ 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)}
@@ -82,13 +69,10 @@ public void setEnabled(boolean enabled) {
@Override
public void initialize(AppStateManager stateManager, Application app) {
super.initialize(stateManager, app);
this.app = (BattleshipApp) 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
rocketSound = loadSound(app, "Sound/Effects/rocket.wav");
volume = PREFERENCES.getFloat(SOUND_VOLUME_PREF, 1.0f);
missileLaunch = loadSound(app, "Sound/Effects/missileLaunch.wav");
}
/**
@@ -104,13 +88,19 @@ private AudioNode loadSound(Application app, String name) {
sound.setLooping(false);
sound.setPositional(false);
return sound;
}
catch (AssetLoadException | AssetNotFoundException ex) {
} catch (AssetLoadException | AssetNotFoundException ex) {
LOGGER.log(Level.ERROR, ex.getMessage(), ex);
}
return null;
}
/**
* Plays the missile launch sound effect.
*/
public void missileLaunch() {
missileLaunch.playInstance();
}
/**
* Plays the splash sound effect.
*/
@@ -136,64 +126,16 @@ public void shipDestroyed() {
}
/**
* Plays sound effect when a rocket starts
* Checks the according case for the soundeffect
* @param event the received event
*/
public void rocket() {
if (isEnabled() && rocketSound != null)
rocketSound.playInstance();
}
/**
* this method sets the sound volume of the sounds
*
* @param volume the volume to be set to
*/
public void setSoundVolume(float volume) {
float mainVolume = app.getMainVolumeControl().getMainVolume();
float calculatedVolume = volume * mainVolume;
shipDestroyedSound.setVolume(calculatedVolume);
splashSound.setVolume(calculatedVolume);
explosionSound.setVolume(calculatedVolume);
this.volume = volume;
PREFERENCES.putFloat(SOUND_VOLUME_PREF, volume);
}
/**
* this method will be used if the main volume changes
*/
public void setSoundVolume() {
float mainVolume = app.getMainVolumeControl().getMainVolume();
shipDestroyedSound.setVolume(volume * mainVolume);
splashSound.setVolume(volume * mainVolume);
explosionSound.setVolume(volume * mainVolume);
rocketSound.setVolume(volume * mainVolume);
PREFERENCES.putFloat(SOUND_VOLUME_PREF, volume);
}
/**
* this method returns the sound
*
* @return
*/
public float getVolume(){
return volume;
}
@Override
public void receivedEvent(SoundEvent event) {
switch (event.sound()) {
case EXPLOSION :
explosion();
break;
case SPLASH :
splash();
break;
case DESTROYED_SHIP:
shipDestroyed();
break;
case ROCKET_FIRED:
rocket();
break;
case EXPLOSION -> explosion();
case SPLASH -> splash();
case DESTROYED_SHIP -> shipDestroyed();
case MISSILE_LAUNCH -> missileLaunch();
}
}
}

View File

@@ -1,32 +0,0 @@
package pp.battleship.client;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.prefs.Preferences;
public class MainVolume {
private static final Preferences PREFS = Preferences.userNodeForPackage(MainVolume.class);
private static final String MAIN_VOLUME_PREFS = "MainVolume";
static final Logger LOGGER = System.getLogger(MainVolume.class.getName());
private float mainVolume;
private BattleshipApp app;
public MainVolume(BattleshipApp app) {
this.mainVolume = PREFS.getFloat(MAIN_VOLUME_PREFS, 1.0f);
this.app = app;
}
public void setMainVolume(float mainVolume) {
LOGGER.log(Level.DEBUG, "setMainVolume: mainVolume = {0}", mainVolume);
app.getBackgroundMusic().setVolume();
app.getStateManager().getState(GameSound.class).setSoundVolume();
this.mainVolume = mainVolume;
PREFS.putFloat(MAIN_VOLUME_PREFS, mainVolume);
}
public float getMainVolume() {
return mainVolume;
}
}

View File

@@ -9,14 +9,15 @@
import com.simsilica.lemur.Button;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.core.VersionedReference;
import com.simsilica.lemur.style.ElementId;
import pp.dialog.Dialog;
import pp.dialog.StateCheckboxModel;
import pp.dialog.TextInputDialog;
import com.simsilica.lemur.DefaultRangedValueModel;
import com.simsilica.lemur.Slider;
import com.simsilica.lemur.core.VersionedReference;
import java.io.File;
import java.io.IOException;
@@ -36,14 +37,7 @@ class Menu extends Dialog {
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 static final double SLIDER_DELTA = 0.1;
private static final double SLIDER_MIN_VALUE = 0.0;
private static final double SLIDER_MAX_VALUE = 2.0;
private final VersionedReference<Double> volumeRef;
private final VersionedReference<Double> soundVolumeRef;
private final VersionedReference<Double> mainVolumeRef;
/**
* Constructs the Menu dialog for the Battleship application.
@@ -54,26 +48,20 @@ public Menu(BattleshipApp app) {
super(app.getDialogManager());
this.app = app;
addChild(new Label(lookup("battleship.name"), new ElementId("header"))); //NON-NLS
addChild(new Label(lookup("menu.main.volume"), new ElementId("label")));
Slider mainVolumeSlider = createSlider(app.getMainVolumeControl().getMainVolume());
addChild(mainVolumeSlider);
mainVolumeRef = mainVolumeSlider.getModel().createReference();
addChild(new Label(lookup("menu.sound.volume"), new ElementId("label")));
addChild(new Checkbox(lookup("menu.sound-enabled"),
new StateCheckboxModel(app, GameSound.class)));
Slider soundSlider = createSlider(app.getStateManager().getState(GameSound.class).getVolume());
addChild(soundSlider);
soundVolumeRef = soundSlider.getModel().createReference();
addChild(new Label(lookup("menu.volume"), new ElementId("label")));
Checkbox musicToggle = new Checkbox(lookup("menu.music.toggle"));
musicToggle.setChecked(app.getBackgroundMusic().isMusicEnabled());
musicToggle.addClickCommands(s -> toggleMusic());
addChild(musicToggle);
Slider volumeSlider = createSlider(app.getBackgroundMusic().getVolume());
Slider volumeSlider = new Slider();
volumeSlider.setModel(new DefaultRangedValueModel(0.0 , 2.0, app.getBackgroundMusic().getVolume()));
volumeSlider.setDelta(0.1);
addChild(volumeSlider);
volumeRef = volumeSlider.getModel().createReference();
addChild(loadButton)
@@ -88,69 +76,30 @@ public Menu(BattleshipApp app) {
}
/**
* this method creates a slider to be used in the menu
*
* @param relativePosition the position of the regulator on the slider
* @return the creates slider
* Updates the volume if there is a change and adjusts it accordingly.
* @param tmp unused time parameter
*/
private Slider createSlider(double relativePosition){
Slider slider = new Slider();
slider.setModel(new DefaultRangedValueModel(SLIDER_MIN_VALUE, SLIDER_MAX_VALUE, relativePosition));
slider.setDelta(SLIDER_DELTA);
return slider;
}
/**
* this method is used update the volume when there is a change in the slider
* @param tpf time per frame
*/
@Override
public void update(float tpf){
if(volumeRef.update()){
public void update(float tmp){
if(volumeRef.update()) {
double newVolume = volumeRef.get();
adjustMusicVolume(newVolume);
}
else if (soundVolumeRef.update()) {
double newSoundVolume = soundVolumeRef.get();
adjustSoundVolume(newSoundVolume);
} else if (mainVolumeRef.update()) {
double newMainVolume = mainVolumeRef.get();
adjustMainVolume(newMainVolume);
adjustVolume(newVolume);
}
}
/**
* this method adjusts the main volume
*
* @param newVolume the volume to be set as main volume
* Adjusts the background music volume to the specified value.
* @param newVolume the new volume level
*/
private void adjustMainVolume(double newVolume) {
app.getMainVolumeControl().setMainVolume((float) newVolume);
private void adjustVolume(double newVolume) {
app.getBackgroundMusic().setVolume((float) newVolume);
}
/**
* this method adjust the volume for the background music
*
* @param volume is the double value of the volume
* Toggles the background music on or off.
*/
private void adjustMusicVolume(double volume) {
app.getBackgroundMusic().setVolume((float) volume);
}
/**
* this method adjusts the volume for the sound
*
* @param volume is a double value of the sound volume
*/
private void adjustSoundVolume(double volume) {
app.getStateManager().getState(GameSound.class).setSoundVolume((float) volume);
}
/**
* this method toggles the background music on and off
*/
private void toggleMusic() {
app.getBackgroundMusic().toggleMusic();
private void toggleMusic(){
app.getBackgroundMusic().toogleMusic();
}
/**

View File

@@ -7,10 +7,7 @@
package pp.battleship.client;
import com.simsilica.lemur.Checkbox;
import com.simsilica.lemur.Container;
import com.simsilica.lemur.Label;
import com.simsilica.lemur.TextField;
import com.simsilica.lemur.*;
import com.simsilica.lemur.component.SpringGridLayout;
import pp.battleship.client.server.BattleshipServer;
import pp.dialog.Dialog;
@@ -39,7 +36,10 @@ class NetworkDialog extends SimpleDialog {
private int portNumber;
private Future<Object> connectionFuture;
private Dialog progressDialog;
private boolean hostServer = false;
private BattleshipServer battleshipServer;
private boolean GUI = true;
private Container menu;
private boolean serverToggle = false;
/**
* Constructs a new NetworkDialog.
@@ -53,26 +53,64 @@ class NetworkDialog extends SimpleDialog {
host.setPreferredWidth(400f);
port.setSingleLine(true);
Checkbox serverHost = new Checkbox(lookup("host.own.server"));
serverHost.setChecked(false);
serverHost.addClickCommands(s -> toggleServerHost());
this.menu = new Container();
menu();
addChild(menu);
}
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);
input.addChild(serverHost);
/**
* Main Menu creation
*/
private void menu() {
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);
Button connectGame = new Button("Multiplayer");
connectGame.addClickCommands(source -> serverConnectGUI());
Checkbox serverHost = new Checkbox("Host own Game");
serverHost.addClickCommands(source -> toggleServer());
menu.addChild(serverHost);
menu.addChild(connectGame);
}
/**
* Logic for the ServerGUI
*/
private void serverConnectGUI() {
LOGGER.log(Level.INFO, "Hosting Server...");
if (GUI) {
host.setSingleLine(true);
host.setPreferredWidth(400f);
port.setSingleLine(true);
final BattleshipApp app = network.getApp();
final Container input = new Container(new SpringGridLayout());
input.addChild(new Label(lookup("host.name") + ": "));
input.addChild(host, 1);
input.addChild(new Label(lookup("port.number") + ": "));
input.addChild(port, 1);
DialogBuilder.simple(app.getDialogManager())
.setTitle(lookup("server.dialog"))
.setExtension(d -> d.addChild(input))
.setOkButton(lookup("button.connect"), d -> connect())
.setOkClose(false)
.build(this);
GUI = false;
}
}
/**
* Connect to server if serverToggle is false hosts otherwise
*/
private void connect(){
if (serverToggle) {
hostServer();
} else {
connectServer();
}
}
/**
@@ -86,54 +124,56 @@ private void connectServer() {
portNumber = Integer.parseInt(port.getText());
openProgressDialog();
connectionFuture = network.getApp().getExecutor().submit(this::initNetwork);
}
catch (NumberFormatException e) {
} catch (NumberFormatException e) {
network.getApp().errorDialog(lookup("port.must.be.integer"));
}
}
/**
* This method will start a server or just connect to one based on the boolean hostServer
* Checks if the Server is running or starts the Server and handles Exceptions *
*/
private void connect() {
if(hostServer){
startServer();
private void hostServer() {
if (battleshipServer == null) {
startServer(); // Starts the server in a new thread
try {
Thread.sleep(1000);
Thread.sleep(1000); // Wait for the server to start
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, e.getMessage(), e);
}
connectServer();
} else {
connectServer();
}
// Proceed with connecting to the server
connectServer();
}
/**
* This method starts a server in a new thread
* Toggle Method for Boolean
*/
private void toggleServer(){
serverToggle = !serverToggle;
}
/**
* Allows Client to Start a Server in a new Thread
*/
private void startServer() {
new Thread(() -> {
try{
BattleshipServer battleshipServer = new BattleshipServer(Integer.parseInt(port.getText()));
battleshipServer.run();
try {
// Initialize and run the server
battleshipServer = new BattleshipServer();
battleshipServer.run(Integer.parseInt(port.getText()));
} catch (Exception e) {
LOGGER.log(Level.ERROR, e.getMessage(), e);
LOGGER.log(Level.ERROR, e);
}
}).start();
}
private void toggleServerHost(){
hostServer = !hostServer;
}
/**
* Creates a dialog indicating that the connection is in progress.
*/
private void openProgressDialog() {
progressDialog = DialogBuilder.simple(network.getApp().getDialogManager())
.setText(lookup("label.connecting"))
.build();
.setText(lookup("label.connecting"))
.build();
progressDialog.open();
}
@@ -146,8 +186,7 @@ private Object initNetwork() {
try {
network.initNetwork(hostname, portNumber);
return null;
}
catch (Exception e) {
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@@ -162,11 +201,9 @@ public void update(float delta) {
try {
connectionFuture.get();
success();
}
catch (ExecutionException e) {
} catch (ExecutionException e) {
failure(e.getCause());
}
catch (InterruptedException e) {
} catch (InterruptedException e) {
LOGGER.log(Level.WARNING, "Interrupted!", e); //NON-NLS
Thread.currentThread().interrupt();
}

View File

@@ -1,179 +0,0 @@
package pp.battleship.client.gui;
import com.jme3.app.Application;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.model.Shot;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.util.Timer;
import java.util.TimerTask;
/**
* This class is used to handle the effects for impacts
*/
public class EffectHandler {
private final AssetManager assetManager;
static final Logger LOGGER = System.getLogger(EffectHandler.class.getName());
private Material particleMat;
/**
* the constructor is used to get the asset manager from the app
*
* @param app the main application
*/
public EffectHandler(Application app) {
assetManager = app.getAssetManager();
particleMat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
}
/**
* creates a new HitEffect
*
* @param battleshipNode the node of the ship
* @param shot the shot which triggered the effect
*/
public void createHitEffect(Node battleshipNode, Shot shot) {
createFieryEffect(battleshipNode,shot, "HitEffect", 30, 0.45f, 0.1f, -0.5f, 1f , 2f, false);
}
/**
* creates a new FireEffect
*
* @param battleshipNode the node of the ship
* @param shot the shot which triggered the effect
*/
public void createFireEffect(Node battleshipNode, Shot shot) {
createFieryEffect(battleshipNode, shot, "FireEffect", 30, 0.1f, 0.05f, -0.9f, 1f , 2f, true);
}
/**
* creates a fiery type hit effect
*
* @param battleshipNode the ship to which the effect should be attached
* @param shot the shot that triggered the effect
* @param name the name of the particle emitter
* @param numOfParticle the overall numberOfParticles
* @param startSize the start size of the particles
* @param endSize the end size of the particles
* @param gravity the gravity of the particles
* @param lowLife the lowest lifetime of a particle
* @param highLife the maximum lifetime of a particle
* @param loop if the effect should be looped
*/
public void createFieryEffect(Node battleshipNode, Shot shot, String name, int numOfParticle, float startSize, float endSize, float gravity,
float lowLife, float highLife, boolean loop) {
ParticleEmitter fieryEffect = new ParticleEmitter(name, Type.Triangle, numOfParticle);
fieryEffect.setMaterial(particleMat);
fieryEffect.setImagesX(2);
fieryEffect.setImagesY(2);
fieryEffect.setStartColor(ColorRGBA.Orange);
fieryEffect.setEndColor(ColorRGBA.Red);
fieryEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0,1,0));
fieryEffect.setStartSize(startSize);
fieryEffect.setEndSize(endSize);
fieryEffect.setGravity(0, gravity, 0);
fieryEffect.setLowLife(lowLife);
fieryEffect.setHighLife(highLife);
if(!loop) {
fieryEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
fieryEffect.setParticlesPerSec(0);
fieryEffect.emitAllParticles();
} else {
fieryEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
fieryEffect.getLocalTranslation().subtractLocal(battleshipNode.getLocalTranslation());
fieryEffect.setParticlesPerSec(10);
}
battleshipNode.attachChild(fieryEffect);
LOGGER.log(Level.DEBUG, "Created {0} at {1}", name ,fieryEffect.getLocalTranslation().toString());
fieryEffect.addControl(new EffectControl(fieryEffect, battleshipNode));
}
/**
* This method is used to create a miss effect at a certain location
*/
public ParticleEmitter createMissEffect(Shot shot) {
ParticleEmitter missEffect = new ParticleEmitter("MissEffect", Type.Triangle, 15);
missEffect.setMaterial(particleMat);
missEffect.setImagesX(2);
missEffect.setImagesY(2);
missEffect.setStartColor(ColorRGBA.Blue); // Water color
missEffect.setEndColor(ColorRGBA.Cyan);
missEffect.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
missEffect.setStartSize(0.3f);
missEffect.setEndSize(0.05f);
missEffect.setGravity(0, -0.1f, 0);
missEffect.setLowLife(0.5f);
missEffect.setHighLife(1.5f);
missEffect.setParticlesPerSec(0);
missEffect.setLocalTranslation(shot.getY() + 0.5f, 0 , shot.getX() + 0.5f);
missEffect.emitAllParticles();
missEffect.addControl(new EffectControl(missEffect));
return missEffect;
}
/**
* This inner class is used to control the effects
*/
private static class EffectControl extends AbstractControl {
private final ParticleEmitter emitter;
private final Node parentNode;
/**
* this constructor is used to when the effect should be attached to a specific node
*
* @param emitter the Particle emitter to be controlled
* @param parentNode the node to be attached
*/
public EffectControl(ParticleEmitter emitter, Node parentNode) {
this.emitter = emitter;
this.parentNode = parentNode;
}
/**
* This constructor is used when the effect shouldn't be attached to
* a specific node
*
* @param emitter the Particle emitter to be controlled
*/
public EffectControl(ParticleEmitter emitter){
this.emitter = emitter;
this.parentNode = null;
}
/**
* The method which checks if the Effect is not rendered anymore so it can be removed
*
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
if (emitter.getParticlesPerSec() == 0 && emitter.getNumVisibleParticles() == 0) {
if (parentNode != null)
parentNode.detachChild(emitter);
}
}
/**
* @param rm the RenderManager rendering the controlled Spatial (not null)
* @param vp the ViewPort being rendered (not null)
*/
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {}
}
}

View File

@@ -0,0 +1,155 @@
package pp.battleship.client.gui;
import com.jme3.asset.AssetManager;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh.Type;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.client.BattleshipApp;
import pp.battleship.model.Shot;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
/**
* Manages the visual effects for impact events such as hits and misses.
*/
public class ImpactEffectManager {
private final AssetManager assetManager;
private static final Logger LOGGER = System.getLogger(ImpactEffectManager.class.getName());
private Material particleMaterial;
private BattleshipApp app;
/**
* Constructor to initialize the asset manager via the main application.
*
* @param app The main application instance.
*/
public ImpactEffectManager(BattleshipApp app) {
this.app = app;
this.assetManager = app.getAssetManager();
this.particleMaterial = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
}
/**
* Generates a visual effect for a hit at the specified position.
*
* @param battleshipNode The node where the effect should be attached.
* @param shot The details of the shot where the hit occurred.
*/
public void triggerHitEffect(Node battleshipNode, Shot shot) {
ParticleEmitter hitEffect = createParticleEmitter("HitEffect", 50, ColorRGBA.Orange, ColorRGBA.Red, 0.45f, 0.1f, 1f, 2f);
hitEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
LOGGER.log(Level.DEBUG, "Hit effect created at position: {0}", hitEffect.getLocalTranslation().toString());
// hitEffect.emitAllParticles();
hitEffect.addControl(new EffectCleanupControl(hitEffect));
app.getRootNode().attachChild(hitEffect);
}
/**
* Creates a visual effect for a missed shot at the specified location.
*
* @param shot The details of the missed shot.
* @return The particle emitter representing the miss effect.
*/
public ParticleEmitter triggerMissEffect(Shot shot) {
ParticleEmitter missEffect = createParticleEmitter("MissEffect", 50, ColorRGBA.Blue, ColorRGBA.Cyan, 0.3f, 0.05f, 0.5f, 1.5f);
missEffect.setLocalTranslation(shot.getY() + 0.5f, 0, shot.getX() + 0.5f);
missEffect.addControl(new EffectCleanupControl(missEffect));
return missEffect;
}
/**
* Helper method to create a particle emitter with predefined parameters.
*
* @param name The name of the particle emitter.
* @param count The number of particles to emit.
* @param startColor The initial color of the particles.
* @param endColor The final color of the particles.
* @param startSize The initial size of the particles.
* @param endSize The final size of the particles.
* @param lowLife The minimum lifetime of the particles.
* @param highLife The maximum lifetime of the particles.
* @return The configured ParticleEmitter instance.
*/
private ParticleEmitter createParticleEmitter(String name, int count, ColorRGBA startColor, ColorRGBA endColor, float startSize, float endSize, float lowLife, float highLife) {
ParticleEmitter emitter = new ParticleEmitter(name, Type.Triangle, count);
emitter.setNumParticles(0);
emitter.setMaterial(particleMaterial);
emitter.setImagesX(2);
emitter.setImagesY(2);
emitter.setStartColor(startColor);
emitter.setEndColor(endColor);
emitter.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 1, 0));
emitter.setStartSize(startSize);
emitter.setEndSize(endSize);
emitter.setGravity(0, -0.5f, 0);
emitter.setLowLife(lowLife);
emitter.setHighLife(highLife);
return emitter;
}
/**
* Custom control class to handle the cleanup of particle effects once they are no longer visible.
*/
private static class EffectCleanupControl extends AbstractControl {
private final ParticleEmitter emitter;
private float currentTime = 0;
private final float duration = 3.5f;
/**
* Constructor for managing cleanup of a standalone particle emitter.
*
* @param emitter The particle emitter to manage.
*/
public EffectCleanupControl(ParticleEmitter emitter) {
this.emitter = emitter;
}
/**
* Removes the emitter when all particles are no longer visible.
*
* @param tpf Time per frame.
*/
@Override
protected void controlUpdate(float tpf) {
currentTime += tpf;
if (currentTime <= duration) {
}
if (currentTime >= 1f && currentTime <= 1.1) {
//Start
emitter.setNumParticles(50);
emitter.setParticlesPerSec(50);
}
if (currentTime >= duration){
emitter.setParticlesPerSec(0);
}
if (currentTime >= duration+1f) {
//Ende
spatial.removeFromParent();
emitter.emitAllParticles();
}
}
/**
* No custom rendering needed; particle behavior is handled in controlUpdate.
* @param rm handles rendering, and ViewPort
* @param vp defines the view where the scene is displayed.
*/
@Override
protected void controlRender(com.jme3.renderer.RenderManager rm, com.jme3.renderer.ViewPort vp) {
// No rendering-specific behavior needed for this control.
}
}
}

View File

@@ -12,7 +12,6 @@
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell;
import pp.battleship.model.Shot;
import pp.util.Position;
@@ -29,10 +28,8 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
private static final float SHOT_DEPTH = -2f;
private static final float SHIP_DEPTH = 0f;
private static final float INDENT = 4f;
private static final float SHELL_DEPTH = 8f;
private static final float SHELL_SIZE = 0.75f;
private static final float SHELL_CENTERED_IN_MAP_GRID = 0.0625f;
private static final float SHELL_DEPTH = 8f;
// Colors used for different visual elements
private static final ColorRGBA HIT_COLOR = ColorRGBA.Red;
@@ -44,8 +41,6 @@ class MapViewSynchronizer extends ShipMapSynchronizer {
// The MapView associated with this synchronizer
private final MapView view;
static final Logger LOGGER = System.getLogger(MapViewSynchronizer.class.getName());
/**
* Constructs a new MapViewSynchronizer for the given MapView.
* Initializes the synchronizer and adds existing elements from the model to the view.
@@ -67,7 +62,6 @@ public MapViewSynchronizer(MapView view) {
*/
@Override
public Spatial visit(Shot shot) {
LOGGER.log(Logger.Level.DEBUG, "Visiting " + 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);
@@ -120,24 +114,21 @@ public Spatial visit(Battleship ship) {
}
/**
* this method will create a representation of a shell in the map
*
* @param shell the Shell element to visit
* @return the node the representation is attached to
* Creates a visual representation (Spatial) for the given shell, attaching a control for its behavior.
* @param shell the shell to visit and visualize
* @return the constructed shell node (Spatial)
*/
@Override
public Spatial visit(Shell shell) {
LOGGER.log(Logger.Level.DEBUG, "Visiting {0}", shell);
final Node shellNode = new Node("shell");
final Position p1 = view.modelToView(shell.getX(), shell.getY());
final Position p2 = view.modelToView(shell.getX() + SHELL_SIZE, shell.getY() + SHELL_SIZE);
final Position startPosition = view.modelToView(shell.getCurrentPosition().x,shell.getCurrentPosition().z);
final Position startPosition = view.modelToView(SHELL_CENTERED_IN_MAP_GRID, SHELL_CENTERED_IN_MAP_GRID);
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(),startPosition.getY(), SHELL_DEPTH, 30, 30, ColorRGBA.Black));
shellNode.attachChild(view.getApp().getDraw().makeRectangle(startPosition.getX(), startPosition.getY(), SHELL_DEPTH, p2.getX() - p1.getX(), p2.getY() - p1.getY(), ColorRGBA.Black));
shellNode.setLocalTranslation(startPosition.getX(), startPosition.getY(), SHELL_DEPTH);
shellNode.addControl(new ShellMapControl(p1, view.getApp(), new IntPoint(shell.getX(), shell.getY())));
shellNode.addControl(new ShellControl(shell, this.view, shell.getLogic()));
return shellNode;
}
/**
@@ -151,7 +142,6 @@ public Spatial visit(Shell shell) {
* @return a Geometry representing the line
*/
private Geometry shipLine(float x1, float y1, float x2, float y2, ColorRGBA color) {
LOGGER.log(Logger.Level.DEBUG, "created ship line");
return view.getApp().getDraw().makeFatLine(x1, y1, x2, y2, SHIP_DEPTH, color, SHIP_LINE_WIDTH);
}
}

View File

@@ -8,11 +8,14 @@
package pp.battleship.client.gui;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.renderer.queue.RenderQueue;
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.*;
@@ -28,18 +31,21 @@
*/
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";
private static final String UBOAT = "Models/UBoat/14084_WWII_Ship_German_Type_II_U-boat_v2_L1.obj"; //NON-NLS
private static final String BATTLE_SHIP_MODERN = "Models/BattleShipModern/Destroyer.j3o";
private static final String BATTLE_SHIP_MODERN_TEXTURE = "Models/BattleShipModern/BattleshipC.jpg";
private static final String PATROL_BOAT = "Models/PatrolBoat/12219_boat_v2_L2.obj";
private static final String SHELL_ROCKET = "Models/Rocket/Rocket.obj";
private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o";//NON-NLS
private static final String DESTROYER = "Models/Destroyer/Destroyer.j3o";
private static final String FERRY = "Models/Ferry/13922_Staten_Island_Ferry_V1_l1.obj";
private static final String SMALL = "Models/Small/10634_SpeedBoat_v01_LOD3.obj";
private static final String BOMB = "Models/Bomb/BombGBU.j3o";
private static final String COLOR = "Color"; //NON-NLS
private static final String SHIP = "ship"; //NON-NLS
private static final String SHELL = "shell";
private final EffectHandler effectHandler;
private static final String SHOT = "shot"; //NON-NLS
private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
private static final ColorRGBA SPLASH_COLOR = new ColorRGBA(0f, 0f, 1f, 0.4f);
private static final ColorRGBA HIT_COLOR = new ColorRGBA(1f, 0f, 0f, 0.4f);
private final ShipMap map;
private final BattleshipApp app;
private ImpactEffectManager effectHandler;
/**
* Constructs a {@code SeaSynchronizer} object with the specified application, root node, and ship map.
@@ -52,7 +58,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
super(app.getGameLogic().getOwnMap(), root);
this.app = app;
this.map = map;
effectHandler = new EffectHandler(app);
effectHandler = new ImpactEffectManager(app);
addExisting();
}
@@ -66,7 +72,7 @@ public SeaSynchronizer(BattleshipApp app, Node root, ShipMap map) {
*/
@Override
public Spatial visit(Shot shot) {
return shot.isHit() ? handleHit(shot) : effectHandler.createMissEffect(shot);
return shot.isHit() ? handleHit(shot) : effectHandler.triggerMissEffect(shot);
}
/**
@@ -81,12 +87,34 @@ private Spatial handleHit(Shot shot) {
final Battleship ship = requireNonNull(map.findShipAt(shot), "Missing ship");
final Node shipNode = requireNonNull((Node) getSpatial(ship), "Missing ship node");
effectHandler.createHitEffect(shipNode, shot);
effectHandler.createFireEffect(shipNode, shot);
System.out.println(shot.getX() + " " + shot.getY());
effectHandler.triggerHitEffect(shipNode, shot);
return null;
}
/**
* Creates a cylinder geometry representing the specified shot.
* The appearance of the cylinder depends on whether the shot is a hit or a miss.
*
* @param shot the shot to be represented
* @return the geometry representing the shot
*/
private Geometry createCylinder(Shot shot) {
final ColorRGBA color = shot.isHit() ? HIT_COLOR : SPLASH_COLOR;
final float height = shot.isHit() ? 1.2f : 0.1f;
final Cylinder cylinder = new Cylinder(2, 20, 0.45f, height, true);
final Geometry geometry = new Geometry(SHOT, cylinder);
geometry.setMaterial(createColoredMaterial(color));
geometry.rotate(HALF_PI, 0f, 0f);
geometry.setLocalTranslation(shot.getY() + 0.5f, 0f, shot.getX() + 0.5f);
return geometry;
}
/**
* Visits a {@link Battleship} and creates a graphical representation of it.
* The representation is either a 3D model or a simple box depending on the
@@ -107,57 +135,78 @@ public Spatial visit(Battleship ship) {
return node;
}
/**
* Visits a shell and creates a graphical representation
*
* @param shell the Shell element to visit
* @return the node containing the graphical representation
*/
@Override
public Spatial visit(Shell shell){
final Node node = new Node(SHELL);
node.attachChild(createRocket());
public Spatial visit(Shell shell) {
final Spatial bombModel = app.getAssetManager().loadModel(BOMB);
final float x = shell.getY();
final float z = shell.getX();
// Apply transformations to the bomb model (scale, rotate, etc. if needed)
bombModel.scale(0.05f);
bombModel.rotate(-HALF_PI, 0f, 0f);
node.setLocalTranslation(x + 0.5f, 10f, z + 0.5f);
ShellControl shellControl = new ShellControl(shell, app);
node.addControl(shellControl);
return node;
}
// Set the position of the bomb at the shell's current position
bombModel.setLocalTranslation(shell.getCurrentPosition());
/**
* creates the spatial representation of a rocket
*
* @return a spatial the rocket
*/
private Spatial createRocket() {
final Spatial model = app.getAssetManager().loadModel(SHELL_ROCKET);
// Add a control to animate the bomb's movement (similar to the previous cylinder animation)
bombModel.addControl(new ShellControl(shell,shell.getLogic()));
model.rotate(PI, 0f, 0f);
model.scale(0.002f);
model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, 0, 0);
return model;
return bombModel;
}
/**
* 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.
* The representation is a detailed model based on the length of the ship.
*
* @param ship the battleship to be represented
* @return the spatial representing the battleship
*/
private Spatial createShip(Battleship ship) {
return switch (ship.getLength()) {
case 1 -> createPatrolBoat(ship);
case 2 -> createModernBattleship(ship);
case 3 -> createUBoat(ship);
case 4 -> createBattleship(ship);
default -> throw new IllegalArgumentException("Ship length must be between 1 and 4 units long");
};
// return ship.getLength() == 4 ? createBattleship(ship) : createBox(ship);
if (ship.getLength() == 4) {
return createBattleship(ship);
} else if (ship.getLength() == 3) {
return createBigShip(ship);
} else if (ship.getLength() == 2) {
return createMediumShip(ship);
} else if (ship.getLength() == 1) {
return createSmallShip(ship);
}
return createBattleship(ship);
}
/**
* Creates a simple box to represent a battleship that is not of the "King George V" type.
*
* @param ship the battleship to be represented
* @return the geometry representing the battleship as a box
*/
private Spatial createBox(Battleship ship) {
final Box box = new Box(0.5f * (ship.getMaxY() - ship.getMinY()) + 0.3f,
0.3f,
0.5f * (ship.getMaxX() - ship.getMinX()) + 0.3f);
final Geometry geometry = new Geometry(SHIP, box);
geometry.setMaterial(createColoredMaterial(BOX_COLOR));
geometry.setShadowMode(ShadowMode.CastAndReceive);
return geometry;
}
/**
* Creates a new {@link Material} with the specified color.
* If the color includes transparency (i.e., alpha value less than 1),
* the material's render state is set to use alpha blending, allowing for
* semi-transparent rendering.
*
* @param color the {@link ColorRGBA} to be applied to the material. If the alpha value
* of the color is less than 1, the material will support transparency.
* @return a {@link Material} instance configured with the specified color and,
* if necessary, alpha blending enabled.
*/
private Material createColoredMaterial(ColorRGBA color) {
final Material material = new Material(app.getAssetManager(), UNSHADED);
if (color.getAlpha() < 1f)
material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);
material.setColor(COLOR, color);
return material;
}
/**
@@ -177,58 +226,47 @@ private Spatial createBattleship(Battleship ship) {
}
/**
* creates a detailed 3D model to represent an UBoat
*
* @param ship the ship to be represented
* @return the spatial representing the Uboat
* Creates a detailed 3D model to represent a "Big Ship"
* @param ship The Battleship object with position and rotation info.
* @return the spatial representing the "Big Ship" battleship
*/
private Spatial createUBoat(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(UBOAT);
private Spatial createBigShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(DESTROYER);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.5f);
model.move(0,0.4f,0);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
model.scale(0.0001f);
model.setShadowMode(ShadowMode.CastAndReceive);
model.move(0, -0.3f, 0);
return model;
}
/**
* creates a detailed 3D model to represent the modern battleship
*
* @param ship the ship to be represented
* @return the spatial representing the Modern Battleship
* Creates a detailed 3D model to represent a "Medium Ship"
* @param ship The Battleship object with position and rotation info.
* @return the spatial representing the "Medium Ship" battleship
*/
private Spatial createModernBattleship(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(BATTLE_SHIP_MODERN);
Material mat = new Material(app.getAssetManager(), UNSHADED);
mat.setTexture("ColorMap", app.getAssetManager().loadTexture(BATTLE_SHIP_MODERN_TEXTURE));
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Off);
model.setMaterial(mat);
model.setQueueBucket(RenderQueue.Bucket.Opaque);
private Spatial createMediumShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(FERRY);
model.move(0,0.25f,0);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.08f);
model.setLocalTranslation(0f, 0.2f, 0f);
model.scale(0.00025f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;
}
/**
* creates a detailed 3D model to represent the patrol boat
*
* @param ship the ship to be represented
* @return the spatial representing the patrol boat
* Creates a detailed 3D model to represent a "Small Ship"
* @param ship The Battleship object with position and rotation info.
* @return the spatial representing the "Small Ship" battleship
*/
private Spatial createPatrolBoat(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(PATROL_BOAT);
private Spatial createSmallShip(Battleship ship) {
final Spatial model = app.getAssetManager().loadModel(SMALL);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()), 0f);
model.scale(0.0005f);
model.rotate(-HALF_PI, calculateRotationAngle(ship.getRot()) + HALF_PI, 0f);
model.scale(0.0009f);
model.setShadowMode(ShadowMode.CastAndReceive);
return model;

View File

@@ -1,65 +1,96 @@
////////////////////////////////////////
// Programming project code
// UniBw M, 2022, 2023, 2024
// www.unibw.de/inf2
// (c) Mark Minas (mark.minas@unibw.de)
////////////////////////////////////////
package pp.battleship.client.gui;
import com.jme3.math.Quaternion;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.control.AbstractControl;
import pp.battleship.client.BattleshipApp;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.game.client.ClientGameLogic;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.model.Shell;
import java.lang.System.Logger;
import pp.battleship.notification.Sound;
import pp.util.Position;
/**
* This class controls a 3D representation of a shell
* 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.
*/
public class ShellControl extends AbstractControl {
class ShellControl extends AbstractControl {
private final Shell shell;
private final BattleshipApp app;
private static final float MOVE_SPEED = 8.0f;
static final Logger LOGGER = System.getLogger(ShellControl.class.getName());
private MapView view;
private final ClientGameLogic logic;
private final EffectMessage msg;
private boolean hasPlayedSound = false;
/**
* Constructor to create a new ShellControl object
* Constructs a new ShipControl instance for the specified Battleship.
* The ship's orientation determines the axis of rotation, while its length influences
* the cycle duration and amplitude of the oscillation.
*
* @param shell the shell to be displayed
* @param app the main application
* @param shell the Battleship object to control
*/
public ShellControl(Shell shell, BattleshipApp app) {
public ShellControl(Shell shell, ClientGameLogic clientGameLogic) {
this.shell = shell;
this.app = app;
this.logic = clientGameLogic;
this.msg = shell.getMsg();
}
/**
* this method moves the representation towards it destination
* and deletes it if it reaches its target
* Initializes ShellControl with a shell, map view, and game logic.
* @param shell the shell to be controlled
* @param view the map view to display the shell
* @param clientGameLogic the game logic instance
*/
public ShellControl(Shell shell, MapView view, ClientGameLogic clientGameLogic) {
this.shell = shell;
this.view = view;
this.logic = clientGameLogic;
this.msg = shell.getMsg();
}
/**
* Updates the ship's pitch oscillation each frame. The ship's pitch is adjusted
* to create a continuous tilting motion, simulating the effect of waves.
*
* @param tpf time per frame (in seconds)
* @param tpf time per frame (in seconds), used to calculate the new pitch angle
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(0, -MOVE_SPEED * tpf, 0);
spatial.rotate(0f, 0.05f, 0f);
//LOGGER.log(System.Logger.Level.DEBUG, "moved rocket {0}", spatial.getLocalTranslation().getY());
if (spatial.getLocalTranslation().getY() <= 1.5){
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(new IntPoint(shell.getX(), shell.getY())));
if (spatial == null)
return;
if (shell.isFinished() && !hasPlayedSound) {
if (!msg.getShot().isHit())
logic.playSound(Sound.SPLASH);
else if (msg.getDestroyedShip() == null)
logic.playSound(Sound.EXPLOSION);
else
logic.playSound(Sound.DESTROYED_SHIP);
hasPlayedSound = true;
}
if (view == null) {
shell.updatePosition(tpf);
spatial.setLocalTranslation(shell.getCurrentPosition());
} else {
shell.updatePosition(tpf);
Position pos2d = view.modelToView(shell.getCurrentPosition().x - 0.3f, shell.getCurrentPosition().z - 0.3f);
spatial.setLocalTranslation(pos2d.getY(), pos2d.getX(), 0);
}
}
/**
* 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)
* Called during rendering, but no operations are needed as this control only affects spatial transformation.
* @param rm the RenderManager rendering the spatial
* @param vp the ViewPort being rendered
*/
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
// No rendering logic is needed for this control
}
}

View File

@@ -1,63 +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.client.BattleshipApp;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.model.IntPoint;
import pp.util.Position;
/**
* This class controls a ShellMap element
*/
public class ShellMapControl extends AbstractControl {
private final Position position;
private final IntPoint pos;
private static final Vector3f VECTOR = new Vector3f();
private final BattleshipApp app;
/**
* constructs a new ShellMapControl object
*
* @param position the position where the shell should move to on the map
* @param app the main application
* @param pos the position the then to render shot goes to
*/
public ShellMapControl(Position position, BattleshipApp app, IntPoint pos) {
super();
this.position = position;
this.pos = pos;
this.app = app;
VECTOR.set(new Vector3f(position.getX(), position.getY(), 0));
}
/**
* this method moves the shell representation to its correct spot and removes it after
* it arrived at its destination
*
* @param tpf time per frame (in seconds)
*/
@Override
protected void controlUpdate(float tpf) {
spatial.move(VECTOR.mult(tpf));
if (spatial.getLocalTranslation().getX() >= position.getX() && spatial.getLocalTranslation().getY() >= position.getY()) {
spatial.getParent().detachChild(spatial);
app.getGameLogic().send(new AnimationEndMessage(pos));
}
}
/**
* 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) {
}
}

View File

@@ -17,6 +17,7 @@
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import static pp.util.FloatMath.DEG_TO_RAD;
import static pp.util.FloatMath.TWO_PI;
import static pp.util.FloatMath.sin;
@@ -46,26 +47,17 @@ class ShipControl extends AbstractControl {
*/
private final Quaternion pitch = new Quaternion();
/**
* the speed at which ships sink
*/
private static final float SINKING_SPEED = -0.05f;
/**
* the threshold when ships should be removed from the scene if they sink below the value
*/
private static final float SHIP_SINKING_REMOVE_THRESHOLD = -0.6f;
/**
* The current time within the oscillation cycle, used to calculate the ship's pitch angle.
*/
private float time;
/**
* The ship to be controlled
* The current Ship
*/
private final Battleship battleship;
/**
* Logger for logging messages related to ShipControl operations.
*/
private final Battleship battleship;
static final Logger LOGGER = System.getLogger(ShipControl.class.getName());
/**
@@ -77,7 +69,6 @@ class ShipControl extends AbstractControl {
*/
public ShipControl(Battleship ship) {
battleship = ship;
// Determine the axis of rotation based on the ship's orientation
axis = switch (ship.getRot()) {
case LEFT, RIGHT -> Vector3f.UNIT_X;
@@ -92,7 +83,6 @@ public ShipControl(Battleship ship) {
/**
* 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.
* And lets the ship sink if it is destroyed and removes it from the scene when it has completely sunk
*
* @param tpf time per frame (in seconds), used to calculate the new pitch angle
*/
@@ -101,25 +91,23 @@ protected void controlUpdate(float tpf) {
// If spatial is null, do nothing
if (spatial == null) return;
if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= SHIP_SINKING_REMOVE_THRESHOLD) {
// Update the time within the oscillation cycle
time = (time + tpf) % cycle;
if (battleship.isDestroyed() && spatial.getLocalTranslation().getY() <= -0.6f) {
LOGGER.log(Level.INFO, "Ship removed {0}", spatial.getName());
spatial.getParent().detachChild(spatial);
} else if (battleship.isDestroyed()) {
spatial.move(0, SINKING_SPEED * tpf, 0);
spatial.move(0, -0.05f * tpf, 0);
} else {
// 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);
}
spatial.setLocalRotation(pitch);
}
/**

View File

@@ -7,22 +7,19 @@
package pp.battleship.client.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.*;
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.AnimationEndMessage;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.*;
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;
@@ -43,9 +40,6 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
private static final Logger LOGGER = System.getLogger(BattleshipServer.class.getName());
private static final File CONFIG_FILE = new File("server.properties");
private static int port;
private final BattleshipConfig config = new BattleshipConfig();
private Server myServer;
private final ServerGameLogic logic;
private final BlockingQueue<ReceivedMessage> pendingMessages = new LinkedBlockingQueue<>();
@@ -62,23 +56,32 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
}
}
/**
* Creates the server.
*/
public BattleshipServer(int port) {
public BattleshipServer() {
BattleshipConfig config = new BattleshipConfig();
config.readFromIfExists(CONFIG_FILE);
BattleshipServer.port = port;
LOGGER.log(Level.INFO, "Configuration: {0}", config); //NON-NLS
logic = new ServerGameLogic(this, config);
}
public void run() {
startServer();
/**
* Starts the server on the given port and continuously processes incoming messages.
* @param port the port to start the server on
*/
public void run(int port) {
startServer(port);
while (true)
processNextMessage();
}
private void startServer() {
/**
* Initializes and starts the server on the specified port, handling exceptions if the server fails to start.
* @param port the port to start the server on
*/
private void startServer(int port) {
try {
LOGGER.log(Level.INFO, "Starting server..."); //NON-NLS
myServer = Network.createServer(port);
@@ -93,6 +96,9 @@ private void startServer() {
}
}
/**
* Retrieves and processes the next message from the queue, handling interruptions during the wait.
*/
private void processNextMessage() {
try {
pendingMessages.take().process(logic);
@@ -103,6 +109,9 @@ private void processNextMessage() {
}
}
/**
* Processes the next message from the queue, handling interruptions during message retrieval.
*/
private void initializeSerializables() {
Serializer.registerClass(GameDetails.class);
Serializer.registerClass(StartBattleMessage.class);
@@ -112,18 +121,22 @@ private void initializeSerializables() {
Serializer.registerClass(Battleship.class);
Serializer.registerClass(IntPoint.class);
Serializer.registerClass(Shot.class);
Serializer.registerClass(AnimationEndMessage.class);
Serializer.registerClass(AnimationStartMessage.class);
Serializer.registerClass(SwitchBattleState.class);
}
/**
* Registers message and connection listeners for the server.
*/
private void registerListeners() {
myServer.addMessageListener(this, MapMessage.class);
myServer.addMessageListener(this, ShootMessage.class);
myServer.addMessageListener(this, AnimationEndMessage.class);
myServer.addConnectionListener(this);
}
/**
* Handles received messages, logging the source and adding client messages to the pending queue.
* @param source the connection the message was received from
* @param message the received message
*/
@Override
public void messageReceived(HostedConnection source, Message message) {
LOGGER.log(Level.INFO, "message received from {0}: {1}", source.getId(), message); //NON-NLS
@@ -131,12 +144,22 @@ public void messageReceived(HostedConnection source, Message message) {
pendingMessages.add(new ReceivedMessage(clientMessage, source.getId()));
}
/**
* Handles a new connection by logging it and adding a new player to the game logic.
* @param server the server receiving the connection
* @param hostedConnection the newly added connection
*/
@Override
public void connectionAdded(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "new connection {0}", hostedConnection); //NON-NLS
logic.addPlayer(hostedConnection.getId());
}
/**
* Handles the removal of a connection by logging it, checking if it belongs to an active player, and exiting if necessary.
* @param server the server losing the connection
* @param hostedConnection the removed connection
*/
@Override
public void connectionRemoved(Server server, HostedConnection hostedConnection) {
LOGGER.log(Level.INFO, "connection closed: {0}", hostedConnection); //NON-NLS
@@ -149,6 +172,10 @@ public void connectionRemoved(Server server, HostedConnection hostedConnection)
}
}
/**
* Closes all client connections and terminates the server with the given exit value.
* @param exitValue the exit code to terminate the application with
*/
private void exit(int exitValue) { //NON-NLS
LOGGER.log(Level.INFO, "close request"); //NON-NLS
if (myServer != null)

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,11 @@
# Blender MTL File: 'untitletttd.blend'
# Material Count: 1
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
map_Kd C:\Users\Convidado.Cliente-JMF-PC\Desktop\ghftht.png

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,64 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 13.03.2012 15:35:28
newmtl metall
Ns 31.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2431 0.2431 0.2431
Kd 0.2431 0.2431 0.2431
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl 13922_StatenIslandFerry
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
map_Ka 13922_StatenIslandFerry_diffuse.jpg
map_Kd 13922_StatenIslandFerry_diffuse.jpg
newmtl metall2
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2118 0.2824 0.3451
Kd 0.2118 0.2824 0.3451
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl boat
Ns 34.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6588 0.3059 0.1294
Kd 0.6588 0.3059 0.1294
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl white
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.9686 0.9686 0.9686
Kd 0.9686 0.9686 0.9686
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000

View File

@@ -0,0 +1,3 @@
based on:
https://free3d.com/3d-model/statenislandferry-v1--603882.html
License: Free Personal Use Only

View File

@@ -1,104 +0,0 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 16.12.2011 14:18:52
newmtl white
Ns 53.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6667 0.6667 0.6667
Kd 0.6667 0.6667 0.6667
Ks 0.1800 0.1800 0.1800
Ke 0.0000 0.0000 0.0000
newmtl boat_elements_black
Ns 55.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.0000 0.0000 0.0000
Kd 0.0000 0.0000 0.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl boat_glass
Ns 60.0000
Ni 7.0000
d 0.4000
Tr 0.6000
Tf 0.4000 0.4000 0.4000
illum 2
Ka 0.1059 0.1569 0.1451
Kd 0.1059 0.1569 0.1451
Ks 0.6750 0.6750 0.6750
Ke 0.0000 0.0000 0.0000
newmtl boat_screw_hooks_bronze
Ns 80.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2941 0.2157 0.0510
Kd 0.2941 0.2157 0.0510
Ks 0.7200 0.7200 0.7200
Ke 0.0000 0.0000 0.0000
newmtl boat_silver
Ns 80.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.3333 0.3333 0.3333
Kd 0.3333 0.3333 0.3333
Ks 0.7200 0.7200 0.7200
Ke 0.0000 0.0000 0.0000
newmtl boat_buffer
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.2700 0.2700 0.2700
Ke 0.0000 0.0000 0.0000
map_Ka boat_buffer_diffuse.jpg
map_Kd boat_buffer_diffuse.jpg
newmtl boat_roof_accessory
Ns 15.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
map_Ka boat_roof_accessory_diffuse.jpg
map_Kd boat_roof_accessory_diffuse.jpg
newmtl boat_body
Ns 55.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
map_Ka boat_body_diffuse.jpg
map_Kd boat_body_diffuse.jpg

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/boat-v2--225787.html
License: Free Personal Use Only

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

View File

@@ -1,250 +0,0 @@
#
# Generated by Sweet Home 3D - ven. janv. 02 20:37:08 CET 2015
# http://www.sweethome3d.com/
#
newmtl FrontColorNoCulling
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 0.0
newmtl ForegroundColor
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white
illum 1
Ka 0.48235294 0.5019608 0.5803922
Kd 0.48235294 0.5019608 0.5803922
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_5
illum 1
Ka 0.47843137 0.49803922 0.5764706
Kd 0.47843137 0.49803922 0.5764706
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_10
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl FrontColorNoCulling_11
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl ForegroundColor_12
illum 1
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Mesh_13
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 0.0
newmtl Cube_1_1_1
illum 1
Ka 0.0 0.0 0.0
Kd 0.0 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_7_7
illum 1
Ka 0.4 0.4 0.4
Kd 0.4 0.4 0.4
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_10_10
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_11_11
illum 2
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl 12_12
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_1_1_Cube_1_1_1_38
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 1.0
newmtl white_Cylinder_58
illum 1
Ka 0.1882353 0.27058825 0.58431375
Kd 0.1882353 0.27058825 0.58431375
Ks 0.0 0.0 0.0
Ns 0.0
newmtl white_Cylinder_59
illum 1
Ka 0.3137255 0.14901961 0.011764706
Kd 0.3137255 0.14901961 0.011764706
Ks 0.0 0.0 0.0
Ns 0.0
newmtl 1_1
illum 2
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.5 0.5 0.5
Ns 64.0
Ni 1.0
d 0.48000002
map_Kd Missile_AIM-120_D_[AMRAAM]_1_1.png
newmtl Cube_1_2_2
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_4_4
illum 2
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_5_5
illum 2
Ka 0.8 0.8 0.0
Kd 0.8 0.8 0.0
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_6_6
illum 2
Ka 0.8784314 0.8745098 0.8901961
Kd 0.8784314 0.8745098 0.8901961
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_10_10_Cylinder_10_10_73
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl 11_11
illum 1
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_1_1_Cube_1_1_1_76
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
Ni 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_1_1_Cube_1_1_1_76.png
newmtl Cylinder_2_2
illum 2
Ka 0.6 0.6 0.6
Kd 0.6 0.6 0.6
Ks 0.5 0.5 0.5
Ns 64.0
newmtl Cylinder_3_3
illum 1
Ka 0.4 0.4 0.0
Kd 0.4 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_4_4_Cylinder_4_4_79
illum 1
Ka 0.0 0.0 0.0
Kd 0.0 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_5_5
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_5_5.png
newmtl Cube_1_6_6
illum 1
Ka 0.2 0.2 0.2
Kd 1.0 1.0 1.0
Ks 0.0 0.0 0.0
Ns 1.0
Ni 1.0
map_Kd Missile_AIM-120_D_[AMRAAM]_Cube_1_6_6.png
newmtl Cylinder_1_1
illum 1
Ka 0.4 0.4 0.4
Kd 0.4 0.4 0.4
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_5_5_Cube_1_5_5_86
illum 1
Ka 0.2 0.2 0.2
Kd 0.2 0.2 0.2
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cube_1_6_6_Cube_1_6_6_87
illum 1
Ka 0.8 0.0 0.0
Kd 0.8 0.0 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_7_7_Cylinder_7_7_88
illum 1
Ka 0.8 0.4 0.0
Kd 0.8 0.4 0.0
Ks 0.0 0.0 0.0
Ns 1.0
newmtl Cylinder_8_8
illum 1
Ka 0.4 0.6 0.0
Kd 0.4 0.6 0.0
Ks 0.0 0.0 0.0
Ns 1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/de/3d-model/aim-120d-missile-51025.html
License: Free Personal Use Only

Binary file not shown.

After

Width:  |  Height:  |  Size: 717 KiB

View File

@@ -0,0 +1,28 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 08.06.2011 15:26:00
newmtl _10634_SpeedBoat_v01_LOD310634_SpeedBoat_v01
Ns 53.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.2000 0.2000 0.2000
Ke 0.0000 0.0000 0.0000
map_Ka 10634_SpeedBoat_v01.jpg
map_Kd 10634_SpeedBoat_v01.jpg
newmtl glass
Ns 80.0000
Ni 1.5000
d 0.2000
Tr 0.8000
Tf 0.2000 0.2000 0.2000
illum 2
Ka 0.5882 0.5882 0.5882
Kd 0.5882 0.5882 0.5882
Ks 0.5000 0.5000 0.5000
Ke 0.0000 0.0000 0.0000

View File

@@ -0,0 +1,3 @@
based on:
https://free3d.com/3d-model/speedboat-v01--840133.html
License: Free Personal Use Only

View File

@@ -1,16 +0,0 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 29.03.2012 14:25:39
newmtl default
Ns 35.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.5400 0.5400 0.5400
Ke 0.0000 0.0000 0.0000
map_Ka 14084_WWII_ship_German_Type_II_U-boat_diff.jpg
map_Kd 14084_WWII_ship_German_Type_II_U-boat_diff.jpg

View File

@@ -1,3 +0,0 @@
based on:
https://free3d.com/3d-model/wwii-ship-german-type-ii-uboat-v2--700733.html
License: Free Personal Use Only

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/boss-battle-2-symphonic-metal
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/game-over-instrumental
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/victory-fanfare-short
License: CC0 (public domain)

View File

@@ -1,3 +0,0 @@
based on
https://opengameart.org/content/dark-intro
License: CC0 (public domain)

Binary file not shown.

View File

@@ -41,7 +41,7 @@ public static void main(String[] args) {
*/
@Override
public void simpleInitApp() {
export("Models/KingGeorgeV/King_George_V.obj", "KingGeorgeV.j3o"); //NON-NLS
export("Models/Destroyer/10619_Battleship.obj", "Destroyer.j3o"); //NON-NLS
stop();
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
# Blender MTL File: 'untitletttd.blend'
# Material Count: 1
newmtl None
Ns 0
Ka 0.000000 0.000000 0.000000
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
map_Kd C:\Users\Convidado.Cliente-JMF-PC\Desktop\ghftht.png

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,73 @@
newmtl Battleship
illum 4
Kd 0.00 0.00 0.00
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
map_Kd BattleshipC.jpg
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn1SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn2SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.00 0.00 0.00
Ns 256.00
newmtl blinn3SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn4SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn5SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn6SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn7SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00
newmtl blinn8SG
illum 4
Kd 0.50 0.50 0.50
Ka 0.00 0.00 0.00
Tf 1.00 1.00 1.00
Ni 1.00
Ks 0.50 0.50 0.50
Ns 256.00

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 KiB

View File

@@ -0,0 +1,3 @@
based on:
https://free3d.com/3d-model/battleship-v1--611736.html
License: Free Personal Use Only

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -0,0 +1,64 @@
# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
# File Created: 13.03.2012 15:35:28
newmtl metall
Ns 31.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2431 0.2431 0.2431
Kd 0.2431 0.2431 0.2431
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl 13922_StatenIslandFerry
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 1.0000 1.0000 1.0000
Kd 1.0000 1.0000 1.0000
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
map_Ka 13922_StatenIslandFerry_diffuse.jpg
map_Kd 13922_StatenIslandFerry_diffuse.jpg
newmtl metall2
Ns 50.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.2118 0.2824 0.3451
Kd 0.2118 0.2824 0.3451
Ks 0.5850 0.5850 0.5850
Ke 0.0000 0.0000 0.0000
newmtl boat
Ns 34.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.6588 0.3059 0.1294
Kd 0.6588 0.3059 0.1294
Ks 0.3600 0.3600 0.3600
Ke 0.0000 0.0000 0.0000
newmtl white
Ns 10.0000
Ni 1.5000
d 1.0000
Tr 0.0000
Tf 1.0000 1.0000 1.0000
illum 2
Ka 0.9686 0.9686 0.9686
Kd 0.9686 0.9686 0.9686
Ks 0.0000 0.0000 0.0000
Ke 0.0000 0.0000 0.0000

View File

@@ -1,108 +0,0 @@
package pp.battleship.game.client;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.SwitchBattleState;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.notification.Music;
import pp.battleship.notification.Sound;
public class AnimationState extends ClientState {
private boolean myTurn;
/**
* creates an object of AnimationState
*
* @param logic the client logic
* @param myTurn a boolean containing if it is the clients turn
* @param position the position a shell should be created
*/
public AnimationState(ClientGameLogic logic, boolean myTurn, IntPoint position) {
super(logic);
logic.playMusic(Music.BATTLE_THEME);
this.myTurn = myTurn;
if(myTurn) {
logic.getOpponentMap().add(new Shell(position));
}else {
logic.getOwnMap().add(new Shell(position));
logic.playSound(Sound.ROCKET_FIRED);
}
}
/**
* This method makes sure the client renders the correct view
*
* @return true
*/
@Override
boolean showBattle() {
return true;
}
/**
* Reports the effect of a shot based on the server message.
*
* @param msg the message containing the effect of the shot
*/
@Override
public void receivedEffect(EffectMessage msg) {
ClientGameLogic.LOGGER.log(System.Logger.Level.INFO, "report effect: {0}", msg); //NON-NLS
playSound(msg);
myTurn = msg.isMyTurn();
logic.setInfoText(msg.getInfoTextKey());
affectedMap(msg).add(msg.getShot());
if (destroyedOpponentShip(msg)) {
logic.getOpponentMap().add(msg.getDestroyedShip());
}
if (msg.isGameOver()) {
msg.getRemainingOpponentShips().forEach(logic.getOpponentMap()::add);
logic.setState(new GameOverState(logic, msg.isGameLost()));
}
}
/**
* this method is used to change the client to the battle state again
*
* @param msg the message to process
*/
@Override
public void receivedSwitchBattleState(SwitchBattleState msg) {
logic.setState(new BattleState(logic, msg.isTurn()));
}
/**
* Determines which map (own or opponent's) should be affected by the shot based on the message.
*
* @param msg the effect message received from the server
* @return the map (either the opponent's or player's own map) that is affected by the shot
*/
private ShipMap affectedMap(EffectMessage msg) {
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
}
/**
* Checks if the opponent's ship was destroyed by the player's shot.
*
* @param msg the effect message received from the server
* @return true if the shot destroyed an opponent's ship, false otherwise
*/
private boolean destroyedOpponentShip(EffectMessage msg) {
return msg.getDestroyedShip() != null && msg.isOwnShot();
}
/**
* Plays a sound based on the outcome of the shot. Different sounds are played for a miss, hit,
* or destruction of a ship.
*
* @param msg the effect message containing the result of the shot
*/
private void playSound(EffectMessage msg) {
if (!msg.getShot().isHit())
logic.playSound(Sound.SPLASH);
else if (msg.getDestroyedShip() == null)
logic.playSound(Sound.EXPLOSION);
else
logic.playSound(Sound.DESTROYED_SHIP);
}
}

View File

@@ -7,10 +7,15 @@
package pp.battleship.game.client;
import com.jme3.math.Vector3f;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.AnimationStartMessage;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.notification.Music;
import pp.battleship.model.Shell;
import pp.battleship.model.ShipMap;
import pp.battleship.notification.Sound;
import java.lang.System.Logger.Level;
/**
* Represents the state of the client where players take turns to attack each other's ships.
@@ -26,20 +31,22 @@ class BattleState extends ClientState {
*/
public BattleState(ClientGameLogic logic, boolean myTurn) {
super(logic);
logic.playMusic(Music.BATTLE_THEME);
this.myTurn = myTurn;
}
/**
* This method makes sure the client renders the correct view
*
* @return true
* displays the battle scene.
* @return true to show the battle
*/
@Override
public boolean showBattle() {
return true;
}
/**
* Handles clicking on the opponent's map. If it's the player's turn and the position is valid, a ShootMessage is sent.
* @param pos the clicked position on the opponent's map
*/
@Override
public void clickOpponentMap(IntPoint pos) {
if (!myTurn)
@@ -48,8 +55,44 @@ else if (logic.getOpponentMap().isValid(pos))
logic.send(new ShootMessage(pos));
}
/**
* Reports the effect of a shot based on the server message.
*
* @param msg the message containing the effect of the shot
*/
@Override
public void receivedAnimationStart(AnimationStartMessage msg){
logic.setState(new AnimationState(logic, msg.isMyTurn(), msg.getPosition()));
public void receivedEffect(EffectMessage msg) {
ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
myTurn = msg.isMyTurn();
logic.playSound(Sound.MISSILE_LAUNCH);
logic.setInfoText(msg.getInfoTextKey());
affectedMap(msg).add(msg.getShot());
affectedMap(msg).add(new Shell(new Vector3f(0, 50, 0), new Vector3f(msg.getShot().getY() + 0.5f, -0.4f, msg.getShot().getX() + 0.5f), 1f,msg,logic));
if (destroyedOpponentShip(msg))
logic.getOpponentMap().add(msg.getDestroyedShip());
if (msg.isGameOver()) {
msg.getRemainingOpponentShips().forEach(logic.getOpponentMap()::add);
logic.setState(new GameOverState(logic));
}
}
/**
* Determines which map (own or opponent's) should be affected by the shot based on the message.
*
* @param msg the effect message received from the server
* @return the map (either the opponent's or player's own map) that is affected by the shot
*/
private ShipMap affectedMap(EffectMessage msg) {
return msg.isOwnShot() ? logic.getOpponentMap() : logic.getOwnMap();
}
/**
* Checks if the opponent's ship was destroyed by the player's shot.
*
* @param msg the effect message received from the server
* @return true if the shot destroyed an opponent's ship, false otherwise
*/
private boolean destroyedOpponentShip(EffectMessage msg) {
return msg.getDestroyedShip() != null && msg.isOwnShot();
}
}

View File

@@ -8,7 +8,10 @@
package pp.battleship.game.client;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.server.*;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.model.ShipMap;
import pp.battleship.model.dto.ShipMapDTO;
@@ -17,8 +20,6 @@
import pp.battleship.notification.GameEventBroker;
import pp.battleship.notification.GameEventListener;
import pp.battleship.notification.InfoTextEvent;
import pp.battleship.notification.Music;
import pp.battleship.notification.MusicEvent;
import pp.battleship.notification.Sound;
import pp.battleship.notification.SoundEvent;
@@ -225,26 +226,6 @@ public void received(EffectMessage msg) {
state.receivedEffect(msg);
}
/**
* Reports that the client should start an animation
*
* @param msg the AnimationStartMessage received
*/
@Override
public void received(AnimationStartMessage msg) {
state.receivedAnimationStart(msg);
}
/**
* Reports that the client should move to the battle state
*
* @param msg the SwitchBattleState received
*/
@Override
public void received(SwitchBattleState msg) {
state.receivedSwitchBattleState(msg);
}
/**
* Initializes the player's own map, opponent's map, and harbor based on the game details.
*
@@ -277,15 +258,6 @@ public void playSound(Sound sound) {
notifyListeners(new SoundEvent(sound));
}
/**
* Emits an event to play the specified music
*
* @param music the music to be played
*/
public void playMusic(Music music) {
notifyListeners(new MusicEvent(music));
}
/**
* Loads a map from the specified file.
*
@@ -332,7 +304,7 @@ public void saveMap(File file) throws IOException {
*
* @param msg the message to be sent
*/
public void send(ClientMessage msg) {
void send(ClientMessage msg) {
if (clientSender == null)
LOGGER.log(Level.ERROR, "trying to send {0} with sender==null", msg); //NON-NLS
else

View File

@@ -7,7 +7,9 @@
package pp.battleship.game.client;
import pp.battleship.message.server.*;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint;
import java.io.File;
@@ -163,24 +165,6 @@ void receivedEffect(EffectMessage msg) {
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName()); //NON-NLS
}
/**
* Reports that the client should start an animation
*
* @param msg the AnimationStartMessage received
*/
void receivedAnimationStart(AnimationStartMessage msg){
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedEffect not allowed in {0}", getName());
}
/**
* Reports that the client should move to the battle state
*
* @param msg the SwitchBattleState received
*/
void receivedSwitchBattleState(SwitchBattleState msg){
ClientGameLogic.LOGGER.log(Level.ERROR, "receivedSwitchBattleState not allowed in {0}", getName());
}
/**
* Loads a map from the specified file.
*

View File

@@ -16,11 +16,9 @@
import java.io.File;
import java.io.IOException;
import java.lang.System.Logger.Level;
import java.util.Arrays;
import java.util.List;
import static pp.battleship.Resources.lookup;
import static pp.battleship.game.client.ClientGameLogic.LOGGER;
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
import static pp.battleship.model.Battleship.Status.NORMAL;
import static pp.battleship.model.Battleship.Status.VALID_PREVIEW;
@@ -59,7 +57,7 @@ public boolean showEditor() {
*/
@Override
public void movePreview(IntPoint pos) {
LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
ClientGameLogic.LOGGER.log(Level.DEBUG, "move preview to {0}", pos); //NON-NLS
if (preview == null || !ownMap().isValid(pos)) return;
preview.moveTo(pos);
setPreviewStatus(preview);
@@ -74,7 +72,7 @@ public void movePreview(IntPoint pos) {
*/
@Override
public void clickOwnMap(IntPoint pos) {
LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in own map", pos); //NON-NLS
if (!ownMap().isValid(pos)) return;
if (preview == null)
modifyShip(pos);
@@ -114,8 +112,7 @@ private void placeShip(IntPoint cursor) {
harbor().remove(selectedInHarbor);
preview = null;
selectedInHarbor = null;
}
else {
} else {
preview.setStatus(INVALID_PREVIEW);
ownMap().add(preview);
}
@@ -128,7 +125,7 @@ private void placeShip(IntPoint cursor) {
*/
@Override
public void clickHarbor(IntPoint pos) {
LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
ClientGameLogic.LOGGER.log(Level.DEBUG, "click at {0} in harbor", pos); //NON-NLS
if (!harbor().isValid(pos)) return;
final Battleship shipAtCursor = harbor().findShipAt(pos);
if (preview != null) {
@@ -138,8 +135,7 @@ public void clickHarbor(IntPoint pos) {
harbor().add(selectedInHarbor);
preview = null;
selectedInHarbor = null;
}
else if (shipAtCursor != null) {
} else if (shipAtCursor != null) {
selectedInHarbor = shipAtCursor;
selectedInHarbor.setStatus(VALID_PREVIEW);
harbor().remove(selectedInHarbor);
@@ -155,7 +151,7 @@ else if (shipAtCursor != null) {
*/
@Override
public void rotateShip() {
LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
ClientGameLogic.LOGGER.log(Level.DEBUG, "pushed rotate"); //NON-NLS
if (preview == null) return;
preview.rotated();
ownMap().remove(preview);
@@ -231,7 +227,7 @@ private ShipMap harbor() {
}
/**
* Loads a map from the specified file.
* Loads a map from the specified file. Also Checks if the map is valid
*
* @param file the file to load the map from
* @throws IOException if the map cannot be loaded
@@ -241,8 +237,8 @@ public void loadMap(File file) throws IOException {
final ShipMapDTO dto = ShipMapDTO.loadFrom(file);
if (!dto.fits(logic.getDetails()))
throw new IOException(lookup("map.doesnt.fit"));
else if (!checkMapToLoad(dto)) {
throw new IOException(lookup("ships.dont.fit.the.map"));
if(!validMap(dto)){
throw new IOException(lookup("map.is.invalid"));
}
ownMap().clear();
dto.getShips().forEach(ownMap()::add);
@@ -251,40 +247,6 @@ else if (!checkMapToLoad(dto)) {
selectedInHarbor = null;
}
/**
* This method is used to check if the loaded map is correct
*
* @param dto the data transfer object to check
* @return boolean if map is correct or not
*/
private boolean checkMapToLoad(ShipMapDTO dto) {
int mapWidth = dto.getWidth();
int mapHeight = dto.getHeight();
// check if ship is out of bounds
for (int i = 0; i < dto.getShips().size(); i++) {
Battleship battleship = dto.getShips().get(i);
if (battleship.getMaxX() >= mapWidth || battleship.getMinX() < 0 || battleship.getMaxY() >= mapHeight || battleship.getMinY() < 0) {
LOGGER.log(Level.ERROR, "Ship is out of bounds ({0})", battleship.toString());
return false;
}
}
// check if ships overlap
List<Battleship> ships = dto.getShips();
for(Battleship ship:ships){
for(Battleship compareShip:ships){
if(!(ship==compareShip)){
if(ship.collidesWith(compareShip)){
return false;
}
}
}
}
return true;
}
/**
* Checks if the player's own map may be loaded from a file.
*
@@ -304,4 +266,51 @@ public boolean mayLoadMap() {
public boolean maySaveMap() {
return harbor().getItems().isEmpty();
}
/**
* Checks if the map is valid in terms of overlapping and if the ship is within the boundries of the map
* @param dto DataTransferObject is the loaded json file
* @return returns true if the map is valid, false otherwhise
*/
private boolean validMap(ShipMapDTO dto) {
return inBoundsClient(dto) && overLapClient(dto);
}
/**
* Checks if the Ships overlap on the map
* @param dto DataTransferObject is the loaded json file
* @return returns true if the ships arent overlapping, false otherwhise
*/
private boolean overLapClient(ShipMapDTO dto) {
List<Battleship> battleshipList = dto.getShips();
for (int i = 0; i < battleshipList.size(); i++) {
Battleship ship1 = battleshipList.get(i);
for (int j = i + 1; j < battleshipList.size(); j++) {
Battleship ship2 = battleshipList.get(j);
if (ship1.collidesWith(ship2)) {
return false;
}
}
}
return true;
}
/**
* Checks if the Ship is in the map Boundries
* @param dto DataTransferObject is the loaded json file
* @return true if the ship is in the maps boundriess, false otherwhise
*/
private boolean inBoundsClient(ShipMapDTO dto) {
int widht = dto.getWidth();
int height = dto.getHeight();
for (int i = 0; i < dto.getShips().size(); i++) {
Battleship localShip = dto.getShips().get(i);
if (!(localShip.getMaxX() < widht && localShip.getMaxY() < height && localShip.getMinY() >= 0 && localShip.getMinX() >= 0)) {
return false;
}
}
return true;
}
}

View File

@@ -7,8 +7,6 @@
package pp.battleship.game.client;
import pp.battleship.notification.Music;
/**
* Represents the state of the client when the game is over.
*/
@@ -18,13 +16,8 @@ class GameOverState extends ClientState {
*
* @param logic the client game logic
*/
GameOverState(ClientGameLogic logic, boolean lost) {
GameOverState(ClientGameLogic logic) {
super(logic);
if (lost){
logic.playMusic(Music.GAME_OVER_THEME_L);
} else {
logic.playMusic(Music.GAME_OVER_THEME_V);
}
}
/**

View File

@@ -7,7 +7,6 @@
package pp.battleship.game.client;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.StartBattleMessage;
import java.lang.System.Logger.Level;
@@ -39,16 +38,4 @@ public void receivedStartBattle(StartBattleMessage msg) {
logic.setInfoText(msg.getInfoTextKey());
logic.setState(new BattleState(logic, msg.isMyTurn()));
}
/**
* This method will revert the client from wait state to editor state
* in case a wrong map was submitted
*
* @param details the game details including map size and ships
*/
@Override
public void receivedGameDetails(GameDetails details){
logic.setInfoText("invalid.map");
logic.setState(new EditorState(logic));
}
}

View File

@@ -8,21 +8,19 @@
package pp.battleship.game.server;
import pp.battleship.BattleshipConfig;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.*;
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.Rotation;
import pp.util.Position;
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -41,9 +39,6 @@ public class ServerGameLogic implements ClientInterpreter {
private Player activePlayer;
private ServerState state = ServerState.WAIT;
private boolean player1AnimationReady = false;
private boolean player2AnimationReady = false;
/**
* Constructs a ServerGameLogic with the specified sender and configuration.
*
@@ -59,6 +54,7 @@ public ServerGameLogic(ServerSender serverSender, BattleshipConfig config) {
* Returns the state of the game.
*/
ServerState getState() {
return state;
}
@@ -138,7 +134,7 @@ public Player addPlayer(int id) {
}
/**
* Handles the reception of a MapMessage.
* Handles the reception of a MapMessage. Also Checks if the given map is valid or invalid
*
* @param msg the received MapMessage
* @param from the ID of the sender client
@@ -147,42 +143,60 @@ public Player addPlayer(int id) {
public void received(MapMessage msg, int from) {
if (state != ServerState.SET_UP)
LOGGER.log(Level.ERROR, "playerReady not allowed in {0}", state); //NON-NLS
else if (!checkMap(msg, from)) {
LOGGER.log(Level.ERROR, "player submitted not allowed Map");
send(getPlayerById(from), new GameDetails(config));
else if(!validMap(msg,from)){
LOGGER.log(Level.ERROR, "Map message not valid",state);
}
else
playerReady(getPlayerById(from), msg.getShips());
}
/**
* Returns true if the map contains correct ship placement and is of the correct size
*
* @param msg the received MapMessage of the player
* @param from the ID of the Player
* @return a boolean based on if the transmitted map ist correct
* Checks if the map is Valid in terms of boundries and overlaps
* *
* @param msg the received MapMessage
* @param id the ID of the sender client
* @return returns true if the map is valid, false otherwhise
*/
private boolean checkMap(MapMessage msg, int from) {
int mapWidth = getPlayerById(from).getMap().getWidth();
int mapHeight = getPlayerById(from).getMap().getHeight();
private boolean validMap(MapMessage msg, int id) {
return inBounds(msg, id) && overLap(msg);
}
// check if ship is out of bounds
for (Battleship battleship : msg.getShips()){
if (battleship.getMaxX() >= mapWidth || battleship.getMinX() < 0 || battleship.getMaxY() >= mapHeight || battleship.getMinY() < 0) {
LOGGER.log(Level.ERROR, "Ship is out of bounds ({0})", battleship.toString());
return false;
/**
* Checks if the Ships overLap
*
* @param msg the received MapMessage
* @return returns true if the ships arent overlapping, false otherwhise
*/
private boolean overLap(MapMessage msg) {
List<Battleship> battleshipList = msg.getShips();
for (int i = 0; i < battleshipList.size(); i++) {
Battleship ship1 = battleshipList.get(i);
for (int j = i + 1; j < battleshipList.size(); j++) {
Battleship ship2 = battleshipList.get(j);
if (ship1.collidesWith(ship2)) {
return false;
}
}
}
return true;
}
// check if ships overlap
List<Battleship> ships = msg.getShips();
for(Battleship ship:ships){
for(Battleship compareShip:ships){
if(!(ship==compareShip)){
if(ship.collidesWith(compareShip)){
return false;
}
}
/**
* Checks if the Ship is placed in Bounds with the Map
*
* @param msg the received MapMessage
* @param id the ID of the sender client
* @return returns true if the ship is within the maps boundries, false otherwhise
*/
private boolean inBounds(MapMessage msg, int id) {
int widht = getPlayerById(id).getMap().getWidth();
int height = getPlayerById(id).getMap().getHeight();
for (int i = 0; i < msg.getShips().size(); i++) {
Battleship localShip = msg.getShips().get(i);
if (!(localShip.getMaxX() < widht && localShip.getMaxY() < height && localShip.getMinY() >= 0 && localShip.getMinX() >= 0)) {
return false;
}
}
return true;
@@ -199,40 +213,7 @@ public void received(ShootMessage msg, int from) {
if (state != ServerState.BATTLE)
LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
else
for (Player player : players){
send(player, new AnimationStartMessage(msg.getPosition(), player == activePlayer));
setState(ServerState.ANIMATION_WAIT);
}
}
/**
* Handles a clients message that it is done with the animation
*
* @param msg the AnimationEndMessage to be processed
* @param from the connection ID from which the message was received
*/
@Override
public void received(AnimationEndMessage msg, int from){
if(state != ServerState.ANIMATION_WAIT) {
LOGGER.log(Level.ERROR, "animation not allowed in {0}", state);
return;
}
if(getPlayerById(from) == players.get(0)){
LOGGER.log(Level.DEBUG, "{0} set to true", getPlayerById(from));
player1AnimationReady = true;
shoot(getPlayerById(from), msg.getPosition());
} else if (getPlayerById(from) == players.get(1)){
LOGGER.log(Level.DEBUG, "{0} set to true {1}", getPlayerById(from), getPlayerById(from).toString());
player2AnimationReady = true;
shoot(getPlayerById(from), msg.getPosition());
}
if(player1AnimationReady && player2AnimationReady){
setState(ServerState.BATTLE);
for (Player player : players)
send(player, new SwitchBattleState(player == activePlayer));
player1AnimationReady = false;
player2AnimationReady = false;
}
}
/**
@@ -256,56 +237,36 @@ void playerReady(Player player, List<Battleship> ships) {
}
/**
* This method decides what effectMessage the client should get based on the shot made
* and switches the active player if a shot was missed
* Handles the shooting action by the player.
*
* @param p the player to be sent the message
* @param position the position where the shot would hit in the 2d map model
* @param p the player who shot
* @param pos the position of the shot
*/
void shoot(Player p, IntPoint position) {
final Battleship selectedShip;
if(p != activePlayer){
selectedShip = p.getMap().findShipAt(position);
} else {
selectedShip = getOpponent(p).getMap().findShipAt(position);
}
void shoot(Player p, IntPoint pos) {
if (p != activePlayer) return;
final Player otherPlayer = getOpponent(activePlayer);
final Battleship selectedShip = otherPlayer.getMap().findShipAt(pos);
if (selectedShip == null) {
if (p != activePlayer) {
send(p, EffectMessage.miss(false, position));
} else {
send(activePlayer, EffectMessage.miss(true, position));
}
if(player1AnimationReady && player2AnimationReady){
LOGGER.log(Level.DEBUG, "switched active player");
if(p != activePlayer){
activePlayer = p;
} else {
activePlayer = getOpponent(p);
}
}
// shot missed
send(activePlayer, EffectMessage.miss(true, pos));
send(otherPlayer, EffectMessage.miss(false, pos));
activePlayer = otherPlayer;
} else {
selectedShip.hit(position);
if(getOpponent(activePlayer).getMap().getRemainingShips().isEmpty()){
if(p != activePlayer){
send(p, EffectMessage.lost(position, selectedShip, activePlayer.getMap().getRemainingShips()));
} else {
send(activePlayer, EffectMessage.won(position, selectedShip));
}
if(player1AnimationReady && player2AnimationReady){
setState(ServerState.GAME_OVER);
}
} else if (selectedShip.isDestroyed()){
if(p != activePlayer){
send(p, EffectMessage.shipDestroyed(false, position, selectedShip));
} else {
send(activePlayer, EffectMessage.shipDestroyed(true, position, selectedShip));
}
// shot hit a ship
selectedShip.hit(pos);
if (otherPlayer.getMap().getRemainingShips().isEmpty()) {
// game is over
send(activePlayer, EffectMessage.won(pos, selectedShip));
send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
setState(ServerState.GAME_OVER);
} else if (selectedShip.isDestroyed()) {
// ship has been destroyed, but game is not yet over
send(activePlayer, EffectMessage.shipDestroyed(true, pos, selectedShip));
send(otherPlayer, EffectMessage.shipDestroyed(false, pos, selectedShip));
} else {
if(p != activePlayer){
send(p, EffectMessage.hit(false, position));
} else {
send(activePlayer, EffectMessage.hit(true, position));
}
// ship has been hit, but it hasn't been destroyed
send(activePlayer, EffectMessage.hit(true, pos));
send(otherPlayer, EffectMessage.hit(false, pos));
}
}
}

View File

@@ -29,10 +29,5 @@ enum ServerState {
/**
* The game has ended because all the ships of one player have been destroyed.
*/
GAME_OVER,
/**
* The server waits for all players to finish the animation
*/
ANIMATION_WAIT
GAME_OVER
}

View File

@@ -7,7 +7,10 @@
package pp.battleship.game.singlemode;
import pp.battleship.message.client.*;
import pp.battleship.message.client.ClientInterpreter;
import pp.battleship.message.client.ClientMessage;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.model.Battleship;
/**
@@ -60,18 +63,6 @@ public void received(MapMessage msg, int from) {
copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
}
/**
* Handles the reception of a AnimationEndMessage
* Creates a copy of the AnimationEndMessage
*
* @param msg the AnimationEndMessage to be processed
* @param from the connection ID from which the message was received
*/
@Override
public void received(AnimationEndMessage msg, int from) {
copiedMessage = new AnimationEndMessage(msg.getPosition());
}
/**
* Creates a copy of the provided {@link Battleship}.
*

View File

@@ -9,7 +9,11 @@
import pp.battleship.game.client.BattleshipClient;
import pp.battleship.game.client.ClientGameLogic;
import pp.battleship.message.server.*;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.ServerMessage;
import pp.battleship.message.server.StartBattleMessage;
import java.io.IOException;
@@ -20,13 +24,11 @@
class InterpreterProxy implements ServerInterpreter {
private final BattleshipClient playerClient;
static final System.Logger LOGGER = System.getLogger(InterpreterProxy.class.getName());
/**
* Constructs an InterpreterProxy with the specified BattleshipClient.
*
* @param playerClient the client to which the server messages are forwarded
*/
InterpreterProxy(BattleshipClient playerClient) {
this.playerClient = playerClient;
}
@@ -80,27 +82,6 @@ public void received(EffectMessage msg) {
forward(msg);
}
/**
* Forwards the received AnimationStartMessage to the client's game logic.
*
* @param msg the AnimationStartMessage received from the server
*/
@Override
public void received(AnimationStartMessage msg) {
forward(msg);
}
/**
* Forwards the received SwitchBattleState to the client's game logic.
*
* @param msg the SwitchBattleState received from the server
*/
@Override
public void received(SwitchBattleState msg){
LOGGER.log(System.Logger.Level.INFO, "Received SwitchBattleState");
forward(msg);
}
/**
* Forwards the specified ServerMessage to the client's game logic by enqueuing the message acceptance.
*

View File

@@ -1,10 +1,12 @@
package pp.battleship.game.singlemode;
import pp.battleship.game.client.BattleshipClient;
import pp.battleship.message.client.AnimationEndMessage;
import pp.battleship.message.client.MapMessage;
import pp.battleship.message.client.ShootMessage;
import pp.battleship.message.server.*;
import pp.battleship.message.server.EffectMessage;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.ServerInterpreter;
import pp.battleship.message.server.StartBattleMessage;
import pp.battleship.model.IntPoint;
import pp.battleship.model.dto.ShipMapDTO;
import pp.util.RandomPositionIterator;
@@ -111,35 +113,15 @@ public void received(StartBattleMessage msg) {
}
/**
* Receives an effect message, logs it.
* Receives an effect message, logs it, and updates the turn status.
* If it is RobotClient's turn to shoot, schedules a shot using shoot();
*
* @param msg The effect message
*/
@Override
public void received(EffectMessage msg) {
LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS
}
/**
* Receives an AnimationStartMessage, and responds instantly with an AnimationEndMessage
*
* @param msg the AnimationStartMessage received
*/
@Override
public void received(AnimationStartMessage msg) {
LOGGER.log(Level.INFO, "Received AnimationStartMessage: {0}", msg);
connection.sendRobotMessage(new AnimationEndMessage(msg.getPosition()));
}
/**
* Receives a SwitchBattleState, and shots if it is the robots turn
*
* @param msg the SwitchBattleState received
*/
@Override
public void received(SwitchBattleState msg){
LOGGER.log(Level.INFO, "Received SwitchBattleStateMessage: {0}", msg);
if (msg.isTurn())
if (msg.isMyTurn())
shoot();
}
}

View File

@@ -1,44 +0,0 @@
package pp.battleship.message.client;
import com.jme3.network.serializing.Serializable;
import pp.battleship.model.IntPoint;
@Serializable
public class AnimationEndMessage extends ClientMessage {
private IntPoint position;
/**
* used for serialization
*/
private AnimationEndMessage(){ /* nothing */}
/**
* constructs a new AnimationEndMessage
*
* @param position the position to be effected by the server
*/
public AnimationEndMessage(IntPoint position) {
this.position = position;
}
/**
* getter for the position
*
* @return IntPoint position
*/
public IntPoint getPosition() {
return position;
}
/**
* Accepts Visitors to process this message
*
* @param interpreter the visitor to be used for processing
* @param from the connection ID of the sender
*/
@Override
public void accept(ClientInterpreter interpreter, int from) {
interpreter.received(this, from);
}
}

Some files were not shown because too many files have changed in this diff Show More