diff --git a/OGP1718-Worms/.project b/OGP1718-Worms/.project index 5657ca3..7c6ada6 100644 --- a/OGP1718-Worms/.project +++ b/OGP1718-Worms/.project @@ -20,5 +20,10 @@ 2 PROJECT_LOC/images + + resources/levels + 2 + PROJECT_LOC/levels + diff --git a/OGP1718-Worms/images/burger.png b/OGP1718-Worms/images/burger.png new file mode 100644 index 0000000..95854eb Binary files /dev/null and b/OGP1718-Worms/images/burger.png differ diff --git a/OGP1718-Worms/levels/Blocks.lvl b/OGP1718-Worms/levels/Blocks.lvl new file mode 100644 index 0000000..7413ebf --- /dev/null +++ b/OGP1718-Worms/levels/Blocks.lvl @@ -0,0 +1,3 @@ +# A map with blocks +map:Blocks.png +height:20 \ No newline at end of file diff --git a/OGP1718-Worms/levels/Blocks.png b/OGP1718-Worms/levels/Blocks.png new file mode 100644 index 0000000..7d603ad Binary files /dev/null and b/OGP1718-Worms/levels/Blocks.png differ diff --git a/OGP1718-Worms/levels/Simple.lvl b/OGP1718-Worms/levels/Simple.lvl new file mode 100644 index 0000000..baeb7e0 --- /dev/null +++ b/OGP1718-Worms/levels/Simple.lvl @@ -0,0 +1,3 @@ +# A simple map +map:Simple.png +height:20 diff --git a/OGP1718-Worms/levels/Simple.png b/OGP1718-Worms/levels/Simple.png new file mode 100644 index 0000000..ba94492 Binary files /dev/null and b/OGP1718-Worms/levels/Simple.png differ diff --git a/OGP1718-Worms/levels/Skulls-lowres.lvl b/OGP1718-Worms/levels/Skulls-lowres.lvl new file mode 100644 index 0000000..458e86c --- /dev/null +++ b/OGP1718-Worms/levels/Skulls-lowres.lvl @@ -0,0 +1,4 @@ +# A smaller version of the skulls map, +# which may improve performance +map:Skulls-lowres.png +height:20 diff --git a/OGP1718-Worms/levels/Skulls-lowres.png b/OGP1718-Worms/levels/Skulls-lowres.png new file mode 100644 index 0000000..50c8916 Binary files /dev/null and b/OGP1718-Worms/levels/Skulls-lowres.png differ diff --git a/OGP1718-Worms/levels/Skulls.lvl b/OGP1718-Worms/levels/Skulls.lvl new file mode 100644 index 0000000..2c15e2a --- /dev/null +++ b/OGP1718-Worms/levels/Skulls.lvl @@ -0,0 +1,3 @@ +# A large and complex map +map:Skulls.png +height:20 diff --git a/OGP1718-Worms/levels/Skulls.png b/OGP1718-Worms/levels/Skulls.png new file mode 100644 index 0000000..3907c49 Binary files /dev/null and b/OGP1718-Worms/levels/Skulls.png differ diff --git a/OGP1718-Worms/levels/levels.txt b/OGP1718-Worms/levels/levels.txt new file mode 100644 index 0000000..3dfd07b --- /dev/null +++ b/OGP1718-Worms/levels/levels.txt @@ -0,0 +1,4 @@ +Blocks.lvl +Simple.lvl +Skulls-lowres.lvl +Skulls.lvl \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/facade/IFacade.java b/OGP1718-Worms/src-provided/worms/facade/IFacade.java index 98b70ee..b9b1ff1 100644 --- a/OGP1718-Worms/src-provided/worms/facade/IFacade.java +++ b/OGP1718-Worms/src-provided/worms/facade/IFacade.java @@ -1,7 +1,10 @@ package worms.facade; -import worms.model.Worm; +import java.math.BigInteger; +import java.util.*; +import worms.model.*; import worms.util.ModelException; +import worms.util.MustNotImplementException; /** * Implement this interface to connect your code to the graphical user interface @@ -18,6 +21,12 @@ import worms.util.ModelException; * @@ -31,8 +40,8 @@ import worms.util.ModelException; * *
  • Each method defined in the interface IFacade must be * implemented by the class Facade. For example, the implementation - * of getX should call a method of the given worm to - * retrieve its x-coordinate.
  • + * of getMass should call a method of the given worm to + * retrieve its mass. * *
  • Your Facade class should offer a default constructor.
  • * @@ -63,106 +72,452 @@ import worms.util.ModelException; */ public interface IFacade { - /** - * Create and return a new worm that is positioned at the given location, looks - * in the given direction, has the given radius and the given name. - * - * @param coordinates - * An array containing the x-coordinate of the position of the new - * worm followed by the y-coordinate of the position of the new worm - * (in meter) - * @param direction - * The direction of the new worm (in radians) - * @param radius - * The radius of the new worm (in meter) - * @param name - * The name of the new worm - */ - Worm createWorm(double[] location, double direction, double radius, String name) throws ModelException; + + /************ + * WORLD + ************/ /** - * Moves the given worm by the given number of steps. + * Create a new world with given width and given height. + * The passable map is a rectangular matrix indicating which parts of the terrain + * are passable and which parts are impassable. + * This matrix is derived from the transparency of the pixels in the image file + * of the terrain. passableMap[r][c] is true if the location at row r and column c + * is passable, and false if that location is impassable. + * The elements in the first row (row 0) represent the pixels at the top of the + * terrain (i.e., largest y-coordinates). + * The elements in the last row (row passableMap.length-1) represent pixels at the + * bottom of the terrain (smallest y-coordinates). + * The elements in the first column (column 0) represent the pixels at the left + * of the terrain (i.e., smallest x-coordinates). + * The elements in the last column (column passableMap[0].length-1) represent the + * pixels at the right of the terrain (i.e., largest x-coordinates). */ - void move(Worm worm, int nbSteps) throws ModelException; + public World createWorld(double width, double height, + boolean[][] passableMap) throws ModelException; + + /** + * Terminate the given world. + */ + void terminate(World world) throws ModelException; + + /** + * Check whether the given worls is terminated. + */ + boolean isTerminated(World world) throws ModelException; + + /** + * Return the width of the given world. + */ + public double getWorldWidth(World world) throws ModelException; + + /** + * Return the height of the given world. + */ + public double getWorldHeight(World world) throws ModelException; + + /** + * Check whether the given world is passable at the given location. + * - The location is an array containing the x-coordinate of the location to + * check followed by the y-coordinate of that location. + * - Locations outside the boundaries of the world are always passable. + */ + boolean isPassable(World world, double[] location) throws ModelException; /** - * Turns the given worm by the given angle. + * Check whether the circular area with given center and given radius + * is passable in the given world. + * - The circular area must not lie completely within the given world. */ - void turn(Worm worm, double angle) throws ModelException; + boolean isPassable(World world, double[] center, double radius); /** - * Makes the given worm jump. + * Check whether the circular area with given center and given radius + * is adjacent to impassable terrain in the given world. + * - The circular area must not lie completely within the given world. */ - void jump(Worm worm) throws ModelException; + boolean isAdjacent(World world, double[] center, double radius); + + /** + * Check whether the given world contains the given worm. + */ + boolean hasAsWorm(World world, Worm worm) throws ModelException; /** - * Returns the total amount of time (in seconds) that a jump of the given worm - * would take. + * Add the given worm to the given world. */ - double getJumpTime(Worm worm) throws ModelException; + void addWorm(World world, Worm worm) throws ModelException; + + /** + * Remove the given worm from the given world. + */ + void removeWorm(World world, Worm worm) throws ModelException; /** - * Returns the location on the jump trajectory of the given worm after a time t. - * - * @return An array with two elements, with the first element being the - * x-coordinate and the second element the y-coordinate. + * Return a list filled with all the worms in the given world. */ - double[] getJumpStep(Worm worm, double t) throws ModelException; + List getAllWorms(World world) throws ModelException; + + /** + * Check whether the given world contains the given food. + */ + boolean hasAsFood(World world, Food food) throws ModelException; /** - * Returns the x-coordinate of the current location of the given worm. + * Add the given portion of food to the given world. */ - double getX(Worm worm) throws ModelException; + void addFood(World world, Food food) throws ModelException; + + /** + * Remove the given portion of food from the given world. + */ + void removeFood(World world, Food food) throws ModelException; + + /** + * Return a collection filled with all the worms and all the + * portions of food in the given world. + */ + Collection getAllItems(World world) throws ModelException; /** - * Returns the y-coordinate of the current location of the given worm. + * Return a set of all the team in the given world. */ - double getY(Worm worm) throws ModelException; + Set getAllTeams(World world) throws ModelException; + + /** + * Check whether the given world has an active game. + */ + boolean hasActiveGame(World world) throws ModelException; /** - * Returns the current orientation of the given worm (in radians). + * Return the active worm in the given world. + * - The active worm is the worm whose turn it is to perform + * player-controlled actions. + */ + Worm getActiveWorm(World world) throws ModelException; + + /** + * Start a new game in the given world. + */ + void startGame(World world) throws ModelException; + + /** + * Finish the current game, if any, in the given world. + */ + void finishGame(World world) throws ModelException; + + /** + * Activate the next worm in the given world. + */ + void activateNextWorm(World world) throws ModelException; + + /** + * Return the name of a single worm if that worm is the winner, or the name + * of a team if that team is the winner and the team still has several members. + */ + String getWinner(World world); + + + /************ + * WORM + ************/ + + /** + * Create and return a new worm that is positioned at the given location in + * the given world, that looks in the given direction, that has the given radius + * and the given name, and that is a member of the given team. + * - If the given world is not effective, the new worm is simply positioned + * at the given location. + * - If the given team is not effective, the new worm is not part of any team. + * The location is an array containing the x-coordinate of the location of + * the new worm followed by the y-coordinate of that location. + */ + Worm createWorm(World world, double[] location, double direction, double radius, + String name, Team team) throws ModelException; + + /** + * Terminate the given worm. + */ + void terminate(Worm worm) throws ModelException; + + /** + * Check whether the given worm is terminated. + */ + boolean isTerminated(Worm worm) throws ModelException; + + /** + * Return the current location of the given worm. + * - The resulting array contains the the x-coordinate of the given worm + * followed by its y-coordinate. + */ + double[] getLocation(Worm worm) throws ModelException; + + /** + * Return the current orientation of the given worm (in radians). */ double getOrientation(Worm worm) throws ModelException; /** - * Returns the radius of the given worm. + * Return the radius of the given worm. */ double getRadius(Worm worm) throws ModelException; /** - * Sets the radius of the given worm to the given value. + * Set the radius of the given worm to the given value. */ void setRadius(Worm worm, double newRadius) throws ModelException; /** - * Returns the current number of action points of the given worm. + * Return the mass of the given worm. */ - long getNbActionPoints(Worm worm) throws ModelException; + double getMass(Worm worm) throws ModelException; /** - * Decreases the current number of action points of the given worm with the - * given delta. - */ - void decreaseNbActionPoints(Worm worm, long delta) throws ModelException; - - /** - * Returns the maximum number of action points of the given worm. + * Return the maximum number of action points of the given worm. */ long getMaxNbActionPoints(Worm worm) throws ModelException; /** - * Returns the name the given worm. + * Return the current number of action points of the given worm. + */ + long getNbActionPoints(Worm worm) throws ModelException; + + /** + * Decrease the current number of action points of the given worm + * with the given delta. + * - The given delta may be negative. + */ + void decreaseNbActionPoints(Worm worm, long delta) throws ModelException; + + /** + * Return the current number of hit points of the given worm. + */ + BigInteger getNbHitPoints(Worm worm) throws ModelException; + + /** + * Increment the current number of hit points of the given worm + * with the given delta. + * - The given delta may be negative. + */ + void incrementNbHitPoints(Worm worm, long delta) throws ModelException; + + /** + * Return the name the given worm. */ String getName(Worm worm) throws ModelException; /** - * Renames the given worm. + * Rename the given worm. */ void rename(Worm worm, String newName) throws ModelException; + + /** + * Return the world to which this worm belongs + */ + World getWorld(Worm worm) throws ModelException; + /** - * Returns the mass of the given worm. + * Turn the given worm by the given angle. */ - double getMass(Worm worm) throws ModelException; + void turn(Worm worm, double angle); + /** + * Return the location the farthest away from its current location to which the given + * worm can move in the world in which that worm is positioned, if any, following + * the given direction and not exceeding the given maximum distance. + * - The maximum distance must be finite and may not be negative. + * - The given direction must be in the range [0.0 .. PI[. + * - On its road to the resulting location, the given worm will always be + * positioned on passable terrain. + * - The resulting position may be outside the boundaries of the world, if any, in + * which the given worm is located. + */ + double[] getFurthestLocationInDirection(Worm worm, double direction, double maxDistance) throws ModelException; + + /** + * Move the given worm according to the rules in the assignment. + */ + void move(Worm worm) throws ModelException; + + /** + * Returns whether the given worm can fall. + * - Students working alone on the project must not override this method. + */ + default public boolean canFall(Worm worm) throws MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Makes the given worm fall down until it rests on impassable terrain again, + * or until it leaves the world in which it is in. + * - Students working alone on the project must not override this method. + */ + default void fall(Worm worm) throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + /** + * Return the time needed by the given worm to jump to the nearest position + * adjacent to impassable terrain. + * - deltaT determines the resolution to be used in successive steps of the jump. + */ + double getJumpTime(Worm worm, double deltaT) throws ModelException; + + /** + * Returns the location on the jump trajectory of the given worm + * after a time t. + * - The resulting location is an array with two elements, + * with the first element being the x-coordinate and the + * second element the y-coordinate. + */ + double[] getJumpStep(Worm worm, double t) throws ModelException; + + /** + * Make the given worm jump using the given time step. + * - The given time step determines a time interval during which + * you may assume that the worm will not move through a piece + * of impassable terrain. + */ + void jump(Worm worm, double timeStep) throws ModelException; + + + + /************ + * FOOD + ************/ + + /** + * Create and return a new portion of food that is positioned at the given + * location in the given world. + * = If the given world is not effective, the new food is simply positioned + * at the given location. + */ + Food createFood(World world, double[] location) throws ModelException; + + /** + * Terminate the given portion of food. + */ + void terminate(Food food) throws ModelException; + + /** + * Check whether the given portion of food is terminated. + */ + boolean isTerminated(Food food) throws ModelException; + + /** + * Return the current location of the given portion of food. + * - The resulting array contains the the x-coordinate of the given worm + * followed by its y-coordinate. + */ + double[] getLocation(Food food) throws ModelException; + + /** + * Return the radius of the given portion of food. + */ + double getRadius(Food food) throws ModelException; + + /** + * Return the mass of the given portion of food. + */ + double getMass(Food food) throws ModelException; + + /** + * Return the world to which this portion of food belongs. + */ + World getWorld(Food food) throws ModelException; + + + + /******** + * TEAM + ********/ + + /** + * Create a new team for the given world with given name and with no members yet. + * - Students working alone on the project must not override this method. + */ + default Team createTeam(World world, String name) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Terminate the given team. + */ + default void terminate(Team team) throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Check whether the given portion of food is terminated. + */ + default boolean isTerminated(Team team) throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Return the name of the given team. + * - Students working alone on the project must not override this method. + */ + default String getName(Team team) throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + + /** + * Return the team to which this worm belongs. + * - Students working alone on the project must not override this method. + */ + default Team getTeam(Worm worm) throws ModelException { + throw new MustNotImplementException(); + } + + /** + * Return the number of worms in the given team. + * - Students working alone on the project must not override this method. + */ + default int getNbWormsOfTeam(Team team) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Return a list of all the worms in the given team, sorted alphabetically. + * This method must run in linear time. + * - Students working alone on the project must not override this method. + */ + default List getAllWormsOfTeam(Team team) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + }; + + /** + * Add the given worms to the given team. + * - Students working alone on the project must not override this method. + */ + default void addWormsToTeam(Team team, Worm... worms) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Remove the given worms from the given team. + * - Students working alone on the project must not override this method. + */ + default void removeWormsFromTeam(Team team, Worm... worms) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + /** + * Merge the given teams. + * - All the worms of the supplying team are transferred to the receiving team. + * - Students working alone on the project must not override this method. + */ + default void mergeTeams(Team recevingTeam, Team supplyingTeam) + throws ModelException, MustNotImplementException { + throw new MustNotImplementException(); + } + + } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java b/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java index cdab28d..af7e59f 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java @@ -12,6 +12,11 @@ import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; +import java.util.Collections; +import java.util.Random; + +import worms.facade.IFacade; +import worms.model.World; public class GUIUtils { @@ -89,4 +94,47 @@ public class GUIUtils { } return url; } + + // ideally, this constant would only exist once in the code, but we cannot refer to the constant from worms.model + private static final double ADJACENCY_RADIUS_FRACTION = 0.1; + + public static double[] findFreeAdjacentSpot(IFacade facade, World world, double radius, Random random) { + double worldWidth = facade.getWorldWidth(world); + double worldHeight = facade.getWorldHeight(world); + + // start at random location + double x = random.nextDouble() * worldWidth; + double y = random.nextDouble() * worldHeight; + int n = 0; + // move towards center + double angle = Math.atan((worldHeight / 2 - y) / (worldWidth / 2 - x)); + while (!isValidLocation(facade, world, x, y, radius)) { + // at some point, give up and start somewhere else + if (!liesInWorld(worldWidth, worldHeight, x, y) || n % 1000 == 0) { + x = random.nextDouble() * worldWidth; + y = random.nextDouble() * worldHeight; + angle = Math.atan((worldHeight / 2 - y) / (worldWidth / 2 - x)); + n = 0; + } + double d = ADJACENCY_RADIUS_FRACTION / 2 * radius; + x += d * Math.cos(angle); + y += d * Math.sin(angle); + n += 1; + } + return new double[] { x, y }; + } + + private static boolean isValidLocation(IFacade facade, World world, double x, double y, double radius) { + double[] center = { x, y }; + return facade.isPassable(world, center, radius) && + facade.isAdjacent(world, center , radius); + } + + private static boolean liesInWorld(double width, double height, double x, double y) { + return 0 <= x && x <= width && 0 <= y && y <= height; + } + + public static String numberToName(int n) { + return String.join("", Collections.nCopies(n / 26, "Z")) + (char)('A' + (n % 26)); + } } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java b/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java index c212cbb..8d28058 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java @@ -1,11 +1,6 @@ package worms.internal.gui; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; import java.util.Random; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; @@ -13,67 +8,47 @@ import java.util.concurrent.TimeUnit; import worms.facade.IFacade; import worms.internal.gui.game.commands.Command; +import worms.model.World; import worms.model.Worm; public class GameState { private final Random random; private final IFacade facade; - private final Collection worms = new ArrayList(); private final BlockingQueue timeDelta = new LinkedBlockingQueue( 1); - private final int width; - private final int height; + private World world; - public GameState(IFacade facade, long randomSeed, int width, int height) { + private final Level level; + + public GameState(IFacade facade, long randomSeed, Level level) { this.random = new Random(randomSeed); this.facade = facade; - this.width = width; - this.height = height; + this.level = level; + } + + public synchronized void createWorld() { + level.load(); + world = facade.createWorld(level.getWorldWidth(), + level.getWorldHeight(), level.getPassableMap()); } - private List wormNames = Arrays.asList("Shari", "Shannon", - "Willard", "Jodi", "Santos", "Ross", "Cora", "Jacob", "Homer", - "Kara"); - private int nameIndex = 0; - private Iterator selection; - private Worm selectedWorm; - - public IFacade getFacade() { return facade; } - private void createRandomWorms() { - double worldWidth = width * GUIConstants.DISPLAY_SCALE; - double worldHeight = height * GUIConstants.DISPLAY_SCALE; - - for (int i = 0; i < wormNames.size(); i++) { - String name = wormNames.get(nameIndex++); - double radius = 0.25 + random.nextDouble() / 4; - - double x = -worldWidth / 2 + radius + random.nextDouble() - * (worldWidth - 2 * radius); - double y = -worldHeight / 2 + radius + random.nextDouble() - * (worldHeight - 2 * radius); - double direction = random.nextDouble() * 2 * Math.PI; - Worm worm = facade.createWorm(new double[] {x, y}, direction, radius, name); - if (worm != null) { - worms.add(worm); - } else { - throw new NullPointerException("Created worm must not be null"); - } - } + public Collection getWorms() { + return getFacade().getAllWorms(getWorld()); } - + public void evolve(double dt) { timeDelta.clear(); // nobody was waiting for the previous tick, so // clear it timeDelta.offer(dt); } - + public boolean executeImmediately(Command cmd) { cmd.startExecution(); while (!cmd.isTerminated()) { @@ -93,32 +68,12 @@ public class GameState { return cmd.isExecutionCompleted(); } - public void startGame() { - createRandomWorms(); - selectNextWorm(); + public Level getLevel() { + return level; } - public Worm getSelectedWorm() { - return selectedWorm; - } - - public void selectNextWorm() { - if (selection == null || !selection.hasNext()) { - selection = worms.iterator(); - } - if (selection.hasNext()) { - selectWorm(selection.next()); - } else { - selectWorm(null); - } - } - - public void selectWorm(Worm worm) { - selectedWorm = worm; - } - - public Collection getWorms() { - return Collections.unmodifiableCollection(worms); + public synchronized World getWorld() { + return world; } public Random getRandom() { diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/Level.java b/OGP1718-Worms/src-provided/worms/internal/gui/Level.java new file mode 100644 index 0000000..ebbe5b8 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/Level.java @@ -0,0 +1,228 @@ +package worms.internal.gui; + +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.imageio.ImageIO; + +public class Level { + + private static final String LEVELS_DIRECTORY = "levels"; + + private static class LoadException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public LoadException(String message, Exception cause) { + super(message, cause); + } + + } + + private static class LevelFile implements Comparable { + private final URL url; + private final String name; + + public LevelFile(String name, URL url) { + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public URL getURL() { + return url; + } + + public InputStream getInputStream() { + try { + return getURL().openStream(); + } catch (IOException e) { + return null; + } + } + + @Override + public int compareTo(LevelFile o) { + return getName().compareTo(o.getName()); + } + } + + public static Level[] getAvailableLevels() { + LevelFile[] files = getLevelFiles(); + Level[] levels = new Level[files.length]; + for (int i = 0; i < files.length; i++) { + levels[i] = new Level(files[i]); + } + return levels; + } + + private static LevelFile[] getLevelFiles() { + InputStream levelsListFile; + try { + levelsListFile = GUIUtils.openResource(LEVELS_DIRECTORY + + "/levels.txt"); + } catch (IOException e1) { + e1.printStackTrace(); + return new LevelFile[0]; + } + + BufferedReader reader = new BufferedReader(new InputStreamReader( + levelsListFile)); + List levelURLs = new ArrayList(); + + try { + String line = reader.readLine(); + while (line != null) { + line = line.trim(); + if (!line.isEmpty() && line.toLowerCase().endsWith(".lvl")) { + URL url = GUIUtils.toURL(LEVELS_DIRECTORY + "/" + line); + levelURLs.add(new LevelFile(line, url)); + } + line = reader.readLine(); + } + } catch (IOException e) { + e.printStackTrace(); + return new LevelFile[0]; + } finally { + try { + reader.close(); + } catch (IOException e) { + // don't care + } + } + LevelFile[] levelFiles = levelURLs.toArray(new LevelFile[levelURLs + .size()]); + Arrays.sort(levelFiles); + return levelFiles; + } + + private final LevelFile file; + private BufferedImage mapImage; + + private double scale; + + public Level(LevelFile file) { + this.file = file; + } + + public String getName() { + return file.getName().substring(0, file.getName().length() - 4); + } + + public void load() { + try { + BufferedReader reader = new BufferedReader(new InputStreamReader( + file.getInputStream())); + readFile(reader); + reader.close(); + } catch (Exception e) { + throw new LoadException("Could not load world from file " + + file.getName(), e); + } + } + + protected void readFile(BufferedReader reader) throws IOException { + this.mapImage = ImageIO.read(GUIUtils.openResource(LEVELS_DIRECTORY + + "/" + readAsKeyVal(reader, "map"))); + try { + double height = Double.parseDouble(readAsKeyVal(reader, "height")); + this.scale = height / mapImage.getHeight(); + } catch (IllegalArgumentException e) { + double width = Double.parseDouble(readAsKeyVal(reader, "width")); + this.scale = width / mapImage.getWidth(); + } + } + + protected String readAsKeyVal(BufferedReader reader, String expectedKey) + throws IOException { + String line = reader.readLine(); + while (line.isEmpty() || line.indexOf("#") == 0) { + line = reader.readLine(); + } + if (line.indexOf("#") > 0) { + line = line.substring(0, line.indexOf("#")).trim(); + } + int split = line.indexOf(":"); + String key = line.substring(0, split); + String value = line.substring(split + 1); + if (!expectedKey.equals(key)) { + throw new IllegalArgumentException("Expected key " + expectedKey + + ", got " + key); + } + return value; + } + + public BufferedImage getMapImage() { + return mapImage; + } + + public int getMapHeight() { + return mapImage.getHeight(); + } + + public int getMapWidth() { + return mapImage.getWidth(); + } + + /** + * Scale of the world (in worm-meter per map pixel) + * + * @return + */ + public double getScale() { + return scale; + } + + public double getWorldWidth() { + return scale * mapImage.getWidth(); + } + + public double getWorldHeight() { + return scale * mapImage.getHeight(); + } + + public boolean[][] getPassableMap() { + final boolean[][] result = new boolean[getMapHeight()][getMapWidth()]; + final byte[] bytes = ((DataBufferByte) mapImage.getRaster() + .getDataBuffer()).getData(); + final int w = getMapWidth(); + final int h = getMapHeight(); + for (int row = 0; row < h; row++) { + final int offset = w * row; + for (int col = 0; col < w; col++) { + final byte alpha = bytes[4 * (offset + col)]; + // alpha < 128 ((alpha & 0xf) == 0) => passable + // alpha >= 128 ((alpha & 0xf) != 0) => impassable + if (((int) alpha & 0xf0) == 0) { + result[row][col] = true; + } + } + } + return result; + } + + /** + * map width / map height + */ + public double getMapAspectRatio() { + return (double) getMapWidth() / getMapHeight(); + } + + /** + * world width / world height + */ + public double getWorldAspectRatio() { + return getWorldWidth() / getWorldHeight(); + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java index c14aa86..59193ce 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java @@ -5,11 +5,14 @@ import java.util.concurrent.Executors; import worms.facade.IFacade; import worms.internal.gui.GameState; +import worms.internal.gui.game.commands.AddNewFood; +import worms.internal.gui.game.commands.AddNewTeam; +import worms.internal.gui.game.commands.AddNewWorm; import worms.internal.gui.game.commands.Command; import worms.internal.gui.game.commands.Jump; import worms.internal.gui.game.commands.Move; import worms.internal.gui.game.commands.Rename; -import worms.internal.gui.game.commands.Resize; +import worms.internal.gui.game.commands.SelectNextWorm; import worms.internal.gui.game.commands.StartGame; import worms.internal.gui.game.commands.Turn; import worms.internal.gui.messages.MessageType; @@ -84,11 +87,24 @@ class DefaultActionHandler implements IActionHandler { executeCommand(new Rename(getFacade(), worm, newName, getScreen())); } + public void selectNextWorm() { + executeCommand(new SelectNextWorm(getFacade(), getScreen())); + } + public void startGame() { executeCommand(new StartGame(getFacade(), getScreen())); } - public void resizeWorm(Worm worm, int sign) { - executeCommand(new Resize(getFacade(), worm, 1 + sign * 0.2, getScreen())); + public void addNewWorm() { + executeCommand(new AddNewWorm(getFacade(), getScreen())); } + + public void addEmptyTeam(String name) { + executeCommand(new AddNewTeam(getFacade(), name, getScreen())); + } + + public void addNewFood() { + executeCommand(new AddNewFood(getFacade(), getScreen())); + } + } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java index d7f60bd..74ca37a 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java @@ -1,23 +1,33 @@ package worms.internal.gui.game; import java.awt.Graphics2D; +import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; import worms.facade.IFacade; import worms.internal.gui.GUIConstants; import worms.internal.gui.GUIUtils; import worms.internal.gui.GameState; import worms.internal.gui.InputMode; +import worms.internal.gui.Level; import worms.internal.gui.Screen; import worms.internal.gui.WormsGUI; import worms.internal.gui.game.modes.DefaultInputMode; import worms.internal.gui.game.modes.EnteringNameMode; +import worms.internal.gui.game.modes.GameOverMode; +import worms.internal.gui.game.modes.SetupInputMode; +import worms.internal.gui.game.sprites.FoodSprite; import worms.internal.gui.game.sprites.WormSprite; +import worms.internal.gui.messages.MessageType; +import worms.model.Food; +import worms.model.Team; +import worms.model.World; import worms.model.Worm; public class PlayGameScreen extends Screen { @@ -47,13 +57,12 @@ public class PlayGameScreen extends Screen { @Override protected InputMode createDefaultInputMode() { - return new DefaultInputMode(this, null); + return new SetupInputMode(this, null); } @Override public void screenStarted() { runGameLoop(); - userActionHandler.startGame(); } final AtomicLong lastUpdateTimestamp = new AtomicLong(); @@ -69,6 +78,7 @@ public class PlayGameScreen extends Screen { repaint(); } }; + private Worm currentWorm; private void runGameLoop() { Timer timer = new Timer(); @@ -77,27 +87,44 @@ public class PlayGameScreen extends Screen { public void uncaughtException(Thread t, Throwable e) { gameLoop.cancel(); e.printStackTrace(); - getGUI().showError( - e.getClass().getName() + ": " + e.getMessage()); + getGUI().showError(e.getClass().getName() + ": " + e.getMessage()); } }); lastUpdateTimestamp.set(System.currentTimeMillis()); timer.scheduleAtFixedRate(gameLoop, 0, 1000 / GUIConstants.FRAMERATE); } + public void gameFinished() { + addMessage("Game over! The winner is " + getFacade().getWinner(getWorld()) + + "\n\nPress 'R' to start another game, or 'ESC' to quit.", MessageType.NORMAL); + gameLoop.cancel(); + switchInputMode(new GameOverMode(this, getCurrentInputMode())); + } + public synchronized void update() { + removeInactiveSprites(); addNewSprites(); for (Sprite sprite : sprites) { sprite.update(); } + currentWorm = getFacade().getActiveWorm(getWorld()); } - + + protected void removeInactiveSprites() { + for (Sprite sprite : new ArrayList>(sprites)) { + if (!sprite.isObjectAlive()) { + removeSprite(sprite); + } + } + } + protected void addNewSprites() { addNewWormSprites(); + addNewFoodSprites(); } private void addNewWormSprites() { - Collection worms = getGameState().getWorms(); + Collection worms = getFacade().getAllWorms(getWorld()); if (worms != null) { for (Worm worm : worms) { WormSprite sprite = getWormSprite(worm); @@ -108,6 +135,28 @@ public class PlayGameScreen extends Screen { } } + private void addNewFoodSprites() { + Collection foods = getAll(Food.class); + if (foods != null) { + for (Food food : foods) { + FoodSprite sprite = getSpriteOfTypeFor(FoodSprite.class, food); + if (sprite == null) { + createFoodSprite(food); + } + } + } + } + + private Collection getAll(Class type) { + return getFacade().getAllItems(getWorld()).stream().filter(type::isInstance).map(type::cast) + .collect(Collectors.toSet()); + } + + private void createFoodSprite(Food food) { + FoodSprite sprite = new FoodSprite(this, food); + addSprite(sprite); + } + private void createWormSprite(Worm worm) { WormSprite sprite = new WormSprite(this, worm); addSprite(sprite); @@ -186,7 +235,7 @@ public class PlayGameScreen extends Screen { } public synchronized Worm getSelectedWorm() { - return getGameState().getSelectedWorm(); + return currentWorm; } @Override @@ -194,8 +243,7 @@ public class PlayGameScreen extends Screen { painter.paint(g); } - public static PlayGameScreen create(WormsGUI gui, GameState gameState, - boolean debugMode) { + public static PlayGameScreen create(WormsGUI gui, GameState gameState, boolean debugMode) { if (!debugMode) { return new PlayGameScreen(gui, gameState); } else { @@ -208,6 +256,14 @@ public class PlayGameScreen extends Screen { } } + protected Level getLevel() { + return getGameState().getLevel(); + } + + public World getWorld() { + return getGameState().getWorld(); + } + public void addSprite(Sprite sprite) { sprites.add(sprite); } @@ -216,11 +272,40 @@ public class PlayGameScreen extends Screen { sprites.remove(sprite); } + /** + * Aspect ratio of the screen + */ + private double getScreenAspectRatio() { + return (double) getScreenWidth() / getScreenHeight(); + } + + /** + * Width of the world when displayed (in pixels) + */ + private double getWorldDisplayWidth() { + if (getLevel().getMapAspectRatio() >= getScreenAspectRatio()) { + return getScreenWidth(); + } else { + return getScreenHeight() * getLevel().getMapAspectRatio(); + } + } + + /** + * Height of the world when displayed (in pixels) + */ + private double getWorldDisplayHeight() { + if (getLevel().getMapAspectRatio() <= getScreenAspectRatio()) { + return getScreenHeight(); + } else { + return getScreenWidth() / getLevel().getMapAspectRatio(); + } + } + /** * Scale of the displayed world (in worm-meter per pixel) */ private double getDisplayScale() { - return GUIConstants.DISPLAY_SCALE; + return getLevel().getWorldWidth() / getWorldDisplayWidth(); } /** @@ -241,36 +326,39 @@ public class PlayGameScreen extends Screen { * World x coordinate to screen x coordinate */ public double getScreenX(double x) { - return getScreenWidth()/2.0 + worldToScreenDistance(x); + double offset = (getScreenWidth() - getWorldDisplayWidth()) / 2.0; + return offset + worldToScreenDistance(x); } /** * Screen x coordinate to world x coordinate */ public double getLogicalX(double screenX) { - return screenToWorldDistance(screenX - getScreenWidth()/2.0); + double offset = (getScreenWidth() - getWorldDisplayWidth()) / 2.0; + return screenToWorldDistance(screenX - offset); } /** * World y coordinate to screen y coordinate */ public double getScreenY(double y) { - return getScreenHeight()/2.0 - worldToScreenDistance(y); + double offset = (getScreenHeight() - getWorldDisplayHeight()) / 2.0; + return offset + getWorldDisplayHeight() - worldToScreenDistance(y); } /** * Screen y coordinate to world y coordinate */ public double getLogicalY(double screenY) { - return screenToWorldDistance(getScreenHeight()/2.0 - screenY); + double offset = (getScreenHeight() - getWorldDisplayHeight()) / 2.0; + return screenToWorldDistance(-screenY + offset + getWorldDisplayHeight()); } public void paintTextEntry(Graphics2D g, String message, String enteredName) { painter.paintTextEntry(g, message, enteredName); } - public void drawTurnAngleIndicator(Graphics2D g, WormSprite wormSprite, - double currentAngle) { + public void drawTurnAngleIndicator(Graphics2D g, WormSprite wormSprite, double currentAngle) { painter.drawTurnAngleIndicator(g, wormSprite, currentAngle); } @@ -285,13 +373,41 @@ public class PlayGameScreen extends Screen { return (InputMode) super.getCurrentInputMode(); } + public void addEmptyTeam() { + switchInputMode( + new EnteringNameMode("Enter team name: ", this, getCurrentInputMode(), newName -> userActionHandler.addEmptyTeam(newName))); + } + + private Team lastTeam; + + public void setLastCreatedTeam(Team team) { + this.lastTeam = team; + } + + public Team getLastCreatedTeam() { + return lastTeam; + } + + public void addPlayerControlledWorm() { + userActionHandler.addNewWorm(); + } + + public void addFood() { + userActionHandler.addNewFood(); + } + + public void startGame() { + lastTeam = null; + userActionHandler.startGame(); + } + public void gameStarted() { switchInputMode(new DefaultInputMode(this, getCurrentInputMode())); } public void renameWorm() { - switchInputMode(new EnteringNameMode("Enter new name for worm: ", this, - getCurrentInputMode(), new EnteringNameMode.Callback() { + switchInputMode(new EnteringNameMode("Enter new name for worm: ", this, getCurrentInputMode(), + new EnteringNameMode.Callback() { @Override public void onNameEntered(String newName) { changeName(newName); @@ -308,11 +424,7 @@ public class PlayGameScreen extends Screen { } public void selectNextWorm() { - getGameState().selectNextWorm(); - } - - public IActionHandler getProgramActionHandler() { - return programActionHandler; + userActionHandler.selectNextWorm(); } public void selectWorm(Worm worm) { @@ -321,12 +433,8 @@ public class PlayGameScreen extends Screen { } } - public void resizeWorm(int sign) { - Worm worm = getSelectedWorm(); - - if (worm != null) { - userActionHandler.resizeWorm(worm, sign); - } + public IActionHandler getProgramActionHandler() { + return programActionHandler; } } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java index 19e64b5..3a55641 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java @@ -1,25 +1,126 @@ package worms.internal.gui.game; import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; import java.awt.Shape; +import java.awt.geom.Ellipse2D.Double; +import java.awt.image.BufferedImage; import worms.internal.gui.GUIUtils; +import worms.internal.gui.Level; +import worms.internal.gui.game.sprites.FoodSprite; import worms.internal.gui.game.sprites.WormSprite; +import worms.model.World; public class PlayGameScreenDebugPainter extends PlayGameScreenPainter { private static final int LOCATION_MARKER_SIZE = 4; + private static final double MAX_DIVERSION = 0.7875; + + private static final boolean PAINT_PASSABLE = true; + + /** + * Step size for sampling; lower = more details (but takes longer) + */ + private static final double PASSABLE_STEP_SIZE = 3; // screen pixels + + /** + * Radius for sampling; lower = more details (but takes longer) + */ + private static final double PASSABLE_TEST_RADIUS = 10 ; // screen pixels + + + private Image passableImage; + public PlayGameScreenDebugPainter(PlayGameScreen screen) { super(screen); } + @Override + public void paint(Graphics2D g) { + super.paint(g); + } + + @Override + protected void paintLevel() { + super.paintLevel(); + + if (passableImage == null) { + BufferedImage image = createPassableImage(); + this.passableImage = image; + } + + currentGraphics.drawImage(passableImage, 0, 0, null); + + drawCrossMarker(getScreenX(0), getScreenY(0), 10, Color.BLUE); + drawCrossMarker(getScreenX(0), getScreenY(getLevel().getWorldHeight()), + 10, Color.BLUE); + drawCrossMarker(getScreenX(getLevel().getWorldWidth()), getScreenY(0), + 10, Color.BLUE); + drawCrossMarker(getScreenX(getLevel().getWorldWidth()), + getScreenY(getLevel().getWorldHeight()), 10, Color.BLUE); + } + + protected BufferedImage createPassableImage() { + Level level = getState().getLevel(); + World world = getState().getWorld(); + + BufferedImage image = new BufferedImage(getScreen().getScreenWidth(), + getScreen().getScreenHeight(), BufferedImage.TYPE_4BYTE_ABGR); + BufferedImage adjacencyImage = new BufferedImage(getScreen() + .getScreenWidth(), getScreen().getScreenHeight(), + BufferedImage.TYPE_4BYTE_ABGR); + Graphics2D imGfx = image.createGraphics(); + imGfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + Graphics2D imAdjacencyGfx = adjacencyImage.createGraphics(); + imAdjacencyGfx.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + double testRadius = getScreen().screenToWorldDistance(PASSABLE_TEST_RADIUS); + double stepSize = getScreen().screenToWorldDistance(PASSABLE_STEP_SIZE); + for (double x = testRadius; x <= level.getWorldWidth() - testRadius; x += stepSize) { + for (double y = testRadius; y <= level.getWorldHeight() + - testRadius; y += stepSize) { + double randomizedX = x + (-0.5 + Math.random()) * stepSize * 2; + double randomizedY = y + (-0.5 + Math.random()) * stepSize * 2; + Graphics2D targetGraphics = imGfx; + boolean isPassable = false; + if (!getState().getFacade().isPassable(world, new double[] {randomizedX, + randomizedY}, testRadius)) { + targetGraphics.setColor(new Color(255, 0, 0, 4)); + } else if (getState().getFacade().isAdjacent(world, + new double[] { randomizedX, randomizedY} , testRadius)) { + targetGraphics = imAdjacencyGfx; + targetGraphics.setColor(new Color(0, 255, 0, 64)); + } else { + isPassable = true; + targetGraphics.setColor(new Color(0, 0, 255, 4)); + } + if (!isPassable || PAINT_PASSABLE) { + Double circle = GUIUtils.circleAt(getScreenX(randomizedX), + getScreenY(randomizedY), getScreen() + .worldToScreenDistance(testRadius)); + targetGraphics.fill(circle); + } + } + } + imGfx.drawImage(adjacencyImage, 0, 0, null); + imAdjacencyGfx.dispose(); + imGfx.dispose(); + return image; + } + @Override protected void paintWorm(WormSprite sprite) { drawName(sprite); drawActionBar(sprite); + drawHitpointsBar(sprite); drawOutline(sprite); drawJumpMarkers(sprite); // also draw for other worms @@ -29,11 +130,6 @@ public class PlayGameScreenDebugPainter extends PlayGameScreenPainter { drawLocationMarker(sprite); } - - @Override - protected void paintLevel() { - drawCrossMarker(getScreenX(0), getScreenY(0), 10, Color.BLUE); - } @Override protected void drawJumpMarkers(WormSprite sprite) { @@ -70,6 +166,21 @@ public class PlayGameScreenDebugPainter extends PlayGameScreenPainter { Color.YELLOW); } + @Override + protected void paintFood(FoodSprite sprite) { + super.paintFood(sprite); + double r = sprite.getRadius(); + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + + currentGraphics.setColor(Color.CYAN); + Shape circle = GUIUtils.circleAt(x, y, getScreen() + .worldToScreenDistance(r)); + currentGraphics.fill(circle); + currentGraphics.setColor(Color.DARK_GRAY); + currentGraphics.draw(circle); + } + protected void drawOutline(WormSprite sprite) { double r = sprite.getRadius(); double x = sprite.getCenterX(); @@ -92,6 +203,18 @@ public class PlayGameScreenDebugPainter extends PlayGameScreenPainter { currentGraphics.drawLine((int) x, (int) y, (int) (x + dist * Math.cos(direction)), (int) (y - dist * Math.sin(direction))); + + // draw move tolerance + + direction = direction - MAX_DIVERSION; + currentGraphics.drawLine((int) x, (int) y, + (int) (x + dist * Math.cos(direction)), + (int) (y - dist * Math.sin(direction))); + + direction = direction + 2 * MAX_DIVERSION; + currentGraphics.drawLine((int) x, (int) y, + (int) (x + dist * Math.cos(direction)), + (int) (y - dist * Math.sin(direction))); } } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java index 5d4578a..b25190b 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java @@ -3,6 +3,7 @@ package worms.internal.gui.game; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.Shape; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; @@ -14,17 +15,27 @@ import java.util.StringTokenizer; import worms.internal.gui.AbstractPainter; import worms.internal.gui.GUIUtils; import worms.internal.gui.GameState; +import worms.internal.gui.Level; +import worms.internal.gui.game.sprites.FoodSprite; import worms.internal.gui.game.sprites.WormSprite; +import worms.model.World; +import worms.util.ModelException; public class PlayGameScreenPainter extends AbstractPainter { - protected static final Color SELECTION_FILL_COLOR = new Color(0xaa84b6cc, true); - protected static final Color SELECTION_IMPASSABLE_FILL_COLOR = new Color(0xaacc8484, true); - protected static final Color SELECTION_OUTLINE_COLOR = new Color(0xaaffffff, true); - protected static final Color DIRECTION_MARKER_COLOR = new Color(0xcc84b6cc, true); - protected static final Color TURN_ANGLE_MARKER_COLOR = new Color(0xcccc84b6, true); + protected static final Color SELECTION_FILL_COLOR = new Color(0xaa84b6cc, + true); + protected static final Color SELECTION_IMPASSABLE_FILL_COLOR = new Color( + 0xaacc8484, true); + protected static final Color SELECTION_OUTLINE_COLOR = new Color( + 0xaaffffff, true); + protected static final Color DIRECTION_MARKER_COLOR = new Color(0xcc84b6cc, + true); + protected static final Color TURN_ANGLE_MARKER_COLOR = new Color( + 0xcccc84b6, true); protected static final Color INVALID_TURN_ANGLE_MARKER_COLOR = Color.RED; - protected static final Color ACTION_POINTS_COLOR = new Color(0xcc00cc00, true); + protected static final Color ACTION_POINTS_COLOR = new Color(0xcc00cc00, + true); protected static final double ACTION_BAR_WIDTH = 30; protected static final double ACTION_BAR_HEIGHT = 5; @@ -32,15 +43,16 @@ public class PlayGameScreenPainter extends AbstractPainter { protected static final Color HIT_POINTS_COLOR = new Color(0xccff6a00, true); protected static final Color BAR_OUTLINE_COLOR = Color.WHITE; - protected static final Color NAME_BAR_BACKGROUND = new Color(0x40ffffff, true); - protected static final Color WEAPON_BAR_BACKGROUND = new Color(0x806666ff, true); + protected static final Color NAME_BAR_BACKGROUND = new Color(0x40ffffff, + true); protected static final Color NAME_BAR_TEXT = Color.WHITE; protected static final double TEXT_BAR_H_MARGIN = 4; protected static final double TEXT_BAR_V_MARGIN = 3; protected static final double TEXT_BAR_V_OFFSET = 2; - protected static final Color RENAME_BACKGROUND_COLOR = new Color(0x600e53a7, true); + protected static final Color RENAME_BACKGROUND_COLOR = new Color( + 0x600e53a7, true); protected static final Color RENAME_TEXT_COLOR = Color.WHITE; protected static final Color JUMP_MARKER_COLOR = Color.GRAY; @@ -48,20 +60,41 @@ public class PlayGameScreenPainter extends AbstractPainter { protected static final double DIRECTION_INDICATOR_SIZE = 10; protected Graphics2D currentGraphics; + private Image scaledImage; public PlayGameScreenPainter(PlayGameScreen screen) { super(screen); } + private void createBackgroundImage() { + if (scaledImage == null) { + scaledImage = GUIUtils.scaleTo(getState().getLevel().getMapImage(), + getScreen().getScreenWidth(), + getScreen().getScreenHeight(), Image.SCALE_SMOOTH); + } + } + protected GameState getState() { return getScreen().getGameState(); } + protected World getWorld() { + return getState().getWorld(); + } + + protected Level getLevel() { + return getState().getLevel(); + } + public void paint(Graphics2D g) { this.currentGraphics = g; - + paintLevel(); + for (FoodSprite sprite : getScreen().getSpritesOfType(FoodSprite.class)) { + paintFood(sprite); + } + for (WormSprite sprite : getScreen().getSpritesOfType(WormSprite.class)) { if (sprite.getWorm() == getScreen().getSelectedWorm()) { drawSelection(sprite); @@ -72,8 +105,16 @@ public class PlayGameScreenPainter extends AbstractPainter { this.currentGraphics = null; } + protected void paintFood(FoodSprite sprite) { + sprite.draw(currentGraphics); + } + protected void paintLevel() { - + createBackgroundImage(); + + int x = (int) getScreenX(0); + int y = (int) getScreenY(getLevel().getWorldHeight()); + currentGraphics.drawImage(scaledImage, x, y, null); } protected double getScreenX(double x) { @@ -91,6 +132,7 @@ public class PlayGameScreenPainter extends AbstractPainter { drawName(sprite); drawActionBar(sprite); + drawHitpointsBar(sprite); if (getScreen().getSelectedWorm() == sprite.getWorm()) { drawDirectionIndicator(sprite); @@ -106,16 +148,29 @@ public class PlayGameScreenPainter extends AbstractPainter { name = "(null)"; } - Rectangle2D bounds = currentGraphics.getFontMetrics().getStringBounds(name, currentGraphics); + String teamName = null; + try { + teamName = sprite.getTeamName(); + } catch (ModelException e) { + // no team + } + + if (teamName != null) { + name += " (" + teamName + ")"; + } + + Rectangle2D bounds = currentGraphics.getFontMetrics().getStringBounds( + name, currentGraphics); final double stringWidth = bounds.getWidth(); final double stringHeight = bounds.getHeight(); final double x = sprite.getCenterX() - stringWidth / 2; final double y = sprite.getCenterY() - voffset - TEXT_BAR_V_OFFSET; - RoundRectangle2D nameBarFill = new RoundRectangle2D.Double(x - TEXT_BAR_H_MARGIN, - y - stringHeight - TEXT_BAR_V_MARGIN, stringWidth + 2 * TEXT_BAR_H_MARGIN, - stringHeight + 2 * TEXT_BAR_V_MARGIN, 5, 5); + RoundRectangle2D nameBarFill = new RoundRectangle2D.Double(x + - TEXT_BAR_H_MARGIN, y - stringHeight - TEXT_BAR_V_MARGIN, + stringWidth + 2 * TEXT_BAR_H_MARGIN, stringHeight + 2 + * TEXT_BAR_V_MARGIN, 5, 5); currentGraphics.setColor(NAME_BAR_BACKGROUND); currentGraphics.fill(nameBarFill); @@ -132,23 +187,53 @@ public class PlayGameScreenPainter extends AbstractPainter { double actionPoints = sprite.getActionPoints(); double maxActionPoints = sprite.getMaxActionPoints(); - RoundRectangle2D actionBarFill = new RoundRectangle2D.Double(x - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2, - actionPoints * ACTION_BAR_WIDTH / maxActionPoints, ACTION_BAR_HEIGHT, 5, 5); + RoundRectangle2D actionBarFill = new RoundRectangle2D.Double(x + - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2, actionPoints + * ACTION_BAR_WIDTH / maxActionPoints, ACTION_BAR_HEIGHT, 5, 5); currentGraphics.setColor(ACTION_POINTS_COLOR); currentGraphics.fill(actionBarFill); - RoundRectangle2D actionBar = new RoundRectangle2D.Double(x - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2, - ACTION_BAR_WIDTH, ACTION_BAR_HEIGHT, 5, 5); + RoundRectangle2D actionBar = new RoundRectangle2D.Double(x + - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2, ACTION_BAR_WIDTH, + ACTION_BAR_HEIGHT, 5, 5); currentGraphics.setColor(BAR_OUTLINE_COLOR); currentGraphics.draw(actionBar); } + protected void drawHitpointsBar(WormSprite sprite) { + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + double spriteHeight = sprite.getHeight(currentGraphics); + + double hitPoints = sprite.getHitPoints().doubleValue(); + double maxHitPoints = 2000; + hitPoints = Math.max(hitPoints, maxHitPoints); + + RoundRectangle2D hitpointsBarFill = new RoundRectangle2D.Double(x + - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2 + + ACTION_BAR_HEIGHT, hitPoints * ACTION_BAR_WIDTH + / maxHitPoints, ACTION_BAR_HEIGHT, 5, 5); + currentGraphics.setColor(HIT_POINTS_COLOR); + currentGraphics.fill(hitpointsBarFill); + + RoundRectangle2D hitpointsBar = new RoundRectangle2D.Double(x + - ACTION_BAR_WIDTH / 2, y + spriteHeight / 2 + + ACTION_BAR_HEIGHT, ACTION_BAR_WIDTH, ACTION_BAR_HEIGHT, 5, 5); + currentGraphics.setColor(BAR_OUTLINE_COLOR); + currentGraphics.draw(hitpointsBar); + } + protected void drawSelection(WormSprite sprite) { double x = sprite.getCenterX(); double y = sprite.getCenterY(); - double spriteHeight = Math.max(sprite.getWidth(currentGraphics), sprite.getHeight(currentGraphics)); + double spriteHeight = Math.max(sprite.getWidth(currentGraphics), + sprite.getHeight(currentGraphics)); - currentGraphics.setColor(SELECTION_FILL_COLOR); + if (sprite.isAtImpassableTerrain()) { + currentGraphics.setColor(SELECTION_IMPASSABLE_FILL_COLOR); + } else { + currentGraphics.setColor(SELECTION_FILL_COLOR); + } Shape circle = GUIUtils.circleAt(x, y, spriteHeight / 2); currentGraphics.fill(circle); @@ -157,41 +242,47 @@ public class PlayGameScreenPainter extends AbstractPainter { protected void drawDirectionIndicator(WormSprite sprite) { double x = sprite.getCenterX(); double y = sprite.getCenterY(); - double distance = Math.max(sprite.getWidth(currentGraphics), sprite.getHeight(currentGraphics)) / 2; + double distance = Math.max(sprite.getWidth(currentGraphics), + sprite.getHeight(currentGraphics)) / 2; distance += DIRECTION_INDICATOR_SIZE / 2; double direction = GUIUtils.restrictDirection(sprite.getOrientation()); currentGraphics.setColor(DIRECTION_MARKER_COLOR); - Shape directionIndicator = new Ellipse2D.Double( - x + distance * Math.cos(direction) - DIRECTION_INDICATOR_SIZE / 2, - y - distance * Math.sin(direction) - DIRECTION_INDICATOR_SIZE / 2, DIRECTION_INDICATOR_SIZE, - DIRECTION_INDICATOR_SIZE); + Shape directionIndicator = new Ellipse2D.Double(x + distance + * Math.cos(direction) - DIRECTION_INDICATOR_SIZE / 2, + y - distance * Math.sin(direction) - DIRECTION_INDICATOR_SIZE + / 2, DIRECTION_INDICATOR_SIZE, DIRECTION_INDICATOR_SIZE); currentGraphics.fill(directionIndicator); } - void drawTurnAngleIndicator(Graphics2D graphics, WormSprite sprite, double angle) { + void drawTurnAngleIndicator(Graphics2D graphics, WormSprite sprite, + double angle) { if (sprite == null) { return; } double x = sprite.getCenterX(); double y = sprite.getCenterY(); - double distance = Math.max(sprite.getWidth(graphics), sprite.getHeight(graphics)) / 2; + double distance = Math.max(sprite.getWidth(graphics), + sprite.getHeight(graphics)) / 2; distance += DIRECTION_INDICATOR_SIZE / 2; - double direction = GUIUtils.restrictDirection(sprite.getOrientation() + angle); + double direction = GUIUtils.restrictDirection(sprite.getOrientation() + + angle); /* - * can't do this when getting information from sprite if - * (getFacade().canTurn(sprite.getWorm(), angle)) { - * graphics.setColor(TURN_ANGLE_MARKER_COLOR); } else { - * graphics.setColor(INVALID_TURN_ANGLE_MARKER_COLOR); } - */ + can't do this when getting information from sprite + if (getFacade().canTurn(sprite.getWorm(), angle)) { + graphics.setColor(TURN_ANGLE_MARKER_COLOR); + } else { + graphics.setColor(INVALID_TURN_ANGLE_MARKER_COLOR); + } + */ graphics.setColor(TURN_ANGLE_MARKER_COLOR); - Shape directionIndicator = new Ellipse2D.Double( - x + distance * Math.cos(direction) - DIRECTION_INDICATOR_SIZE / 2, - y - distance * Math.sin(direction) - DIRECTION_INDICATOR_SIZE / 2, DIRECTION_INDICATOR_SIZE, - DIRECTION_INDICATOR_SIZE); + Shape directionIndicator = new Ellipse2D.Double(x + distance + * Math.cos(direction) - DIRECTION_INDICATOR_SIZE / 2, + y - distance * Math.sin(direction) - DIRECTION_INDICATOR_SIZE + / 2, DIRECTION_INDICATOR_SIZE, DIRECTION_INDICATOR_SIZE); graphics.fill(directionIndicator); } @@ -202,7 +293,8 @@ public class PlayGameScreenPainter extends AbstractPainter { if (xy != null) { double jumpX = getScreenX(xy[0]); double jumpY = getScreenY(xy[1]); - drawCrossMarker(jumpX, jumpY, JUMP_MARKER_SIZE, JUMP_MARKER_COLOR); + drawCrossMarker(jumpX, jumpY, JUMP_MARKER_SIZE, + JUMP_MARKER_COLOR); } } } @@ -210,8 +302,10 @@ public class PlayGameScreenPainter extends AbstractPainter { protected void drawCrossMarker(double x, double y, int size, Color color) { currentGraphics.setColor(color); - currentGraphics.drawLine((int) (x - size), (int) y, (int) (x + size), (int) y); - currentGraphics.drawLine((int) x, (int) (y - size), (int) x, (int) (y + size)); + currentGraphics.drawLine((int) (x - size), (int) y, (int) (x + size), + (int) y); + currentGraphics.drawLine((int) x, (int) (y - size), (int) x, + (int) (y + size)); } void paintTextEntry(Graphics2D g, String message, String enteredText) { @@ -219,7 +313,8 @@ public class PlayGameScreenPainter extends AbstractPainter { g.fillRect(0, 0, getScreen().getScreenWidth(), 120); g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 20)); g.setColor(RENAME_TEXT_COLOR); - GUIUtils.drawCenteredString(g, message + enteredText + "\u2502", getScreen().getScreenWidth(), 100); + GUIUtils.drawCenteredString(g, message + enteredText + "\u2502", + getScreen().getScreenWidth(), 100); } public void paintInstructions(Graphics2D g, String message) { diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java index d4ce2c3..5861b1a 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java @@ -20,6 +20,8 @@ public abstract class Sprite { public abstract T getObject(); + public abstract boolean isObjectAlive(); + protected IFacade getFacade() { return getScreen().getFacade(); } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewFood.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewFood.java new file mode 100644 index 0000000..cc29b6e --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewFood.java @@ -0,0 +1,38 @@ +package worms.internal.gui.game.commands; + +import java.util.Random; + +import worms.facade.IFacade; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.util.ModelException; + +public class AddNewFood extends InstantaneousCommand { + + private static final double FOOD_RADIUS = 0.20; + + public AddNewFood(IFacade facade, PlayGameScreen screen) { + super(facade, screen); + } + + @Override + protected boolean canStart() { + return true; + } + + @Override + protected void doStartExecution() { + try { + Random random = getScreen().getGameState().getRandom(); + + double[] p = GUIUtils.findFreeAdjacentSpot(getFacade(), getWorld(), FOOD_RADIUS, random); + + getFacade().createFood(getWorld(), p); + } catch (ModelException e) { + e.printStackTrace(); + getScreen().addMessage("Error while adding new food", MessageType.ERROR); + } + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewTeam.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewTeam.java new file mode 100644 index 0000000..2210c2c --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewTeam.java @@ -0,0 +1,41 @@ +package worms.internal.gui.game.commands; + +import worms.facade.IFacade; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.model.Team; +import worms.util.ModelException; + +public class AddNewTeam extends InstantaneousCommand { + + private final String name; + + public AddNewTeam(IFacade facade, String name, PlayGameScreen screen) { + super(facade, screen); + this.name = name; + } + + @Override + protected boolean canStart() { + return true; + } + + @Override + protected void doStartExecution() { + try { + Team team = getFacade().createTeam(getWorld(), name); + getScreen().addMessage("Team " + name + " created.", MessageType.NORMAL); + getScreen().setLastCreatedTeam(team); + } catch (ModelException e) { + e.printStackTrace(); + getScreen().addMessage( + "Could not create team " + name + ": " + e.getMessage(), + MessageType.ERROR); + } + } + + @Override + protected void afterExecutionCompleted() { + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewWorm.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewWorm.java new file mode 100644 index 0000000..ee64903 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/AddNewWorm.java @@ -0,0 +1,42 @@ +package worms.internal.gui.game.commands; + +import java.util.Random; + +import worms.facade.IFacade; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.model.Team; +import worms.util.ModelException; + +public class AddNewWorm extends InstantaneousCommand { + + public AddNewWorm(IFacade facade, PlayGameScreen screen) { + super(facade, screen); + } + + @Override + protected boolean canStart() { + return true; + } + + @Override + protected void doStartExecution() { + try { + Random random = getScreen().getGameState().getRandom(); + int nbWorms = getFacade().getAllWorms(getWorld()).size(); + String name = "Worm " + GUIUtils.numberToName(nbWorms++); + // ensures all worms have radius in [0.25, 0.50[, so minimum radius and team size conditions are always fulfilled + double radius = 0.25 * (1.0 + random.nextDouble()); + + double[] p = GUIUtils.findFreeAdjacentSpot(getFacade(), getWorld(), radius, random); + + double direction = random.nextDouble() * 2 * Math.PI; + Team team = getScreen().getLastCreatedTeam(); + getFacade().createWorm(getWorld(), p, direction, radius, name, team); + } catch (ModelException e) { + e.printStackTrace(); + getScreen().addMessage("Could not create worm", MessageType.ERROR); + } + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java index 4fdb594..d404f69 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java @@ -2,6 +2,7 @@ package worms.internal.gui.game.commands; import worms.facade.IFacade; import worms.internal.gui.game.PlayGameScreen; +import worms.model.World; public abstract class Command { @@ -26,9 +27,13 @@ public abstract class Command { return facade; } + protected World getWorld() { + return getScreen().getWorld(); + } + public final void startExecution() { if (canStart()) { - started = true; + started = true; doStartExecution(); afterExecutionStarted(); } else { @@ -37,8 +42,12 @@ public abstract class Command { } protected final void cancelExecution() { + cancelExecution(null); + } + + protected final void cancelExecution(Throwable e) { cancelled = true; - afterExecutionCancelled(); + afterExecutionCancelled(e); } protected final void completeExecution() { @@ -52,6 +61,9 @@ public abstract class Command { doUpdate(dt); if (isTerminated()) { getScreen().update(); + if (!getFacade().hasActiveGame(getWorld())) { + getScreen().gameFinished(); + } } } } @@ -71,12 +83,11 @@ public abstract class Command { } /** - * Returns whether or not the execution of this command is terminated, - * either by cancellation or by successful completion. + * Returns whether or not the execution of this command is terminated, either by + * cancellation or by successful completion. */ public final boolean isTerminated() { - return isExecutionCancelled() - || (hasBeenStarted() && isExecutionCompleted()); + return isExecutionCancelled() || (hasBeenStarted() && isExecutionCompleted()); } /** @@ -111,9 +122,10 @@ public abstract class Command { } /** - * Called when the execution of the command has been cancelled. + * Called when the execution of the command has been cancelled, with an optional + * throwable that indicates the cause of the cancellation. */ - protected void afterExecutionCancelled() { + protected void afterExecutionCancelled(Throwable e) { } /** @@ -131,11 +143,8 @@ public abstract class Command { @Override public String toString() { - return this.getClass().getSimpleName() - + " (" - + (hasBeenStarted() ? "elapsed: " - + String.format("%.2f", getElapsedTime()) + "s)" - : "queued)"); + return this.getClass().getSimpleName() + " (" + + (hasBeenStarted() ? "elapsed: " + String.format("%.2f", getElapsedTime()) + "s)" : "queued)"); } } \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java index b6ab2b9..7e6ba50 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java @@ -1,6 +1,7 @@ package worms.internal.gui.game.commands; import worms.facade.IFacade; +import worms.internal.gui.GUIConstants; import worms.internal.gui.game.PlayGameScreen; import worms.internal.gui.game.sprites.WormSprite; import worms.internal.gui.messages.MessageType; @@ -21,7 +22,6 @@ public class Jump extends Command { return worm; } - @Override protected boolean canStart() { return getWorm() != null; @@ -30,21 +30,23 @@ public class Jump extends Command { @Override protected void doStartExecution() { try { - this.jumpDuration = getFacade().getJumpTime(worm); + this.jumpDuration = getFacade().getJumpTime(worm, GUIConstants.JUMP_TIME_STEP); } catch (ModelException e) { - cancelExecution(); + e.printStackTrace(); + cancelExecution(e); } } @Override - protected void afterExecutionCancelled() { + protected void afterExecutionCancelled(Throwable e) { WormSprite sprite = getScreen().getWormSprite(getWorm()); if (sprite != null) { sprite.setIsJumping(false); } - getScreen().addMessage("This worm cannot jump :(", MessageType.ERROR); + getScreen().addMessage("This worm cannot jump :(" + (e != null ? "\n" + e.getMessage() : ""), + MessageType.ERROR); } - + @Override protected void afterExecutionCompleted() { WormSprite sprite = getScreen().getWormSprite(getWorm()); @@ -62,19 +64,16 @@ public class Jump extends Command { if (getElapsedTime() >= jumpDuration) { if (!hasJumped) { hasJumped = true; - getFacade() - .jump(getWorm()); - double x = getFacade().getX(getWorm()); - double y = getFacade().getY(getWorm()); - sprite.setCenterLocation(getScreen().getScreenX(x), - getScreen().getScreenY(y)); + getFacade().jump(getWorm(), GUIConstants.JUMP_TIME_STEP); + if (!getFacade().isTerminated(getWorm())) { + double[] xy = getFacade().getLocation(getWorm()); + sprite.setCenterLocation(getScreen().getScreenX(xy[0]), getScreen().getScreenY(xy[1])); + } completeExecution(); } } else { - double[] xy = getFacade().getJumpStep(getWorm(), - getElapsedTime()); - sprite.setCenterLocation(getScreen().getScreenX(xy[0]), - getScreen().getScreenY(xy[1])); + double[] xy = getFacade().getJumpStep(getWorm(), getElapsedTime()); + sprite.setCenterLocation(getScreen().getScreenX(xy[0]), getScreen().getScreenY(xy[1])); } } catch (ModelException e) { e.printStackTrace(); diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java index 79d575e..1b0b347 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java @@ -7,6 +7,7 @@ import worms.internal.gui.game.sprites.WormSprite; import worms.internal.gui.messages.MessageType; import worms.model.Worm; import worms.util.ModelException; +import worms.util.MustNotImplementException; public class Move extends Command { @@ -16,6 +17,9 @@ public class Move extends Command { private double finalX; private double finalY; + private boolean isFalling; + private double fallingStartTime = -1; + private final Worm worm; public Move(IFacade facade, Worm worm, PlayGameScreen screen) { @@ -29,13 +33,21 @@ public class Move extends Command { @Override protected boolean canStart() { - return getWorm() != null; + return getWorm() != null; } private double getDuration() { return GUIConstants.MOVE_DURATION; } + protected boolean canFall() { + try { + return getFacade().canFall(getWorm()); + } catch (MustNotImplementException e) { + return false; + } + } + @Override protected void doUpdate(double dt) { WormSprite sprite = getScreen().getWormSprite(getWorm()); @@ -48,13 +60,81 @@ public class Move extends Command { double y = (1.0 - t) * startY + t * finalY; sprite.setCenterLocation(x, y); } else { + fall(dt); + } + } else { + cancelExecution(); + } + } + + protected boolean isFalling() { + return isFalling; + } + + protected void ensureFalling() { + if (fallingStartTime == -1) { + fallingStartTime = getElapsedTime(); + } + isFalling = true; + } + + protected void fall(double dt) { + if (!isFalling) { + startFalling(); + } else { + updateFalling(); + } + } + + protected void updateFalling() { + WormSprite sprite = getScreen().getWormSprite(worm); + if (sprite != null) { + double duration = getScreen().screenToWorldDistance(Math.abs(finalY - startY)) / GUIConstants.FALL_VELOCITY; + double timeElapsedFalling = getElapsedTime() - fallingStartTime; + if (timeElapsedFalling <= duration) { + double t = timeElapsedFalling / duration; + t = t * t; + double x = (1.0 - t) * startX + t * finalX; + double y = (1.0 - t) * startY + t * finalY; + sprite.setCenterLocation(x, y); + } else { + sprite.setCenterLocation(finalX, finalY); completeExecution(); } } else { cancelExecution(); } } - + + protected void startFalling() { + this.startX = getScreen().getScreenX(getObjectX()); + this.startY = getScreen().getScreenY(getObjectY()); + + if (canFall()) { + ensureFalling(); + getFacade().fall(getWorm()); + if (isObjectStillActive()) { + this.finalX = getScreen().getScreenX(getObjectX()); + this.finalY = getScreen().getScreenY(getObjectY()); + } else { + this.finalX = startX; + try { + this.finalY = getScreen().getScreenY(getObjectY()); + } catch (ModelException e) { + this.finalY = getScreen().getScreenY(0); + } + } + } else { + completeExecution(); + } + WormSprite sprite = getScreen().getWormSprite(worm); + if (sprite != null) { + sprite.setCenterLocation(startX, startY); + } else { + cancelExecution(); + } + } + @Override protected void afterExecutionCompleted() { WormSprite sprite = getScreen().getWormSprite(getWorm()); @@ -64,34 +144,39 @@ public class Move extends Command { } @Override - protected void afterExecutionCancelled() { + protected void afterExecutionCancelled(Throwable e) { WormSprite sprite = getScreen().getWormSprite(getWorm()); if (sprite != null) { sprite.setIsMoving(false); } - getScreen().addMessage("This worm cannot move like that :(", + getScreen().addMessage("This worm cannot move like that :(" + (e != null ? "\n" + e.getMessage() : ""), MessageType.ERROR); } @Override protected void doStartExecution() { try { - this.startX = getScreen().getScreenX(getObjectX()); - this.startY = getScreen().getScreenY(getObjectY()); - getFacade().move(getWorm(), 1); - this.finalX = getScreen().getScreenX(getObjectX()); - this.finalY = getScreen().getScreenY(getObjectY()); + double[] xy = getFacade().getLocation(getWorm()); + this.startX = getScreen().getScreenX(xy[0]); + this.startY = getScreen().getScreenY(xy[1]); + getFacade().move(getWorm()); + xy = getFacade().getLocation(getWorm()); + this.finalX = getScreen().getScreenX(xy[0]); + this.finalY = getScreen().getScreenY(xy[1]); } catch (ModelException e) { - e.printStackTrace(); - cancelExecution(); + cancelExecution(e); } } + protected boolean isObjectStillActive() { + return !getFacade().isTerminated(getWorm()); + } + protected double getObjectX() { - return getFacade().getX(getWorm()); + return getFacade().getLocation(getWorm())[0]; } protected double getObjectY() { - return getFacade().getY(getWorm()); + return getFacade().getLocation(getWorm())[1]; } } \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Resize.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Resize.java deleted file mode 100644 index 246f725..0000000 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Resize.java +++ /dev/null @@ -1,40 +0,0 @@ -package worms.internal.gui.game.commands; - -import worms.facade.IFacade; -import worms.internal.gui.game.PlayGameScreen; -import worms.internal.gui.game.sprites.WormSprite; -import worms.internal.gui.messages.MessageType; -import worms.model.Worm; -import worms.util.ModelException; - -public class Resize extends InstantaneousCommand { - private final Worm worm; - private final double factor; - - public Resize(IFacade facade, Worm worm, double factor, - PlayGameScreen screen) { - super(facade, screen); - this.worm = worm; - this.factor = factor; - } - - @Override - protected boolean canStart() { - return worm != null; - } - - @Override - protected void doStartExecution() { - try { - double newRadius = factor * getFacade().getRadius(worm); - getFacade().setRadius(worm, newRadius); - } catch (ModelException e) { - // an invalid radius - getScreen().addMessage( - "Cannot " + (factor > 1.0 ? "grow" : "shrink") - + " that worm anymore :(", MessageType.ERROR); - } - WormSprite sprite = getScreen().getWormSprite(worm); - sprite.update(); - } -} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/SelectNextWorm.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/SelectNextWorm.java new file mode 100644 index 0000000..419833a --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/SelectNextWorm.java @@ -0,0 +1,34 @@ +package worms.internal.gui.game.commands; + +import worms.facade.IFacade; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.util.ModelException; + +public class SelectNextWorm extends InstantaneousCommand { + + public SelectNextWorm(IFacade facade, PlayGameScreen screen) { + super(facade, screen); + } + + @Override + protected boolean canStart() { + return true; + } + + @Override + protected void afterExecutionCancelled(Throwable e) { + getScreen().addMessage("Cannot activate next worm :(" + (e != null ? "\n" + e.getMessage() : ""), MessageType.ERROR); + + } + + @Override + protected void doStartExecution() { + try { + getFacade().activateNextWorm(getWorld()); + } catch (ModelException e) { + cancelExecution(e); + } + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java index 121901b..7445771 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java @@ -6,6 +6,7 @@ import worms.facade.IFacade; import worms.internal.gui.game.PlayGameScreen; import worms.internal.gui.messages.MessageType; import worms.model.Worm; +import worms.util.ModelException; public class StartGame extends InstantaneousCommand { @@ -15,18 +16,30 @@ public class StartGame extends InstantaneousCommand { @Override protected boolean canStart() { - Collection worms = getScreen().getGameState().getWorms(); + Collection worms = getFacade().getAllWorms(getWorld()); return worms != null && !worms.isEmpty(); } - + @Override - protected void afterExecutionCancelled() { - getScreen().addMessage("Cannot start the game without worms", MessageType.ERROR); + protected void afterExecutionCancelled(Throwable e) { + if (e != null) { + getScreen().addMessage("Cannot start the game: " + e.getMessage(), MessageType.ERROR); + } else { + getScreen().addMessage("Cannot start the game without worms", MessageType.ERROR); + } } @Override protected void doStartExecution() { - getScreen().gameStarted(); + try { + getScreen().gameStarted(); + getFacade().startGame(getWorld()); + if (!getFacade().hasActiveGame(getWorld())) { + getScreen().gameFinished(); + } + } catch (ModelException e) { + cancelExecution(e); + } } } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java index cb9dcb9..be8c5b5 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java @@ -23,8 +23,8 @@ public class Turn extends InstantaneousCommand { } @Override - protected void afterExecutionCancelled() { - getScreen().addMessage("This worm cannot perform that turn :(", MessageType.ERROR); + protected void afterExecutionCancelled(Throwable e) { + getScreen().addMessage("This worm cannot perform that turn :(" + (e != null ? "\n" + e.getMessage() : ""), MessageType.ERROR); } @Override @@ -34,7 +34,7 @@ public class Turn extends InstantaneousCommand { double angleToTurn = GUIUtils.restrictDirection(direction + angle) - direction; getFacade().turn(worm, angleToTurn); } catch (ModelException e) { - cancelExecution(); + cancelExecution(e); } } } \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java index c72a4ac..63da925 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java @@ -51,12 +51,6 @@ public class DefaultInputMode extends InputMode { case 'N': getScreen().renameWorm(); break; - case '+': - getScreen().resizeWorm(+1); - break; - case '-': - getScreen().resizeWorm(-1); - break; } } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java index e7a0784..bfe40be 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java @@ -8,6 +8,7 @@ import worms.internal.gui.game.PlayGameScreen; public class EnteringNameMode extends InputMode { + @FunctionalInterface public static interface Callback { public void onNameEntered(String newName); } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/GameOverMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/GameOverMode.java new file mode 100644 index 0000000..58d8551 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/GameOverMode.java @@ -0,0 +1,46 @@ +package worms.internal.gui.game.modes; + +import java.awt.event.KeyEvent; + +import worms.internal.gui.InputMode; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.menu.MainMenuScreen; + +public class GameOverMode extends InputMode { + + /** + * @param playGameScreen + */ + public GameOverMode(PlayGameScreen playGameScreen, + InputMode previous) { + super(playGameScreen, previous); + } + + @Override + public void keyTyped(KeyEvent e) { + switch (e.getKeyChar()) { + case 'r': + case 'R': + // need to do this on a new thread, because otherwise the GUI will be blocked on the main menu + new Thread(new Runnable() { + + @Override + public void run() { + getScreen().getGUI().switchToScreen( + new MainMenuScreen(getScreen().getGUI())); + } + }).start(); + break; + } + } + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + getScreen().getGUI().exit(); + break; + } + } + +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/SetupInputMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/SetupInputMode.java new file mode 100644 index 0000000..a3c8517 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/SetupInputMode.java @@ -0,0 +1,55 @@ +package worms.internal.gui.game.modes; + +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; + +import worms.internal.gui.InputMode; +import worms.internal.gui.game.PlayGameScreen; + +public class SetupInputMode extends InputMode { + + public SetupInputMode(PlayGameScreen screen, + InputMode previous) { + super(screen, previous); + } + + @Override + public void keyTyped(KeyEvent e) { + switch (e.getKeyChar()) { + case 't': + case 'T': + getScreen().addEmptyTeam(); + break; + case 'w': + case 'W': + getScreen().addPlayerControlledWorm(); + break; + case 'f': + case 'F': + getScreen().addFood(); + break; + case 's': + case 'S': + getScreen().startGame(); + break; + } + } + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + getScreen().getGUI().exit(); + break; + } + } + + @Override + public void paintOverlay(Graphics2D g) { + getScreen() + .showInstructions( + g, + "Press\n'T' to create a new team\n'W' to add a new worm\n'F' to add food\n'S' to start the game"); + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/FoodSprite.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/FoodSprite.java new file mode 100644 index 0000000..e990efa --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/FoodSprite.java @@ -0,0 +1,74 @@ +package worms.internal.gui.game.sprites; + +import worms.internal.gui.game.ImageSprite; +import worms.internal.gui.game.PlayGameScreen; +import worms.model.Food; + +public class FoodSprite extends ImageSprite { + + private static final double MAX_SCALE = 100; + private static final double MIN_SCALE = 0.05; + + private final Food food; + + private double radius; + + public FoodSprite(PlayGameScreen screen, Food food) { + super(screen, "images/burger.png"); + this.food = food; + update(); + } + + /** + * @param radius + * (in worm-meter) + */ + public synchronized void setRadius(double radius) { + this.radius = radius; + + /* + * Height of the image (when drawn at native size) in worm-meters, given + * the scale at which the world is drawn to screen + */ + double imageHeightInMeters = getScreen().screenToWorldDistance( + getImageHeight()); + + /* + * scale factor to nicely fit the image in a circle with diameter equal + * to the image height (value determined experimentally) + */ + double fitFactor = 1.2; + + double scaleFactor = fitFactor * 2 * radius / imageHeightInMeters; + + scaleFactor = Math.max(MIN_SCALE, Math.min(scaleFactor, MAX_SCALE)); + + setScale(scaleFactor); + } + + @Override + public synchronized void update() { + setRadius(getFacade().getRadius(getFood())); + double[] xy = getFacade().getLocation(getFood()); + setCenterLocation(getScreen().getScreenX(xy[0]), getScreen().getScreenY(xy[1])); + } + + @Override + public Food getObject() { + return getFood(); + } + + public Food getFood() { + return food; + } + + @Override + public boolean isObjectAlive() { + return !getFacade().isTerminated(food); + } + + public synchronized double getRadius() { + return radius; + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java index 25a7350..f295828 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java @@ -1,24 +1,32 @@ package worms.internal.gui.game.sprites; +import java.math.BigInteger; + +import worms.internal.gui.GUIConstants; import worms.internal.gui.GUIUtils; import worms.internal.gui.game.ImageSprite; import worms.internal.gui.game.PlayGameScreen; +import worms.model.Team; import worms.model.Worm; import worms.util.ModelException; +import worms.util.MustNotImplementException; public class WormSprite extends ImageSprite { private static final double MAX_SCALE = 100; private static final double MIN_SCALE = 0.05; private final Worm worm; - + private boolean isJumping; private boolean isMoving; private double[][] xys; private double orientation; private String name; + private String teamName; + private boolean atImpassableTerrain; private long actionPoints; private long maxActionPoints; + private BigInteger hitPoints; private double actualX; private double actualY; private double radius; @@ -82,23 +90,43 @@ public class WormSprite extends ImageSprite { return dx * dx + dy * dy <= radius * radius; } + @Override + public boolean isObjectAlive() { + return !getFacade().isTerminated(getWorm()); + } + @Override public synchronized void update() { + double[] xy = getFacade().getLocation(getWorm()); if (isJumping || isMoving) { // don't update the location here, because it may differ from the - // location in the model + // location in the model (interpolation) } else { - setCenterLocation(getScreen().getScreenX(getFacade().getX(getWorm())), - getScreen().getScreenY(getFacade().getY(worm))); + setCenterLocation( + getScreen().getScreenX(xy[0]), + getScreen().getScreenY(xy[1])); } - this.actualX = getFacade().getX(getWorm()); - this.actualY = getFacade().getY(getWorm()); + this.actualX = xy[0]; + this.actualY = xy[1]; setRadius(getFacade().getRadius(getWorm())); setDirection(getFacade().getOrientation(getWorm())); updateJumpTime(); setName(getFacade().getName(getWorm())); + try { + Team team = getFacade().getTeam(getWorm()); + if (team != null) { + setTeamName(getFacade().getName(team)); + } else { + setTeamName("_teamless_"); + } + } catch (MustNotImplementException e) { + setTeamName("No Teams"); + } + this.atImpassableTerrain = !getFacade().isPassable(getScreen().getWorld(), + xy, getFacade().getRadius(getWorm())); this.actionPoints = getFacade().getNbActionPoints(getWorm()); this.maxActionPoints = getFacade().getMaxNbActionPoints(getWorm()); + this.hitPoints = getFacade().getNbHitPoints(getWorm()); } public void setIsJumping(boolean isJumping) { @@ -113,7 +141,7 @@ public class WormSprite extends ImageSprite { private void updateJumpTime() { try { - double time = getFacade().getJumpTime(getWorm()); + double time = getFacade().getJumpTime(getWorm(), GUIConstants.JUMP_TIME_STEP); if (time > 0) { int n = 1 + (int) (time / JUMP_MARKER_TIME_DISTANCE); xys = new double[n][]; @@ -146,6 +174,18 @@ public class WormSprite extends ImageSprite { this.name = name; } + public synchronized String getTeamName() { + return teamName; + } + + private void setTeamName(String teamName) { + this.teamName = teamName; + } + + public synchronized boolean isAtImpassableTerrain() { + return atImpassableTerrain; + } + public synchronized long getActionPoints() { return actionPoints; } @@ -154,6 +194,10 @@ public class WormSprite extends ImageSprite { return maxActionPoints; } + public synchronized BigInteger getHitPoints() { + return hitPoints; + } + public synchronized double getActualX() { return actualX; } diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/menu/ChooseLevelScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/menu/ChooseLevelScreen.java new file mode 100644 index 0000000..72b7c59 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/menu/ChooseLevelScreen.java @@ -0,0 +1,30 @@ +package worms.internal.gui.menu; + +import worms.internal.gui.Level; +import worms.internal.gui.WormsGUI; + +class ChooseLevelScreen extends AbstractMenuScreen { + + public ChooseLevelScreen(WormsGUI gui) { + super(gui); + } + + @Override + protected Level[] getChoices() { + return Level.getAvailableLevels(); + } + + @Override + protected String getDisplayName(Level level) { + return level.getName(); + } + + @Override + protected String getInstructions() { + return "Choose the level you want to play"; + } + + @Override + public void screenStarted() { + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java index 3453d40..49fdfd4 100644 --- a/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java +++ b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java @@ -1,8 +1,10 @@ package worms.internal.gui.menu; import worms.internal.gui.GameState; +import worms.internal.gui.Level; import worms.internal.gui.WormsGUI; import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; enum MainMenuOption { Play("Play worms"), PlayDebug("Play worms (debug mode)"), Exit("Exit"); @@ -56,13 +58,28 @@ public class MainMenuScreen extends AbstractMenuScreen { private void startGame(boolean debugMode) { WormsGUI gui = getGUI(); + + ChooseLevelScreen chooseLevel = new ChooseLevelScreen(gui); + getGUI().switchToScreen(chooseLevel); + Level level = chooseLevel.select(); + if (debugMode) { + chooseLevel + .addMessage( + "Loading level, please wait...\n\n(This can take a while in debug mode)", + MessageType.NORMAL); + } else { + chooseLevel.addMessage("Loading level, please wait...", + MessageType.NORMAL); + } + GameState gameState = new GameState(gui.getFacade(), - gui.getOptions().randomSeed, gui.getWidth(), gui.getHeight()); + gui.getOptions().randomSeed, level); PlayGameScreen playGameScreen = PlayGameScreen.create(gui, gameState, debugMode); - gameState.startGame(); + gameState.createWorld(); + getGUI().switchToScreen(playGameScreen); } diff --git a/OGP1718-Worms/src-provided/worms/util/MustNotImplementException.java b/OGP1718-Worms/src-provided/worms/util/MustNotImplementException.java new file mode 100644 index 0000000..e6de852 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/util/MustNotImplementException.java @@ -0,0 +1,10 @@ +package worms.util; + +@SuppressWarnings("serial") +/** + * Students working alone on the project will throw MustNotImplementException + * for each method they must not implement. + */ +public class MustNotImplementException extends RuntimeException{ + +} diff --git a/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java b/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java deleted file mode 100644 index f0f2790..0000000 --- a/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package worms.model; -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; - -import worms.facade.Facade; -import worms.facade.IFacade; -import worms.model.Worm; -import worms.util.ModelException; - -public class PartialFacadeTest { - - private static final double EPS = 1e-4; - - private IFacade facade; - - @Before - public void setup() { - facade = new Facade(); - } - - @Test - public void testMaximumActionPoints() { - Worm worm = facade.createWorm(new double[] {0.0,0.0}, 0, 1, "Test"); - assertEquals(4448, facade.getMaxNbActionPoints(worm)); - } - - @Test - public void testMoveHorizontal() { - Worm worm = facade.createWorm(new double[] {0.0,0.0}, 0, 1, "Test"); - facade.move(worm, 5); - assertEquals(5, facade.getX(worm), EPS); - assertEquals(0, facade.getY(worm), EPS); - } - - @Test - public void testMoveVertical() { - Worm worm = facade.createWorm(new double[] {0.0,0.0}, Math.PI / 2, 1, "Test"); - facade.move(worm, 5); - assertEquals(0, facade.getX(worm), EPS); - assertEquals(5, facade.getY(worm), EPS); - } - - @Test(expected = ModelException.class) - public void testJumpException() { - Worm worm = facade.createWorm(new double[] {0.0,0.0}, 3 * Math.PI / 2, 1, "Test"); - facade.jump(worm); - } - -} diff --git a/OGP1718-Worms/tests/worms/model/PartialPart2FacadeTest.java b/OGP1718-Worms/tests/worms/model/PartialPart2FacadeTest.java new file mode 100644 index 0000000..71f2690 --- /dev/null +++ b/OGP1718-Worms/tests/worms/model/PartialPart2FacadeTest.java @@ -0,0 +1,81 @@ +package worms.model; + +import static org.junit.Assert.*; + +import org.junit.Before; +import org.junit.Test; + +import worms.facade.Facade; +import worms.facade.IFacade; +import worms.model.Worm; + +public class PartialPart2FacadeTest { + + private static final double EPS = 1e-4; + + private IFacade facade; + + // X X X X // + // . . . . // + // . . . . // + // X X X X // + private boolean[][] passableMap = new boolean[][] { // + { false, false, false, false }, // + { true, true, true, true }, // + { true, true, true, true }, // + { false, false, false, false } }; + + private World world; + + @Before + public void setup() { + facade = new Facade(); + world = facade.createWorld(4.0, 4.0, passableMap); + } + + @Test + public void testMaximumActionPoints() { + Worm worm = facade.createWorm(world, new double[] { 1, 2 }, 0, 1, "Test", null); + assertEquals(4448, facade.getMaxNbActionPoints(worm)); + } + + @Test + public void testMoveHorizontal() { + Worm worm = facade.createWorm(world, new double[] { 1, 2 }, 0, 1, "Test", null); + facade.move(worm); + double[] xy = facade.getLocation(worm); + assertEquals(2, xy[0], EPS); + assertEquals(2, xy[1], EPS); + } + + @Test + public void testMoveVertical() { + Worm worm = facade.createWorm(world, new double[] { 1, 1.5 }, Math.PI / 2, 0.5, "Test", null); + facade.move(worm); + double[] xy = facade.getLocation(worm); + assertEquals(1, xy[0], EPS); + assertEquals(2.0, xy[1], EPS); + } + + @Test + public void testFall() { + // . X . + // . w . + // . . . + // X X X + World world = facade.createWorld(3.0, 4.0, new boolean[][] { // + { true, false, true }, // + { true, true, true }, // + { true, true, true }, // + { false, false, false } }); + Worm worm = facade.createWorm(world, new double[] { 1.5, 2.5 }, 3*Math.PI/2, 0.5, "Test", null); + assertFalse(facade.canFall(worm)); + facade.move(worm); + assertTrue(facade.canFall(worm)); + facade.fall(worm); + double[] xy = facade.getLocation(worm); + assertEquals(1.5, xy[0], EPS); + assertEquals(1.5, xy[1], EPS); + } + +}