Figure Syncronizer now added

This commit is contained in:
Johannes Schmelz 2024-11-18 05:38:04 +01:00
parent 8e3cb43244
commit 0e580a3180
6 changed files with 473 additions and 8 deletions

View File

@ -17,7 +17,7 @@ import pp.view.ModelViewSynchronizer;
* are accurately reflected in the view.
*/
abstract class BoardSynchronizer extends ModelViewSynchronizer<Item> implements Visitor<Spatial>, GameEventListener {
private final Board board;
protected final Board board;
/**
* Constructs a new BoardSynchronizer.

View File

@ -0,0 +1,132 @@
////////////////////////////////////////
// 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.effect.ParticleEmitter;
import com.jme3.material.Material;
import com.jme3.material.RenderState.BlendMode;
import com.jme3.math.ColorRGBA;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Cylinder;
import groovyjarjarantlr4.v4.parse.ANTLRParser.finallyClause_return;
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.Item;
import pp.monopoly.model.Rotation;
import static java.util.Objects.requireNonNull;
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;
};
}
}

View File

@ -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;
}
}

View File

@ -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;
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<IntPoint> 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<IntPoint> 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 <T> the type of the value returned by the visitor
* @return the value returned by the visitor
*/
@Override
public <T> T accept(Visitor<T> visitor) {
// TODO Auto-generated method stub
throw new UnsupportedOperationException("Unimplemented method 'accept'");
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);
}
}

View File

@ -14,4 +14,12 @@ package pp.monopoly.model;
*/
public interface Visitor<T> {
/**
* Visits a Figure element.
*
* @param figure the figure element to visit
* @return the result of visiting the figure element
*/
T visit(Figure figure);
}

View File

@ -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);
}