added animation of a shell to the game
- in BattleShipServer class added serialization of the AnimationMessage classs - added to VoidVisitor and Visitor the Shell class - edited the ServerGameLogic class to implement a new Animation state (see new Server-State-Chart) - added a new client state AnimationState (see new Client-State-Chart)
This commit is contained in:
		@@ -29,7 +29,7 @@ robot.targets=2, 0,\
 | 
			
		||||
              2, 3
 | 
			
		||||
#
 | 
			
		||||
# Delay in milliseconds between each shot fired by the RobotClient.
 | 
			
		||||
robot.delay=500
 | 
			
		||||
robot.delay=2000
 | 
			
		||||
#
 | 
			
		||||
# The dimensions of the game map used in single mode.
 | 
			
		||||
# 'map.width' defines the number of columns, and 'map.height' defines the number of rows.
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,6 @@
 | 
			
		||||
import com.jme3.app.DebugKeysAppState;
 | 
			
		||||
import com.jme3.app.SimpleApplication;
 | 
			
		||||
import com.jme3.app.StatsAppState;
 | 
			
		||||
import com.jme3.audio.AudioNode;
 | 
			
		||||
import com.jme3.font.BitmapFont;
 | 
			
		||||
import com.jme3.font.BitmapText;
 | 
			
		||||
import com.jme3.input.KeyInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ public class GameSound extends AbstractAppState implements GameEventListener {
 | 
			
		||||
    private AudioNode splashSound;
 | 
			
		||||
    private AudioNode shipDestroyedSound;
 | 
			
		||||
    private AudioNode explosionSound;
 | 
			
		||||
    private AudioNode shellFiredSound;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if sound is enabled in the preferences.
 | 
			
		||||
@@ -78,6 +79,7 @@ public void initialize(AppStateManager stateManager, Application 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
 | 
			
		||||
        shellFiredSound = loadSound(app, "Sound/Effects/missle.wav"); //NON-NLS
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -93,8 +95,7 @@ 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;
 | 
			
		||||
@@ -124,12 +125,21 @@ public void shipDestroyed() {
 | 
			
		||||
            shipDestroyedSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Plays sound effect when a shell has been fired.
 | 
			
		||||
     */
 | 
			
		||||
    public void shellFired() {
 | 
			
		||||
        if (isEnabled() && shellFiredSound != null)
 | 
			
		||||
            shellFiredSound.playInstance();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEvent(SoundEvent event) {
 | 
			
		||||
        switch (event.sound()) {
 | 
			
		||||
            case EXPLOSION -> explosion();
 | 
			
		||||
            case SPLASH -> splash();
 | 
			
		||||
            case DESTROYED_SHIP -> shipDestroyed();
 | 
			
		||||
            case SHELL_FIRED -> shellFired();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,6 +143,10 @@ public float getHeight() {
 | 
			
		||||
        return FIELD_SIZE * map.getHeight();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static float getFieldSize() {
 | 
			
		||||
        return FIELD_SIZE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Converts coordinates from view coordinates to model coordinates.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -7,14 +7,20 @@
 | 
			
		||||
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.material.Material;
 | 
			
		||||
import com.jme3.material.RenderState;
 | 
			
		||||
import com.jme3.math.ColorRGBA;
 | 
			
		||||
import com.jme3.scene.Geometry;
 | 
			
		||||
import com.jme3.scene.Node;
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Sphere;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
import static com.jme3.material.Materials.UNSHADED;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Synchronizes the visual representation of the ship map with the game model.
 | 
			
		||||
 * It handles the rendering of ships and shots on the map view, updating the view
 | 
			
		||||
@@ -65,9 +71,9 @@ public Spatial visit(Shot shot) {
 | 
			
		||||
 | 
			
		||||
        // Create and return a rectangle representing the shot
 | 
			
		||||
        return view.getApp().getDraw().makeRectangle(p1.getX(), p1.getY(),
 | 
			
		||||
                                                     SHOT_DEPTH,
 | 
			
		||||
                                                     p2.getX() - p1.getX(), p2.getY() - p1.getY(),
 | 
			
		||||
                                                     color);
 | 
			
		||||
                SHOT_DEPTH,
 | 
			
		||||
                p2.getX() - p1.getX(), p2.getY() - p1.getY(),
 | 
			
		||||
                color);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -109,6 +115,25 @@ public Spatial visit(Battleship ship) {
 | 
			
		||||
        return shipNode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a visual representation of a shell on the map.
 | 
			
		||||
     * The shell is represented as a black ellipse.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the Shell object representing the shell in the model
 | 
			
		||||
     * @return a Spatial representing the shell on the map
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        Geometry ellipse = new Geometry("ellipse", new Sphere(50, 50, MapView.getFieldSize() / 2 * 0.8f));
 | 
			
		||||
        Material mat = new Material(view.getApp().getAssetManager(), UNSHADED); //NON-NLS
 | 
			
		||||
        mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
 | 
			
		||||
        mat.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off);
 | 
			
		||||
        mat.setColor("Color", ColorRGBA.Black);
 | 
			
		||||
        ellipse.setMaterial(mat);
 | 
			
		||||
        ellipse.addControl(new ShellMapControl(view, shell));
 | 
			
		||||
        return ellipse;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a line geometry representing part of the ship's border.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,7 @@
 | 
			
		||||
import com.jme3.scene.Spatial;
 | 
			
		||||
import com.jme3.scene.shape.Box;
 | 
			
		||||
import pp.battleship.client.BattleshipApp;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
import pp.battleship.model.ShipMap;
 | 
			
		||||
import pp.battleship.model.Shot;
 | 
			
		||||
import pp.battleship.model.*;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
import static pp.util.FloatMath.HALF_PI;
 | 
			
		||||
@@ -35,8 +32,7 @@
 | 
			
		||||
 * It extends the {@link ShipMapSynchronizer} to provide specific synchronization
 | 
			
		||||
 * logic for the sea map.
 | 
			
		||||
 */
 | 
			
		||||
class SeaSynchronizer extends ShipMapSynchronizer { ;
 | 
			
		||||
 | 
			
		||||
class SeaSynchronizer extends ShipMapSynchronizer {
 | 
			
		||||
    private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS
 | 
			
		||||
    private static final String KING_GEORGE_V_MODEL = "Models/KingGeorgeV/KingGeorgeV.j3o"; //NON-NLS
 | 
			
		||||
    private static final String DESTROYER_MODEL = "Models/Destroyer/Destroyer.j3o"; //NON-NLS
 | 
			
		||||
@@ -44,8 +40,11 @@ class SeaSynchronizer extends ShipMapSynchronizer { ;
 | 
			
		||||
    private static final String TYPE_II_UBOAT_MODEL = "Models/TypeIIUboat/TypeIIUboat.j3o"; //NON-NLS
 | 
			
		||||
    private static final String TYPE_II_UBOAT_TEXTURE = "Models/TypeIIUboat/Type_II_U-boat_diff.jpg"; //NON-NLS
 | 
			
		||||
    private static final String ATLANTICA_MODEL = "Models/Atlantica/Atlantica.j3o"; //NON-NLS
 | 
			
		||||
    private static final String ROCKET = "Models/Rocket/Rocket.j3o"; //NON-NLS
 | 
			
		||||
 | 
			
		||||
    private static final String COLOR = "Color"; //NON-NLS
 | 
			
		||||
    private static final String SHIP = "ship"; //NON-NLS
 | 
			
		||||
    private static final String SHELL = "shell"; //NON-NLS
 | 
			
		||||
    private static final ColorRGBA BOX_COLOR = ColorRGBA.Gray;
 | 
			
		||||
 | 
			
		||||
    private final ShipMap map;
 | 
			
		||||
@@ -176,6 +175,36 @@ private ParticleEmitter createFireEffect(Shot shot, Node shipNode) {
 | 
			
		||||
        return fire;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a {@link Shell} and creates a graphical representation of it.
 | 
			
		||||
     * The shell is represented as a node with a model attached to it.
 | 
			
		||||
     * The node is then positioned and controlled by a {@link ShellControl}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the shell to be represented
 | 
			
		||||
     * @return the node containing the graphical representation of the shell
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public Spatial visit(Shell shell) {
 | 
			
		||||
        final Node node = new Node(SHELL);
 | 
			
		||||
        node.attachChild(createShell());
 | 
			
		||||
        node.setLocalTranslation(shell.getY() + 0.5f, 10f, shell.getX() + 0.5f);
 | 
			
		||||
        node.addControl(new ShellControl());
 | 
			
		||||
        return node;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a graphical representation of a shell.
 | 
			
		||||
     *
 | 
			
		||||
     * @return the spatial representing the shell
 | 
			
		||||
     */
 | 
			
		||||
    private Spatial createShell() {
 | 
			
		||||
        final Spatial model = app.getAssetManager().loadModel(ROCKET);
 | 
			
		||||
        model.scale(0.0025f);
 | 
			
		||||
        model.rotate(PI, 0f, 0f);
 | 
			
		||||
        model.setShadowMode(ShadowMode.CastAndReceive);
 | 
			
		||||
        return model;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,42 @@
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the movement and rotation of a shell in the game.
 | 
			
		||||
 * The shell moves downward at a constant speed and rotates around its Y-axis.
 | 
			
		||||
 * When the shell reaches a certain Y-coordinate, it is removed from its parent node.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellControl extends AbstractControl {
 | 
			
		||||
    private final static float SHELL_SPEED = 7.5f;
 | 
			
		||||
    private final static float SHELL_ROTATION_SPEED = 0.5f;
 | 
			
		||||
    private final static float MIN_HEIGHT = 0.7f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the shell's position and rotation.
 | 
			
		||||
     * If the shell's Y-coordinate is less than or equal to 1.0, it is detached from its parent node.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf time per frame, used to ensure consistent movement speed across different frame rates
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        spatial.move(0, -SHELL_SPEED * tpf, 0);
 | 
			
		||||
        spatial.rotate(0, SHELL_ROTATION_SPEED, 0);
 | 
			
		||||
        if (spatial.getLocalTranslation().getY() <= MIN_HEIGHT) {
 | 
			
		||||
            spatial.getParent().detachChild(spatial);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Renders the shell. This method is currently not used.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager
 | 
			
		||||
     * @param vp the ViewPort
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // nothing to do here
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,82 @@
 | 
			
		||||
package pp.battleship.client.gui;
 | 
			
		||||
 | 
			
		||||
import com.jme3.math.Vector3f;
 | 
			
		||||
import com.jme3.renderer.RenderManager;
 | 
			
		||||
import com.jme3.renderer.ViewPort;
 | 
			
		||||
import com.jme3.scene.control.AbstractControl;
 | 
			
		||||
import pp.battleship.model.Shell;
 | 
			
		||||
import pp.util.Position;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Controls the animation of a shell in the map view.
 | 
			
		||||
 * This class handles the movement of a shell from its starting position to its target position
 | 
			
		||||
 * using linear interpolation over a specified duration.
 | 
			
		||||
 */
 | 
			
		||||
public class ShellMapControl extends AbstractControl {
 | 
			
		||||
    private static final Logger LOGGER = System.getLogger(ShellMapControl.class.getName());
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The duration of the shell animation in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float ANIMATION_DURATION = 0.8f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The end position of the shell in the map view.
 | 
			
		||||
     */
 | 
			
		||||
    private final Position endPos;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The progress of the shell's movement, ranging from 0 to 1.
 | 
			
		||||
     */
 | 
			
		||||
    private float progress = 0f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new instance of {@link ShellMapControl}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param view  the map view
 | 
			
		||||
     * @param shell the shell to be controlled
 | 
			
		||||
     */
 | 
			
		||||
    public ShellMapControl(MapView view, Shell shell) {
 | 
			
		||||
        Vector3f endPos = new Vector3f(shell.getX(), 0, shell.getY());
 | 
			
		||||
        this.endPos = view.modelToView(endPos.x, endPos.z);
 | 
			
		||||
        LOGGER.log(Level.DEBUG, "ShellMapControl created with endPos: " + this.endPos);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the position of the shell in the view with linear interpolation.
 | 
			
		||||
     * This method is called during the update phase.
 | 
			
		||||
     *
 | 
			
		||||
     * @param tpf the time per frame
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlUpdate(float tpf) {
 | 
			
		||||
        // adjust speed by changing the multiplier
 | 
			
		||||
        progress += tpf * ANIMATION_DURATION;
 | 
			
		||||
 | 
			
		||||
        // progress is between 0 and 1
 | 
			
		||||
        if (progress > 1f) {
 | 
			
		||||
            progress = 1f;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // linearly interpolate the current position between (0, 0) and endPos
 | 
			
		||||
        float newX = (1 - progress) * 0 + progress * endPos.getX() + MapView.getFieldSize() / 2;
 | 
			
		||||
        float newZ = (1 - progress) * 0 + progress * endPos.getY() + MapView.getFieldSize() / 2;
 | 
			
		||||
 | 
			
		||||
        spatial.setLocalTranslation(newX, newZ, 0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This method is called during the render phase.
 | 
			
		||||
     * Currently, it does nothing.
 | 
			
		||||
     *
 | 
			
		||||
     * @param rm the RenderManager
 | 
			
		||||
     * @param vp the ViewPort
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    protected void controlRender(RenderManager rm, ViewPort vp) {
 | 
			
		||||
        // nothing to do here
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -0,0 +1,250 @@
 | 
			
		||||
# 
 | 
			
		||||
# 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.
										
									
								
							| 
		 After Width: | Height: | Size: 210 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 164 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 289 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 26 KiB  | 
@@ -0,0 +1,3 @@
 | 
			
		||||
based on:
 | 
			
		||||
https://free3d.com/de/3d-model/aim-120d-missile-51025.html
 | 
			
		||||
License: Free Personal Use Only
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -0,0 +1,159 @@
 | 
			
		||||
package pp.battleship.game.client;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the game during an animation sequence.
 | 
			
		||||
 * This state handles the progress and completion of the animation,
 | 
			
		||||
 * updates the game state accordingly, and transitions to the next state.
 | 
			
		||||
 */
 | 
			
		||||
public class AnimationState extends ClientState {
 | 
			
		||||
    /**
 | 
			
		||||
     * Progress of the current animation, ranging from 0 to 1.
 | 
			
		||||
     */
 | 
			
		||||
    private float animationProgress = 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Duration of the animation in seconds.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float ANIMATION_DURATION = 0.375f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Speed of the shell in the animation.
 | 
			
		||||
     */
 | 
			
		||||
    private final static float SHELL_SPEED = 0.3f;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The effect message received from the server.
 | 
			
		||||
     */
 | 
			
		||||
    private final EffectMessage msg;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The shell involved in the animation.
 | 
			
		||||
     */
 | 
			
		||||
    private final Shell shell;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs an AnimationState with the specified game logic, effect message, and shell.
 | 
			
		||||
     *
 | 
			
		||||
     * @param logic the game logic associated with this state
 | 
			
		||||
     * @param msg   the effect message received from the server
 | 
			
		||||
     * @param shell the shell involved in the animation
 | 
			
		||||
     */
 | 
			
		||||
    public AnimationState(ClientGameLogic logic, EffectMessage msg, Shell shell) {
 | 
			
		||||
        super(logic);
 | 
			
		||||
        this.msg = msg;
 | 
			
		||||
        this.shell = shell;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Ends the animation state and transitions to the next state:<br>
 | 
			
		||||
     * - Plays the appropriate sound.<br>
 | 
			
		||||
     * - Updates the affected map.<br>
 | 
			
		||||
     * - Adds destroyed ships to the opponent's map.<br>
 | 
			
		||||
     * - Sends an `AnimationMessage` to the server.<br>
 | 
			
		||||
     * - If the game is over, transitions to `GameOverState` and plays music.<br>
 | 
			
		||||
     * - Otherwise, transitions to `BattleState`.
 | 
			
		||||
     */
 | 
			
		||||
    public void endState() {
 | 
			
		||||
        playSound(msg);
 | 
			
		||||
        affectedMap(msg).add(msg.getShot());
 | 
			
		||||
        affectedMap(msg).remove(shell);
 | 
			
		||||
 | 
			
		||||
        if (destroyedOpponentShip(msg))
 | 
			
		||||
            logic.getOpponentMap().add(msg.getDestroyedShip());
 | 
			
		||||
 | 
			
		||||
        logic.send(new AnimationMessage());
 | 
			
		||||
        if (msg.isGameOver()) {
 | 
			
		||||
            for (Battleship ship : msg.getRemainingOpponentShips()) {
 | 
			
		||||
                logic.getOpponentMap().add(ship);
 | 
			
		||||
            }
 | 
			
		||||
            logic.setState(new GameOverState(logic));
 | 
			
		||||
            if (msg.isOwnShot())
 | 
			
		||||
                logic.playMusic(Music.VICTORY_MUSIC);
 | 
			
		||||
            else
 | 
			
		||||
                logic.playMusic(Music.DEFEAT_MUSIC);
 | 
			
		||||
        } else {
 | 
			
		||||
            logic.setState(new BattleState(logic, msg.isMyTurn()));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * 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);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        if (!msg.isMyTurn())
 | 
			
		||||
            logic.setInfoText("wait.its.not.your.turn");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Updates the state of the animation. This method increments the animationProgress value
 | 
			
		||||
     * until it exceeds a threshold, at which point the state ends.
 | 
			
		||||
     *
 | 
			
		||||
     * @param delta the time elapsed since the last update, in seconds
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void update(float delta) {
 | 
			
		||||
        if (animationProgress > ANIMATION_DURATION) {
 | 
			
		||||
            endState();
 | 
			
		||||
        } else {
 | 
			
		||||
            animationProgress += delta * SHELL_SPEED;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,12 +10,10 @@
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents the state of the client where players take turns to attack each other's ships.
 | 
			
		||||
 */
 | 
			
		||||
@@ -33,11 +31,21 @@ public BattleState(ClientGameLogic logic, boolean myTurn) {
 | 
			
		||||
        this.myTurn = myTurn;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if the battle state should be shown.
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the battle state should be shown, false otherwise
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean showBattle() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles a click on the opponent's map.
 | 
			
		||||
     *
 | 
			
		||||
     * @param pos the position where the click occurred
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void clickOpponentMap(IntPoint pos) {
 | 
			
		||||
        if (!myTurn)
 | 
			
		||||
@@ -53,21 +61,16 @@ else if (logic.getOpponentMap().isValid(pos))
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void receivedEffect(EffectMessage msg) {
 | 
			
		||||
        ClientGameLogic.LOGGER.log(Level.INFO, "report effect: {0}", msg); //NON-NLS
 | 
			
		||||
        playSound(msg);
 | 
			
		||||
        ClientGameLogic.LOGGER.log(System.Logger.Level.INFO, "report effect: {0}", msg); //NON-NLS
 | 
			
		||||
        // Update turn and info text
 | 
			
		||||
        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));
 | 
			
		||||
            if (msg.isOwnShot())
 | 
			
		||||
                logic.playMusic(Music.VICTORY_MUSIC);
 | 
			
		||||
            else
 | 
			
		||||
                logic.playMusic(Music.DEFEAT_MUSIC);
 | 
			
		||||
        }
 | 
			
		||||
        // Add the shell to the affected map
 | 
			
		||||
        Shell shell = new Shell(msg.getShot());
 | 
			
		||||
        affectedMap(msg).add(shell);
 | 
			
		||||
        // Change state to AnimationState
 | 
			
		||||
        logic.playSound(Sound.SHELL_FIRED);
 | 
			
		||||
        logic.setState(new AnimationState(logic, msg, shell));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -79,29 +82,4 @@ public void receivedEffect(EffectMessage msg) {
 | 
			
		||||
    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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
package pp.battleship.game.server;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.BattleshipConfig;
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
@@ -17,7 +18,6 @@
 | 
			
		||||
import pp.battleship.message.server.StartBattleMessage;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
import pp.battleship.model.IntPoint;
 | 
			
		||||
import pp.battleship.model.Rotation;
 | 
			
		||||
 | 
			
		||||
import java.lang.System.Logger;
 | 
			
		||||
import java.lang.System.Logger.Level;
 | 
			
		||||
@@ -36,6 +36,7 @@ public class ServerGameLogic implements ClientInterpreter {
 | 
			
		||||
    private final BattleshipConfig config;
 | 
			
		||||
    private final List<Player> players = new ArrayList<>(2);
 | 
			
		||||
    private final Set<Player> readyPlayers = new HashSet<>();
 | 
			
		||||
    private final Set<Player> finishedAnimation = new HashSet<>();
 | 
			
		||||
    private final ServerSender serverSender;
 | 
			
		||||
    private Player activePlayer;
 | 
			
		||||
    private ServerState state = ServerState.WAIT;
 | 
			
		||||
@@ -192,9 +193,9 @@ private boolean isWithinBounds(Battleship ship) {
 | 
			
		||||
        int width = config.getMapWidth();
 | 
			
		||||
        int height = config.getMapHeight();
 | 
			
		||||
        return minX >= 0 && minX < width &&
 | 
			
		||||
               minY >= 0 && minY < height &&
 | 
			
		||||
               maxX >= 0 && maxX < width &&
 | 
			
		||||
               maxY >= 0 && maxY < height;
 | 
			
		||||
                minY >= 0 && minY < height &&
 | 
			
		||||
                maxX >= 0 && maxX < width &&
 | 
			
		||||
                maxY >= 0 && maxY < height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -226,8 +227,40 @@ private boolean overlaps(List<Battleship> ships) {
 | 
			
		||||
    public void received(ShootMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.BATTLE)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "shoot not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
        else {
 | 
			
		||||
            setState(ServerState.ANIMATION);
 | 
			
		||||
            shoot(getPlayerById(from), msg.getPosition());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of an {@link AnimationMessage}.
 | 
			
		||||
     * Marks the player's animation as finished and transitions the game state if necessary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received {@code AnimationMessage}
 | 
			
		||||
     * @param from the ID of the sender client
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(AnimationMessage msg, int from) {
 | 
			
		||||
        if (state != ServerState.ANIMATION)
 | 
			
		||||
            LOGGER.log(Level.ERROR, "animation not allowed in {0}", state); //NON-NLS
 | 
			
		||||
        else
 | 
			
		||||
            finishedAnimation(getPlayerById(from));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Marks the player's animation as finished and transitions the game state if necessary.
 | 
			
		||||
     *
 | 
			
		||||
     * @param player the player whose animation is finished
 | 
			
		||||
     */
 | 
			
		||||
    private void finishedAnimation(Player player) {
 | 
			
		||||
        if (!finishedAnimation.add(player)) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "{0}'s animation was already finished", player);
 | 
			
		||||
        }
 | 
			
		||||
        if (finishedAnimation.size() == 2) {
 | 
			
		||||
            finishedAnimation.clear();
 | 
			
		||||
            setState(ServerState.BATTLE);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -265,8 +298,7 @@ void shoot(Player p, IntPoint pos) {
 | 
			
		||||
            send(activePlayer, EffectMessage.miss(true, pos));
 | 
			
		||||
            send(otherPlayer, EffectMessage.miss(false, pos));
 | 
			
		||||
            activePlayer = otherPlayer;
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
        } else {
 | 
			
		||||
            // shot hit a ship
 | 
			
		||||
            selectedShip.hit(pos);
 | 
			
		||||
            if (otherPlayer.getMap().getRemainingShips().isEmpty()) {
 | 
			
		||||
@@ -274,13 +306,11 @@ void shoot(Player p, IntPoint pos) {
 | 
			
		||||
                send(activePlayer, EffectMessage.won(pos, selectedShip));
 | 
			
		||||
                send(otherPlayer, EffectMessage.lost(pos, selectedShip, activePlayer.getMap().getRemainingShips()));
 | 
			
		||||
                setState(ServerState.GAME_OVER);
 | 
			
		||||
            }
 | 
			
		||||
            else if (selectedShip.isDestroyed()) {
 | 
			
		||||
            } 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 {
 | 
			
		||||
            } else {
 | 
			
		||||
                // ship has been hit, but it hasn't been destroyed
 | 
			
		||||
                send(activePlayer, EffectMessage.hit(true, pos));
 | 
			
		||||
                send(otherPlayer, EffectMessage.hit(false, pos));
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,11 @@ enum ServerState {
 | 
			
		||||
     */
 | 
			
		||||
    BATTLE,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The server is waiting for clients to finish their animations.
 | 
			
		||||
     */
 | 
			
		||||
    ANIMATION,
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The game has ended because all the ships of one player have been destroyed.
 | 
			
		||||
     */
 | 
			
		||||
 
 | 
			
		||||
@@ -7,10 +7,7 @@
 | 
			
		||||
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.message.client.ClientInterpreter;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.client.*;
 | 
			
		||||
import pp.battleship.model.Battleship;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -63,6 +60,19 @@ public void received(MapMessage msg, int from) {
 | 
			
		||||
        copiedMessage = new MapMessage(msg.getShips().stream().map(Copycat::copy).toList());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Handles the reception of a {@link AnimationMessage}.
 | 
			
		||||
     * Since a {@code AnimationMessage} does not need to be copied, it is directly assigned.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the received {@code AnimationMessage}
 | 
			
		||||
     * @param from the identifier of the sender
 | 
			
		||||
     */
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(AnimationMessage msg, int from) {
 | 
			
		||||
        // copying is not necessary
 | 
			
		||||
        copiedMessage = msg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Creates a copy of the provided {@link Battleship}.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package pp.battleship.game.singlemode;
 | 
			
		||||
 | 
			
		||||
import pp.battleship.game.client.BattleshipClient;
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
import pp.battleship.message.server.EffectMessage;
 | 
			
		||||
@@ -121,6 +122,7 @@ public void received(StartBattleMessage msg) {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void received(EffectMessage msg) {
 | 
			
		||||
        LOGGER.log(Level.INFO, "Received EffectMessage: {0}", msg); //NON-NLS
 | 
			
		||||
        connection.sendRobotMessage(new AnimationMessage());
 | 
			
		||||
        if (msg.isMyTurn())
 | 
			
		||||
            shoot();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,27 @@
 | 
			
		||||
package pp.battleship.message.client;
 | 
			
		||||
 | 
			
		||||
import com.jme3.network.serializing.Serializable;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A message indicating an animation event is finished in the game. (Client → Server)
 | 
			
		||||
 */
 | 
			
		||||
@Serializable
 | 
			
		||||
public class AnimationMessage extends ClientMessage {
 | 
			
		||||
    /**
 | 
			
		||||
     * Constructs a new AnimationMessage instance.
 | 
			
		||||
     */
 | 
			
		||||
    public AnimationMessage() {
 | 
			
		||||
        super();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Accepts a visitor for processing 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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,4 +26,12 @@ public interface ClientInterpreter {
 | 
			
		||||
     * @param from the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    void received(MapMessage msg, int from);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Processes a received AnimationMessage.
 | 
			
		||||
     *
 | 
			
		||||
     * @param msg  the AnimationMessage to be processed
 | 
			
		||||
     * @param from the connection ID from which the message was received
 | 
			
		||||
     */
 | 
			
		||||
    void received(AnimationMessage msg, int from);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,32 @@
 | 
			
		||||
package pp.battleship.model;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Represents a shell in the Battleship game.
 | 
			
		||||
 */
 | 
			
		||||
public class Shell implements Item {
 | 
			
		||||
    private final int x;
 | 
			
		||||
    private final int y;
 | 
			
		||||
 | 
			
		||||
    public Shell(Shot shot) {
 | 
			
		||||
        this.x = shot.getX();
 | 
			
		||||
        this.y = shot.getY();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getX() {
 | 
			
		||||
        return x;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public int getY() {
 | 
			
		||||
        return y;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public <T> T accept(Visitor<T> visitor) {
 | 
			
		||||
        return visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void accept(VoidVisitor visitor) {
 | 
			
		||||
        visitor.visit(this);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -91,6 +91,15 @@ public void add(Shot shot) {
 | 
			
		||||
        addItem(shot);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a shell to the map and triggers an item addition event.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the shell to be added to the map
 | 
			
		||||
     */
 | 
			
		||||
    public void add(Shell shell) {
 | 
			
		||||
        addItem(shell);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes an item from the map and triggers an item removal event.
 | 
			
		||||
     *
 | 
			
		||||
@@ -181,8 +190,8 @@ public int getHeight() {
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isValid(Battleship ship) {
 | 
			
		||||
        return isValid(ship.getMinX(), ship.getMinY()) &&
 | 
			
		||||
               isValid(ship.getMaxX(), ship.getMaxY()) &&
 | 
			
		||||
               getShips().filter(s -> s != ship).noneMatch(ship::collidesWith);
 | 
			
		||||
                isValid(ship.getMaxX(), ship.getMaxY()) &&
 | 
			
		||||
                getShips().filter(s -> s != ship).noneMatch(ship::collidesWith);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -194,8 +203,8 @@ public boolean isValid(Battleship ship) {
 | 
			
		||||
     */
 | 
			
		||||
    public Battleship findShipAt(int x, int y) {
 | 
			
		||||
        return getShips().filter(ship -> ship.contains(x, y))
 | 
			
		||||
                         .findAny()
 | 
			
		||||
                         .orElse(null);
 | 
			
		||||
                .findAny()
 | 
			
		||||
                .orElse(null);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -227,7 +236,7 @@ public boolean isValid(IntPosition pos) {
 | 
			
		||||
     */
 | 
			
		||||
    public boolean isValid(int x, int y) {
 | 
			
		||||
        return x >= 0 && x < width &&
 | 
			
		||||
               y >= 0 && y < height;
 | 
			
		||||
                y >= 0 && y < height;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -28,4 +28,12 @@ public interface Visitor<T> {
 | 
			
		||||
     * @return the result of visiting the Battleship element
 | 
			
		||||
     */
 | 
			
		||||
    T visit(Battleship ship);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Shell element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the Shell element to visit
 | 
			
		||||
     * @return the result of visiting the Shell element
 | 
			
		||||
     */
 | 
			
		||||
    T visit(Shell shell);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,4 +25,11 @@ public interface VoidVisitor {
 | 
			
		||||
     * @param ship the Battleship element to visit
 | 
			
		||||
     */
 | 
			
		||||
    void visit(Battleship ship);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Visits a Shell element.
 | 
			
		||||
     *
 | 
			
		||||
     * @param shell the Shell element to visit
 | 
			
		||||
     */
 | 
			
		||||
    void visit(Shell shell);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,5 +22,9 @@ public enum Sound {
 | 
			
		||||
    /**
 | 
			
		||||
     * Sound of a ship being destroyed.
 | 
			
		||||
     */
 | 
			
		||||
    DESTROYED_SHIP
 | 
			
		||||
    DESTROYED_SHIP,
 | 
			
		||||
    /**
 | 
			
		||||
     * Sound of a shot being fired.
 | 
			
		||||
     */
 | 
			
		||||
    SHELL_FIRED
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@
 | 
			
		||||
import pp.battleship.game.server.Player;
 | 
			
		||||
import pp.battleship.game.server.ServerGameLogic;
 | 
			
		||||
import pp.battleship.game.server.ServerSender;
 | 
			
		||||
import pp.battleship.message.client.AnimationMessage;
 | 
			
		||||
import pp.battleship.message.client.ClientMessage;
 | 
			
		||||
import pp.battleship.message.client.MapMessage;
 | 
			
		||||
import pp.battleship.message.client.ShootMessage;
 | 
			
		||||
@@ -56,8 +57,7 @@ public class BattleshipServer implements MessageListener<HostedConnection>, Conn
 | 
			
		||||
        try {
 | 
			
		||||
            manager.readConfiguration(new FileInputStream("logging.properties"));
 | 
			
		||||
            LOGGER.log(Level.INFO, "Successfully read logging properties"); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.INFO, e.getMessage());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -92,8 +92,7 @@ private void startServer() {
 | 
			
		||||
            myServer.start();
 | 
			
		||||
            registerListeners();
 | 
			
		||||
            LOGGER.log(Level.INFO, "Server started: {0}", myServer.isRunning()); //NON-NLS
 | 
			
		||||
        }
 | 
			
		||||
        catch (IOException e) {
 | 
			
		||||
        } catch (IOException e) {
 | 
			
		||||
            LOGGER.log(Level.ERROR, "Couldn't start server: {0}", e.getMessage()); //NON-NLS
 | 
			
		||||
            exit(1);
 | 
			
		||||
        }
 | 
			
		||||
@@ -102,8 +101,7 @@ private void startServer() {
 | 
			
		||||
    private void processNextMessage() {
 | 
			
		||||
        try {
 | 
			
		||||
            pendingMessages.take().process(logic);
 | 
			
		||||
        }
 | 
			
		||||
        catch (InterruptedException ex) {
 | 
			
		||||
        } catch (InterruptedException ex) {
 | 
			
		||||
            LOGGER.log(Level.INFO, "Interrupted while waiting for messages"); //NON-NLS
 | 
			
		||||
            Thread.currentThread().interrupt();
 | 
			
		||||
        }
 | 
			
		||||
@@ -114,6 +112,7 @@ private void initializeSerializables() {
 | 
			
		||||
        Serializer.registerClass(StartBattleMessage.class);
 | 
			
		||||
        Serializer.registerClass(MapMessage.class);
 | 
			
		||||
        Serializer.registerClass(ShootMessage.class);
 | 
			
		||||
        Serializer.registerClass(AnimationMessage.class);
 | 
			
		||||
        Serializer.registerClass(EffectMessage.class);
 | 
			
		||||
        Serializer.registerClass(Battleship.class);
 | 
			
		||||
        Serializer.registerClass(IntPoint.class);
 | 
			
		||||
@@ -123,6 +122,7 @@ private void initializeSerializables() {
 | 
			
		||||
    private void registerListeners() {
 | 
			
		||||
        myServer.addMessageListener(this, MapMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, ShootMessage.class);
 | 
			
		||||
        myServer.addMessageListener(this, AnimationMessage.class);
 | 
			
		||||
        myServer.addConnectionListener(this);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user