diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/BoardSynchronizer.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/BoardSynchronizer.java index f1819e4..007389d 100644 --- a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/BoardSynchronizer.java +++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/BoardSynchronizer.java @@ -17,7 +17,7 @@ import pp.view.ModelViewSynchronizer; * are accurately reflected in the view. */ abstract class BoardSynchronizer extends ModelViewSynchronizer implements Visitor, GameEventListener { - private final Board board; + protected final Board board; /** * Constructs a new BoardSynchronizer. diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/GameBoardSynchronizer.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/GameBoardSynchronizer.java new file mode 100644 index 0000000..fe5e774 --- /dev/null +++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/GameBoardSynchronizer.java @@ -0,0 +1,126 @@ +//////////////////////////////////////// +// Programming project code +// UniBw M, 2022, 2023, 2024 +// www.unibw.de/inf2 +// (c) Mark Minas (mark.minas@unibw.de) +//////////////////////////////////////// + +package pp.monopoly.client.gui; + +import com.jme3.material.Material; +import com.jme3.material.RenderState.BlendMode; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +import pp.monopoly.client.MonopolyApp; +import pp.monopoly.game.server.PlayerColor; +import pp.monopoly.model.Board; +import pp.monopoly.model.Figure; +import pp.monopoly.model.Rotation; + +import static pp.util.FloatMath.HALF_PI; +import static pp.util.FloatMath.PI; + +/** + * The {@code GameBoardSynchronizer} class is responsible for synchronizing the graphical + * representation of the ships and shots on the sea map with the underlying data model. + * It extends the {@link BoardSynchronizer} to provide specific synchronization + * logic for the sea map. + */ +class GameBoardSynchronizer extends BoardSynchronizer { + private static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; //NON-NLS + private static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; + private static final String COLOR = "Color"; //NON-NLS + private static final String FIGURE = "figure"; //NON-NLS + + private final MonopolyApp app; + private final ParticleEffectFactory particleFactory; + + /** + * Constructs a {@code GameBoardSynchronizer} object with the specified application, root node, and ship map. + * + * @param app the Battleship application + * @param root the root node to which graphical elements will be attached + * @param map the ship map containing the ships and shots + */ + public GameBoardSynchronizer(MonopolyApp app, Node root, Board board) { + super(board, root); + this.app = app; + this.particleFactory = new ParticleEffectFactory(app); + addExisting(); + } + + /** + * Visits a {@link Battleship} and creates a graphical representation of it. + * The representation is either a 3D model or a simple box depending on the + * type of battleship. + * + * @param ship the battleship to be represented + * @return the node containing the graphical representation of the battleship + */ + @Override + public Spatial visit(Figure figure) { + final Node node = new Node(FIGURE); + node.attachChild(createBox(figure)); + // compute the center of the ship in world coordinates + final float x = 1; + final float z = 1; + node.setLocalTranslation(x, 0f, z); + return node; + } + + /** + * 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(Figure figure) { + final Box box = new Box(0.5f * (figure.getMaxY() - figure.getMinY()) + 0.3f, + 0.3f, + 0.5f * (figure.getMaxX() - figure.getMinX()) + 0.3f); + final Geometry geometry = new Geometry(FIGURE, box); + geometry.setMaterial(createColoredMaterial(PlayerColor.PINK.getColor())); + 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; + } + + /** + * Calculates the rotation angle for the specified rotation. + * + * @param rot the rotation of the battleship + * @return the rotation angle in radians + */ + private static float calculateRotationAngle(Rotation rot) { + return switch (rot) { + case RIGHT -> HALF_PI; + case DOWN -> 0f; + case LEFT -> -HALF_PI; + case UP -> PI; + }; + } +} diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/MapViewSynchronizer.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/MapViewSynchronizer.java index c239394..d7c3ff9 100644 --- a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/MapViewSynchronizer.java +++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/MapViewSynchronizer.java @@ -1,5 +1,9 @@ package pp.monopoly.client.gui; +import com.jme3.scene.Spatial; + +import pp.monopoly.model.Figure; + /** * Synchronizes the visual representation of the board with the game model. * Handles updates for items on the board. @@ -33,4 +37,9 @@ class MapViewSynchronizer extends BoardSynchronizer { protected void disableState() { view.getNode().detachAllChildren(); // Entfernt alle visuellen Elemente vom Knoten } + + @Override + public Spatial visit(Figure figure) { + return figure.accept(this); + } } diff --git a/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/ParticleEffectFactory.java b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/ParticleEffectFactory.java new file mode 100644 index 0000000..f1e18bf --- /dev/null +++ b/Projekte/monopoly/client/src/main/java/pp/monopoly/client/gui/ParticleEffectFactory.java @@ -0,0 +1,27 @@ +package pp.monopoly.client.gui; + +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh.Type; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import pp.monopoly.client.MonopolyApp; + +/** + * Factory class responsible for creating particle effects used in the game. + * This centralizes the creation of various types of particle emitters. + */ +public class ParticleEffectFactory { + private static final int COUNT_FACTOR = 1; + private static final float COUNT_FACTOR_F = 1f; + private static final boolean POINT_SPRITE = true; + private static final Type EMITTER_TYPE = POINT_SPRITE ? Type.Point : Type.Triangle; + + private final MonopolyApp app; + + ParticleEffectFactory(MonopolyApp app) { + this.app = app; + } +} \ No newline at end of file diff --git a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Board.java b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Board.java index e46308f..31ee634 100644 --- a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Board.java +++ b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Board.java @@ -57,6 +57,7 @@ public class Board { this.width = width; this.height = height; this.eventBroker = eventBroker; + addItem(new Figure(5, 5, 5, Rotation.LEFT)); } /** diff --git a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Figure.java b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Figure.java index b27f06f..d076acd 100644 --- a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Figure.java +++ b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Figure.java @@ -1,17 +1,309 @@ package pp.monopoly.model; -public class Figure implements Item{ +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; - @Override - public T accept(Visitor visitor) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'accept'"); +import static java.lang.Math.max; +import static java.lang.Math.min; + +public class Figure implements Item{ + /** + * Enumeration representing the different statuses a Figure can have during the game. + */ + public enum Status { + /** + * The ship is in its normal state, not being previewed for placement. + */ + NORMAL, + + /** + * The ship is being previewed in a valid position for placement. + */ + VALID_PREVIEW, + + /** + * The ship is being previewed in an invalid position for placement. + */ + INVALID_PREVIEW } + private final int length; // The length of the Figure + private int x; // The x-coordinate of the Figure's position + private int y; // The y-coordinate of the Figure's position + private Rotation rot; // The rotation of the Figure + private Status status; // The current status of the Figure + private final Set damaged = new HashSet<>(); // The set of positions that have been hit on this ship + + /** + * Default constructor for serialization. Initializes a Figure with length 0, + * at position (0, 0), with a default rotation of RIGHT. + */ + private Figure() { + this(0, 0, 0, Rotation.RIGHT); + } + + /** + * Constructs a new Figure with the specified length, position, and rotation. + * + * @param length the length of the Figure + * @param x the x-coordinate of the Figure's initial position + * @param y the y-coordinate of the Figure's initial position + * @param rot the rotation of the Figure + */ + public Figure(int length, int x, int y, Rotation rot) { + this.x = x; + this.y = y; + this.rot = rot; + this.length = length; + this.status = Status.NORMAL; + } + + /** + * Returns the current x-coordinate of the Figure's position. + * + * @return the x-coordinate of the Figure + */ + public int getX() { + return x; + } + + /** + * Returns the current y-coordinate of the Figure's position. + * + * @return the y-coordinate of the Figure + */ + public int getY() { + return y; + } + + /** + * Moves the Figure to the specified coordinates. + * + * @param x the new x-coordinate of the Figure's position + * @param y the new y-coordinate of the Figure's position + */ + public void moveTo(int x, int y) { + this.x = x; + this.y = y; + } + + /** + * Moves the Figure to the specified position. + * + * @param pos the new position of the Figure + */ + public void moveTo(IntPosition pos) { + moveTo(pos.getX(), pos.getY()); + } + + /** + * Returns the current status of the Figure. + * + * @return the status of the Figure + */ + public Status getStatus() { + return status; + } + + /** + * Sets the status of the Figure. + * + * @param status the new status to be set for the Figure + */ + public void setStatus(Status status) { + this.status = status; + } + + /** + * Returns the length of the Figure. + * + * @return the length of the Figure + */ + public int getLength() { + return length; + } + + /** + * Returns the minimum x-coordinate that the Figure occupies based on its current position and rotation. + * + * @return the minimum x-coordinate of the Figure + */ + public int getMinX() { + return x + min(0, (length - 1) * rot.dx()); + } + + /** + * Returns the maximum x-coordinate that the Figure occupies based on its current position and rotation. + * + * @return the maximum x-coordinate of the Figure + */ + public int getMaxX() { + return x + max(0, (length - 1) * rot.dx()); + } + + /** + * Returns the minimum y-coordinate that the Figure occupies based on its current position and rotation. + * + * @return the minimum y-coordinate of the Figure + */ + public int getMinY() { + return y + min(0, (length - 1) * rot.dy()); + } + + /** + * Returns the maximum y-coordinate that the Figure occupies based on its current position and rotation. + * + * @return the maximum y-coordinate of the Figure + */ + public int getMaxY() { + return y + max(0, (length - 1) * rot.dy()); + } + + /** + * Returns the current rotation of the Figure. + * + * @return the rotation of the Figure + */ + public Rotation getRot() { + return rot; + } + + /** + * Sets the rotation of the Figure. + * + * @param rot the new rotation to be set for the Figure + */ + public void setRotation(Rotation rot) { + this.rot = rot; + } + + /** + * Rotates the Figure by 90 degrees clockwise. + */ + public void rotated() { + setRotation(rot.rotate()); + } + + /** + * Attempts to hit the Figure at the specified position. + * If the position is part of the Figure, the hit is recorded. + * + * @param x the x-coordinate of the position to hit + * @param y the y-coordinate of the position to hit + * @return true if the position is part of the Figure, false otherwise + * @see #contains(int, int) + */ + public boolean hit(int x, int y) { + if (!contains(x, y)) + return false; + damaged.add(new IntPoint(x, y)); + return true; + } + + /** + * Attempts to hit the Figure at the specified position. + * If the position is part of the Figure, the hit is recorded. + * This is a convenience method for {@linkplain #hit(int, int)}. + * + * @param position the position to hit + * @return true if the position is part of the Figure, false otherwise + */ + public boolean hit(IntPosition position) { + return hit(position.getX(), position.getY()); + } + + /** + * Returns the positions of this Figure that have been hit. + * + * @return a set of positions that have been hit + * @see #hit(int, int) + */ + public Set getDamaged() { + return Collections.unmodifiableSet(damaged); + } + + /** + * Checks whether the specified position is covered by the Figure. This method does + * not record a hit, only checks coverage. + * This is a convenience method for {@linkplain #contains(int, int)}. + * + * @param pos the position to check + * @return true if the position is covered by the Figure, false otherwise + */ + public boolean contains(IntPosition pos) { + return contains(pos.getX(), pos.getY()); + } + + /** + * Checks whether the specified position is covered by the Figure. This method does + * not record a hit, only checks coverage. + * + * @param x the x-coordinate of the position to check + * @param y the y-coordinate of the position to check + * @return true if the position is covered by the Figure, false otherwise + */ + public boolean contains(int x, int y) { + return getMinX() <= x && x <= getMaxX() && + getMinY() <= y && y <= getMaxY(); + } + + /** + * Determines if the Figure has been completely destroyed. A Figure is considered + * destroyed if all of its positions have been hit. + * + * @return true if the Figure is destroyed, false otherwise + * @see #hit(int, int) + */ + public boolean isDestroyed() { + return damaged.size() == length; + } + + /** + * Checks whether this Figure collides with another Figure. Two Figures collide + * if any of their occupied positions overlap. + * + * @param other the other Figure to check collision with + * @return true if the Figures collide, false otherwise + */ + public boolean collidesWith(Figure other) { + return other.getMaxX() >= getMinX() && getMaxX() >= other.getMinX() && + other.getMaxY() >= getMinY() && getMaxY() >= other.getMinY(); + } + + /** + * Returns a string representation of the Figure, including its length, position, + * and rotation. + * + * @return a string representation of the Figure + */ + @Override + public String toString() { + return "Ship{length=" + length + ", x=" + x + ", y=" + y + ", rot=" + rot + '}'; //NON-NLS + } + + /** + * Accepts a visitor that returns a value of type {@code T}. This method is part of the + * Visitor design pattern. + * + * @param visitor the visitor to accept + * @param the type of the value returned by the visitor + * @return the value returned by the visitor + */ + @Override + public T accept(Visitor visitor) { + return visitor.visit(this); + } + + /** + * Accepts a visitor that does not return a value. This method is part of the + * Visitor design pattern. + * + * @param visitor the visitor to accept + */ @Override public void accept(VoidVisitor visitor) { - // TODO Auto-generated method stub - throw new UnsupportedOperationException("Unimplemented method 'accept'"); + visitor.visit(this); } } diff --git a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Visitor.java b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Visitor.java index ed5deec..bba2ace 100644 --- a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Visitor.java +++ b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/Visitor.java @@ -14,4 +14,12 @@ package pp.monopoly.model; */ public interface Visitor { + /** + * Visits a Figure element. + * + * @param figure the figure element to visit + * @return the result of visiting the figure element + */ + T visit(Figure figure); + } diff --git a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/VoidVisitor.java b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/VoidVisitor.java index 471421d..0654feb 100644 --- a/Projekte/monopoly/model/src/main/java/pp/monopoly/model/VoidVisitor.java +++ b/Projekte/monopoly/model/src/main/java/pp/monopoly/model/VoidVisitor.java @@ -12,5 +12,11 @@ package pp.monopoly.model; * without returning any result. */ public interface VoidVisitor { + /** + * Visits a Figure element. + * + * @param figure the Figure element to visit + */ + void visit(Figure figure); }