added server-side and client-side validation for JSON files

- added the client-side validation in the EditorState class
- added the server-side validation in the WaitState and ServerGameLogic class
- added Getter in the ShipMapDTO
- added the 'map.invalid' in the properties
This commit is contained in:
Daniel Grigencha
2024-10-05 05:32:26 +02:00
parent 0d2781dbe4
commit eda4f06a75
6 changed files with 174 additions and 2 deletions

View File

@@ -10,12 +10,14 @@
import pp.battleship.message.client.MapMessage;
import pp.battleship.model.Battleship;
import pp.battleship.model.IntPoint;
import pp.battleship.model.Rotation;
import pp.battleship.model.ShipMap;
import pp.battleship.model.dto.ShipMapDTO;
import java.io.File;
import java.io.IOException;
import java.lang.System.Logger.Level;
import java.util.List;
import static pp.battleship.Resources.lookup;
import static pp.battleship.model.Battleship.Status.INVALID_PREVIEW;
@@ -238,6 +240,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"));
if (!validMap(dto))
throw new IOException(lookup("map.invalid"));
ownMap().clear();
dto.getShips().forEach(ownMap()::add);
harbor().clear();
@@ -264,4 +268,70 @@ public boolean mayLoadMap() {
public boolean maySaveMap() {
return harbor().getItems().isEmpty();
}
/**
* Validates the given ShipMapDTO by checking if all ships are within bounds
* and do not overlap with each other.
*
* @param dto the ShipMapDTO to validate
* @return true if the map is valid, false otherwise
*/
private boolean validMap(ShipMapDTO dto) {
return inBounds(dto) && !overlaps(dto);
}
/**
* Checks if all ships in the given ShipMapDTO are within the bounds of the map.
*
* @param dto the ShipMapDTO to validate
* @return true if all ships are within bounds, false otherwise
*/
private boolean inBounds(ShipMapDTO dto) {
List<Battleship> ships = dto.getShips();
for (Battleship ship : ships) {
if (!isWithinBounds(ship, dto.getWidth(), dto.getHeight())) {
return false;
}
}
return true;
}
/**
* Checks if the given ship is within the bounds of the map.
*
* @param ship the Battleship to check
* @param width the width of the map
* @param height the height of the map
* @return true if the ship is within bounds, false otherwise
*/
private boolean isWithinBounds(Battleship ship, int width, int height) {
int x1 = ship.getX();
int y1 = ship.getY();
Rotation r = ship.getRot();
int x2 = x1 + ship.getLength() * r.dx();
int y2 = y1 + ship.getLength() * r.dy();
return x1 >= 0 && y1 >= 0 && x1 <= width && y1 <= height &&
x2 >= 0 && y2 >= 0 && x2 <= width && y2 <= height;
}
/**
* Checks if any ships in the given ShipMapDTO overlap with each other.
*
* @param dto the ShipMapDTO to validate
* @return true if any ships overlap, false otherwise
*/
private boolean overlaps(ShipMapDTO dto) {
List<Battleship> ships = dto.getShips();
for (int i = 0; i < ships.size(); i++) {
Battleship ship1 = ships.get(i);
for (int j = i + 1; j < ships.size(); j++) {
Battleship ship2 = ships.get(j);
if (ship1.collidesWith(ship2)) {
return true; // Collision detected
}
}
}
return false;
}
}

View File

@@ -7,6 +7,7 @@
package pp.battleship.game.client;
import pp.battleship.message.server.GameDetails;
import pp.battleship.message.server.StartBattleMessage;
import java.lang.System.Logger.Level;
@@ -38,4 +39,17 @@ public void receivedStartBattle(StartBattleMessage msg) {
logic.setInfoText(msg.getInfoTextKey());
logic.setState(new BattleState(logic, msg.isMyTurn()));
}
/**
* Handles the GameDetails message received from the server.
* If the map is invalid, the editor state is set.
*
* @param msg the GameDetails message received
*/
@Override
public void receivedGameDetails(GameDetails msg) {
ClientGameLogic.LOGGER.log(Level.WARNING, "Invalid Map"); //NON-NLS
logic.setInfoText("map.invalid");
logic.setState(new EditorState(logic));
}
}

View File

@@ -17,6 +17,7 @@
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;
@@ -142,8 +143,75 @@ 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
else if (validMap(msg))
playerReady(getPlayerById(from), msg.getShips());
else {
LOGGER.log(Level.ERROR, "map does not fit game details"); //NON-NLS
send(getPlayerById(from), new GameDetails(config));
}
}
/**
* Validates the received map message by checking if all ships are within bounds
* and do not overlap with each other.
*
* @param msg the received MapMessage containing the ships
* @return true if the map is valid, false otherwise
*/
private boolean validMap(MapMessage msg) {
List<Battleship> ships = msg.getShips();
return inBounds(ships) && !overlaps(ships);
}
/**
* Checks if all ships in the given list are within the bounds of the map.
*
* @param ships the list of Battleships to validate
* @return true if all ships are within bounds, false otherwise
*/
private boolean inBounds(List<Battleship> ships) {
for (Battleship ship : ships) {
if (!isWithinBounds(ship)) {
return false;
}
}
return true;
}
/**
* Checks if the given ship is within the bounds of the map.
*
* @param ship the Battleship to check
* @return true if the ship is within bounds, false otherwise
*/
private boolean isWithinBounds(Battleship ship) {
int x1 = ship.getX();
int y1 = ship.getY();
Rotation r = ship.getRot();
int x2 = x1 + ship.getLength() * r.dx();
int y2 = y1 + ship.getLength() * r.dy();
return x1 >= 0 && y1 >= 0 && x1 <= config.getMapWidth() && y1 <= config.getMapHeight() &&
x2 >= 0 && y2 >= 0 && x2 <= config.getMapWidth() && y2 <= config.getMapHeight();
}
/**
* Checks if any ships in the given list overlap with each other.
*
* @param ships the list of Battleships to validate
* @return true if any ships overlap, false otherwise
*/
private boolean overlaps(List<Battleship> ships) {
for (int i = 0; i < ships.size(); i++) {
Battleship ship1 = ships.get(i);
for (int j = i + 1; j < ships.size(); j++) {
Battleship ship2 = ships.get(j);
if (ship1.collidesWith(ship2)) {
return true; // Collision detected
}
}
}
return false;
}
/**

View File

@@ -114,4 +114,22 @@ public static ShipMapDTO loadFrom(File file) throws IOException {
throw new IOException(e.getLocalizedMessage());
}
}
/**
* Returns the width of the ship map.
*
* @return the width of the ship map
*/
public int getWidth() {
return width;
}
/**
* Returns the height of the ship map.
*
* @return the height of the ship map
*/
public int getHeight() {
return height;
}
}

View File

@@ -37,3 +37,4 @@ dialog.error=Error
dialog.question=Question
port.must.be.integer=Port must be an integer number
map.doesnt.fit=The map doesn't fit to this game
map.invalid=The map is invalid

View File

@@ -27,7 +27,7 @@ host.name=Host
port.number=Port
wait.its.not.your.turn=Warte, Du bist nicht dran!!
menu.quit=Spiel beenden
menu.return-to-game=Zurück zum Spiel
menu.return-to-game=Zur<EFBFBD>ck zum Spiel
menu.sound-enabled=Sound eingeschaltet
menu.map.load=Karte von Datei laden...
menu.map.save=Karte in Datei speichern...
@@ -37,3 +37,4 @@ dialog.error=Fehler
dialog.question=Frage
port.must.be.integer=Der Port muss eine ganze Zahl sein
map.doesnt.fit=Diese Karte passt nicht zu diesem Spiel
map.invalid=Die Karte ist ung<6E>ltig