From 1c495db60830d680214fa94612129158f08de8d9 Mon Sep 17 00:00:00 2001 From: Koen Yskout Date: Wed, 21 Feb 2018 14:12:42 +0100 Subject: [PATCH 1/2] Initial commit --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..c0ed6b4 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ogp1718-gui \ No newline at end of file From 91bd11d444e23a7347cfc8be6238db2c487f91d7 Mon Sep 17 00:00:00 2001 From: Koen Yskout Date: Wed, 21 Feb 2018 15:13:20 +0100 Subject: [PATCH 2/2] Release v1.0 --- OGP1718-Worms/.classpath | 15 + OGP1718-Worms/.gitignore | 1 + OGP1718-Worms/.project | 24 ++ OGP1718-Worms/images/worm.png | Bin 0 -> 20958 bytes OGP1718-Worms/lib/AnnotationsDoclets.jar | Bin 0 -> 113233 bytes OGP1718-Worms/resources/readme | 4 + OGP1718-Worms/src-provided/worms/Worms.java | 30 ++ .../src-provided/worms/facade/IFacade.java | 168 +++++++++ .../worms/internal/gui/AbstractPainter.java | 15 + .../worms/internal/gui/ErrorScreen.java | 48 +++ .../worms/internal/gui/GUIConstants.java | 64 ++++ .../worms/internal/gui/GUIOptions.java | 29 ++ .../worms/internal/gui/GUIUtils.java | 92 +++++ .../worms/internal/gui/GameState.java | 128 +++++++ .../worms/internal/gui/InputMode.java | 80 +++++ .../worms/internal/gui/Screen.java | 133 +++++++ .../worms/internal/gui/WormsGUI.java | 147 ++++++++ .../gui/game/DefaultActionHandler.java | 94 +++++ .../internal/gui/game/IActionHandler.java | 44 +++ .../worms/internal/gui/game/ImageSprite.java | 140 ++++++++ .../internal/gui/game/PlayGameScreen.java | 332 ++++++++++++++++++ .../gui/game/PlayGameScreenDebugPainter.java | 97 +++++ .../gui/game/PlayGameScreenPainter.java | 260 ++++++++++++++ .../worms/internal/gui/game/Sprite.java | 64 ++++ .../internal/gui/game/commands/Command.java | 141 ++++++++ .../game/commands/InstantaneousCommand.java | 20 ++ .../internal/gui/game/commands/Jump.java | 88 +++++ .../internal/gui/game/commands/Move.java | 97 +++++ .../internal/gui/game/commands/Rename.java | 34 ++ .../internal/gui/game/commands/Resize.java | 40 +++ .../internal/gui/game/commands/StartGame.java | 32 ++ .../internal/gui/game/commands/Turn.java | 36 ++ .../gui/game/modes/DefaultInputMode.java | 89 +++++ .../gui/game/modes/EnteringNameMode.java | 62 ++++ .../internal/gui/game/modes/TurningMode.java | 116 ++++++ .../internal/gui/game/sprites/WormSprite.java | 168 +++++++++ .../internal/gui/menu/AbstractMenuScreen.java | 114 ++++++ .../internal/gui/menu/MainMenuScreen.java | 69 ++++ .../internal/gui/menu/MenuInputMode.java | 34 ++ .../worms/internal/gui/messages/Message.java | 50 +++ .../internal/gui/messages/MessageDisplay.java | 51 +++ .../internal/gui/messages/MessagePainter.java | 61 ++++ .../internal/gui/messages/MessageType.java | 5 + .../worms/util/ModelException.java | 21 ++ OGP1718-Worms/src/.gitignore | 0 .../tests/worms/model/PartialFacadeTest.java | 51 +++ 46 files changed, 3388 insertions(+) create mode 100644 OGP1718-Worms/.classpath create mode 100644 OGP1718-Worms/.gitignore create mode 100644 OGP1718-Worms/.project create mode 100644 OGP1718-Worms/images/worm.png create mode 100644 OGP1718-Worms/lib/AnnotationsDoclets.jar create mode 100644 OGP1718-Worms/resources/readme create mode 100644 OGP1718-Worms/src-provided/worms/Worms.java create mode 100644 OGP1718-Worms/src-provided/worms/facade/IFacade.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/AbstractPainter.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/ErrorScreen.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/GUIConstants.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/GUIOptions.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/GameState.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/InputMode.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/Screen.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/WormsGUI.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/IActionHandler.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/ImageSprite.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/InstantaneousCommand.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Rename.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Resize.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/modes/TurningMode.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/menu/AbstractMenuScreen.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/menu/MenuInputMode.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/messages/Message.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageDisplay.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/messages/MessagePainter.java create mode 100644 OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageType.java create mode 100644 OGP1718-Worms/src-provided/worms/util/ModelException.java create mode 100644 OGP1718-Worms/src/.gitignore create mode 100644 OGP1718-Worms/tests/worms/model/PartialFacadeTest.java diff --git a/OGP1718-Worms/.classpath b/OGP1718-Worms/.classpath new file mode 100644 index 0000000..4629a2b --- /dev/null +++ b/OGP1718-Worms/.classpath @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/OGP1718-Worms/.gitignore b/OGP1718-Worms/.gitignore new file mode 100644 index 0000000..5e56e04 --- /dev/null +++ b/OGP1718-Worms/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/OGP1718-Worms/.project b/OGP1718-Worms/.project new file mode 100644 index 0000000..5657ca3 --- /dev/null +++ b/OGP1718-Worms/.project @@ -0,0 +1,24 @@ + + + 1718-Worms + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + + + resources/images + 2 + PROJECT_LOC/images + + + diff --git a/OGP1718-Worms/images/worm.png b/OGP1718-Worms/images/worm.png new file mode 100644 index 0000000000000000000000000000000000000000..2e46497bef5760b7825b08fa916bdff73aec0477 GIT binary patch literal 20958 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 nj-tZOw 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 IIPl&70e5Q;Qvd(} literal 0 HcmV?d00001 diff --git a/OGP1718-Worms/resources/readme b/OGP1718-Worms/resources/readme new file mode 100644 index 0000000..175fa06 --- /dev/null +++ b/OGP1718-Worms/resources/readme @@ -0,0 +1,4 @@ +This folder is included as a source folder so that making a JAR file +will include resources (images, levels, programs) in the JAR. + +The folders link to the folders in the root of the project. \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/Worms.java b/OGP1718-Worms/src-provided/worms/Worms.java new file mode 100644 index 0000000..c72466b --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/Worms.java @@ -0,0 +1,30 @@ +package worms; + +import worms.facade.Facade; +import worms.internal.gui.GUIOptions; +import worms.internal.gui.WormsGUI; + +public class Worms { + + public static void main(String[] args) { + new WormsGUI(new Facade(), parseOptions(args)).start(); + } + + private static GUIOptions parseOptions(String[] args) { + GUIOptions options = new GUIOptions(); + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + if ("-window".equals(arg)) { + options.disableFullScreen = true; + } else if ("-seed".equals(arg)) { + long randomSeed = Long.parseLong(args[++i]); + options.randomSeed = randomSeed; + } else if ("-clickselect".equals(arg)) { + options.enableClickToSelect = true; + } + } + + return options; + } +} diff --git a/OGP1718-Worms/src-provided/worms/facade/IFacade.java b/OGP1718-Worms/src-provided/worms/facade/IFacade.java new file mode 100644 index 0000000..98b70ee --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/facade/IFacade.java @@ -0,0 +1,168 @@ +package worms.facade; + +import worms.model.Worm; +import worms.util.ModelException; + +/** + * Implement this interface to connect your code to the graphical user interface + * (GUI). + * + *

+ */ +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; + + /** + * Moves the given worm by the given number of steps. + */ + void move(Worm worm, int nbSteps) throws ModelException; + + /** + * Turns the given worm by the given angle. + */ + void turn(Worm worm, double angle) throws ModelException; + + /** + * Makes the given worm jump. + */ + void jump(Worm worm) throws ModelException; + + /** + * Returns the total amount of time (in seconds) that a jump of the given worm + * would take. + */ + double getJumpTime(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. + */ + double[] getJumpStep(Worm worm, double t) throws ModelException; + + /** + * Returns the x-coordinate of the current location of the given worm. + */ + double getX(Worm worm) throws ModelException; + + /** + * Returns the y-coordinate of the current location of the given worm. + */ + double getY(Worm worm) throws ModelException; + + /** + * Returns the current orientation of the given worm (in radians). + */ + double getOrientation(Worm worm) throws ModelException; + + /** + * Returns the radius of the given worm. + */ + double getRadius(Worm worm) throws ModelException; + + /** + * Sets 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. + */ + long getNbActionPoints(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. + */ + long getMaxNbActionPoints(Worm worm) throws ModelException; + + /** + * Returns the name the given worm. + */ + String getName(Worm worm) throws ModelException; + + /** + * Renames the given worm. + */ + void rename(Worm worm, String newName) throws ModelException; + + /** + * Returns the mass of the given worm. + */ + double getMass(Worm worm) throws ModelException; + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/AbstractPainter.java b/OGP1718-Worms/src-provided/worms/internal/gui/AbstractPainter.java new file mode 100644 index 0000000..faecbbd --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/AbstractPainter.java @@ -0,0 +1,15 @@ +package worms.internal.gui; + +public abstract class AbstractPainter { + + private final S screen; + + protected AbstractPainter(S screen) { + this.screen = screen; + } + + protected S getScreen() { + return screen; + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/ErrorScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/ErrorScreen.java new file mode 100644 index 0000000..abb3b6b --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/ErrorScreen.java @@ -0,0 +1,48 @@ +package worms.internal.gui; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; +import java.util.StringTokenizer; + +public class ErrorScreen extends Screen { + + private final String message; + + public ErrorScreen(WormsGUI gui, String message) { + super(gui); + this.message = message; + } + + @Override + public void screenStarted() { + } + + @Override + protected InputMode createDefaultInputMode() { + return new InputMode(this, null) { + @Override + public void keyReleased(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + getGUI().exit(); + } + } + }; + } + + @Override + protected void paintScreen(Graphics2D g) { + g.setColor(Color.RED); + GUIUtils.drawCenteredString((Graphics2D) g, "An error has occurred", + getScreenWidth(), 20); + StringTokenizer tok = new StringTokenizer(message, "\n"); + int y = 50; + while (tok.hasMoreElements()) { + String line = tok.nextToken(); + GUIUtils.drawCenteredString((Graphics2D) g, line, getScreenWidth(), + y); + y += (g.getFont().getSize() * 7) / 5; + } + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GUIConstants.java b/OGP1718-Worms/src-provided/worms/internal/gui/GUIConstants.java new file mode 100644 index 0000000..b8cba2b --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GUIConstants.java @@ -0,0 +1,64 @@ +package worms.internal.gui; + +public final class GUIConstants { + + /** + * Default width of the window, when not running in full-screen, in pixels + */ + public static final int DEFAULT_WINDOW_WIDTH = 1024; + + /** + * Default height of the window, when not running in full-screen, in pixels + */ + public static final int DEFAULT_WINDOW_HEIGHT = 768; + + /** + * Framerate at which to re-draw the screen, in frames per (real) second + */ + public static final int FRAMERATE = 15; // fps + + /** + * Time (in worm-seconds) that elapses in 1 real second + */ + public static final double TIME_SCALE = 0.7; + + /** + * Minimal angle to turn when pressing the 'turn' key a single time + */ + public static final double MIN_TURN_ANGLE = Math.PI / 120.0; + + /** + * Angle that is turned per (real) second while keeping the 'turn' keys + * pressed. + */ + public static final double ANGLE_TURNED_PER_SECOND = Math.PI; + + /** + * Duration of the move animation for a single step (in worm-seconds) + */ + public static final double MOVE_DURATION = 0.1; + + /** + * Time to display messages on the screen (in real seconds) + */ + public static final double MESSAGE_DISPLAY_TIME = 1.5; + + /** + * Default velocity with which worms fall down (in worm-meter per worm-seconds) + */ + public static final double FALL_VELOCITY = 5.0; + + /** + * Time step to use when calculating jump positions + */ + public static final double JUMP_TIME_STEP = 1e-4; + + /** + * Scale to use when displaying the world (in worm-meter per pixel) + */ + public static final double DISPLAY_SCALE = 1.0/45; + + /* disable instantiations */ + private GUIConstants() { + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GUIOptions.java b/OGP1718-Worms/src-provided/worms/internal/gui/GUIOptions.java new file mode 100644 index 0000000..bad55db --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GUIOptions.java @@ -0,0 +1,29 @@ +package worms.internal.gui; + +public class GUIOptions { + + /** + * Disable full screen mode + * Default: false + * + * Full screen can also be disabled from the command line by providing the -window argument + */ + public boolean disableFullScreen = false; + + /** + * Random seed for the game + * Default: 3 + * + * Can also be set from the command line with the -seed argument + */ + public long randomSeed = 3; + + /** + * Enable quick worm selection by clicking the mouse + * Default: false + * + * Can also be enabled from the command line with the -clickselect argument + */ + public boolean enableClickToSelect = false; + +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java b/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java new file mode 100644 index 0000000..cdab28d --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GUIUtils.java @@ -0,0 +1,92 @@ +package worms.internal.gui; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLConnection; + +public class GUIUtils { + + public static Ellipse2D.Double circleAt(double centerX, double centerY, + double r) { + return new Ellipse2D.Double(centerX - r, centerY - r, 2 * r, 2 * r); + } + + public static void drawCenteredString(Graphics2D g2d, String text, + double width, double y) { + Rectangle2D bounds = g2d.getFontMetrics().getStringBounds(text, g2d); + g2d.drawString(text, (int) (width / 2 - bounds.getCenterX()), (int) y); + } + + public static double restrictDirection(double direction) { + return restrictAngle(direction, 0); + } + + /** + * Restrict angle to [min, min+2pi) + */ + public static double restrictAngle(double angle, double min) { + while (angle < min) { + angle += 2 * Math.PI; + } + double max = min + 2 * Math.PI; + while (angle >= max) { + angle -= 2 * Math.PI; + } + return angle; + } + + public static double distance(double x1, double y1, double x2, double y2) { + double dx = x1 - x2; + double dy = y1 - y2; + return Math.sqrt(dx * dx + dy * dy); + } + + public static Image scaleTo(BufferedImage image, int screenWidth, + int screenHeight, int hints) { + double ratio = Math.min((double) screenHeight / image.getHeight(), + (double) screenWidth / image.getWidth()); + return image.getScaledInstance((int) (ratio * image.getWidth()), + (int) (ratio * image.getHeight()), hints); + } + + public static InputStream openResource(String filename) throws IOException { + URL url = toURL(filename); + return openResource(url); + } + + public static InputStream openResource(URL url) throws IOException { + InputStream result; + + URLConnection conn = url.openConnection(); + result = conn.getInputStream(); + + return result; + } + + public static URL toURL(String filename) throws FileNotFoundException { + URL url = GUIUtils.class.getResource("/" + filename); + if (url == null) { + try { + File file = new File(filename); + if (file.exists()) { + url = new File(filename).toURI().toURL(); + } else { + throw new FileNotFoundException("File not found: " + filename); + } + } catch (MalformedURLException e) { + e.printStackTrace(); + return null; + } + } + return url; + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java b/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java new file mode 100644 index 0000000..c212cbb --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/GameState.java @@ -0,0 +1,128 @@ +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; +import java.util.concurrent.TimeUnit; + +import worms.facade.IFacade; +import worms.internal.gui.game.commands.Command; +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; + + public GameState(IFacade facade, long randomSeed, int width, int height) { + this.random = new Random(randomSeed); + this.facade = facade; + this.width = width; + this.height = height; + } + + 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 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()) { + try { + Double dt = timeDelta.poll(1000 / GUIConstants.FRAMERATE, + TimeUnit.MILLISECONDS); // blocks, but allows repainting + // if necessary + if (dt != null) { + cmd.update(dt); + } + cmd.getScreen().repaint(); // repaint while executing command + // (which might block GUI thread) + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return cmd.isExecutionCompleted(); + } + + public void startGame() { + createRandomWorms(); + selectNextWorm(); + } + + 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 Random getRandom() { + return random; + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/InputMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/InputMode.java new file mode 100644 index 0000000..9e63b96 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/InputMode.java @@ -0,0 +1,80 @@ +package worms.internal.gui; + +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseMotionListener; + +public class InputMode implements KeyListener, MouseListener, + MouseMotionListener { + + private final ScreenType screen; + private final InputMode previous; + + public InputMode(ScreenType screen, InputMode previous) { + this.screen = screen; + this.previous = previous; + } + + public ScreenType getScreen() { + return screen; + } + + public void leaveInputMode() { + if (previous == null) { + getScreen().switchInputMode(getScreen().createDefaultInputMode()); + } else { + getScreen().switchInputMode(previous); + } + } + + public void paintOverlay(Graphics2D g) { + } + + @Override + public void keyPressed(KeyEvent e) { + } + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyTyped(KeyEvent e) { + } + + @Override + public void mouseClicked(MouseEvent e) { + + } + + @Override + public void mouseEntered(MouseEvent e) { + + } + + @Override + public void mouseExited(MouseEvent e) { + + } + + @Override + public void mousePressed(MouseEvent e) { + + } + + @Override + public void mouseReleased(MouseEvent e) { + + } + + @Override + public void mouseDragged(MouseEvent e) { + } + + @Override + public void mouseMoved(MouseEvent e) { + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/Screen.java b/OGP1718-Worms/src-provided/worms/internal/gui/Screen.java new file mode 100644 index 0000000..e684f5b --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/Screen.java @@ -0,0 +1,133 @@ +package worms.internal.gui; + +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.SwingUtilities; + +import worms.internal.gui.messages.Message; +import worms.internal.gui.messages.MessageDisplay; +import worms.internal.gui.messages.MessagePainter; +import worms.internal.gui.messages.MessageType; + +public abstract class Screen { + + private MessageDisplay messageDisplay = new MessageDisplay(); + + private final WormsGUI gui; + private final JComponent contents; + private final MessagePainter messagePainter; + + protected Screen(WormsGUI gui) { + this.gui = gui; + + this.contents = createContents(); + contents.setFocusable(true); + contents.setFocusTraversalKeysEnabled(false); + + this.messagePainter = createMessagePainter(); + + switchInputMode(createDefaultInputMode()); + } + + protected MessagePainter createMessagePainter() { + return new MessagePainter(this); + } + + public JComponent getContents() { + return contents; + } + + protected JComponent createContents() { + @SuppressWarnings("serial") + JComponent result = new JPanel() { + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D graphics = (Graphics2D) g; + graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, + RenderingHints.VALUE_ANTIALIAS_ON); + + Screen.this.paintScreen(graphics); + + Screen.this.paintMessage(graphics); + + InputMode inputMode = getCurrentInputMode(); + if (inputMode != null) + inputMode.paintOverlay(graphics); + } + }; + result.setBackground(Color.BLACK); + return result; + } + + public WormsGUI getGUI() { + return gui; + } + + protected abstract InputMode createDefaultInputMode(); + + private InputMode currentInputMode; + + public InputMode getCurrentInputMode() { + return currentInputMode; + } + + public void switchInputMode(InputMode newMode) { + if (currentInputMode != null) { + contents.removeKeyListener(currentInputMode); + contents.removeMouseListener(currentInputMode); + contents.removeMouseMotionListener(currentInputMode); + } + currentInputMode = newMode; + if (newMode != null) { + contents.addKeyListener(newMode); + contents.addMouseListener(newMode); + contents.addMouseMotionListener(newMode); + } + } + + protected void paintScreen(Graphics2D g) { + } + + protected void paintMessage(Graphics2D g) { + Message message = messageDisplay.getMessage(); + if (message != null && messagePainter != null) { + messagePainter.paintMessage(g, message); + } + } + + public void addMessage(String message, MessageType type) { + messageDisplay.addMessage(message, type); + repaint(); + } + + public void screenStarted() { + } + + public int getScreenHeight() { + return getContents().getHeight(); + } + + public int getScreenWidth() { + return getContents().getWidth(); + } + + public void repaint() { + if (SwingUtilities.isEventDispatchThread()) { + getContents().paintImmediately(getContents().getVisibleRect()); + } else { + getContents().repaint(); + } + + } + + public void screenStopped() { + switchInputMode(null); + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/WormsGUI.java b/OGP1718-Worms/src-provided/worms/internal/gui/WormsGUI.java new file mode 100644 index 0000000..5cfd325 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/WormsGUI.java @@ -0,0 +1,147 @@ +package worms.internal.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +import javax.swing.JFrame; +import javax.swing.JPanel; + +import worms.facade.IFacade; +import worms.internal.gui.menu.MainMenuScreen; + +public class WormsGUI { + + private JFrame window; + private JPanel screenPanel; + + private Screen currentScreen = null; + + private final GUIOptions options; + private final IFacade facade; + + public WormsGUI(IFacade facade, GUIOptions options) { + this.facade = facade; + this.options = options; + } + + public void switchToScreen(Screen newScreen) { + if (currentScreen != null) { + currentScreen.screenStopped(); + screenPanel.remove(currentScreen.getContents()); + } + currentScreen = newScreen; + if (newScreen != null) { + screenPanel.add(newScreen.getContents(), BorderLayout.CENTER); + screenPanel.validate(); + setFocusToCurrentScreen(); + newScreen.screenStarted(); + } + } + + public void start() { + try { + initializeGUI(); + gotoMainMenu(); + } catch (Throwable e) { + e.printStackTrace(); + showError(e.getMessage()); + } + } + + private void gotoMainMenu() { + MainMenuScreen menuScreen = new MainMenuScreen(this); + switchToScreen(menuScreen); + } + + public void exit() { + window.dispose(); + System.exit(0); + } + + private void initializeGUI() { + GraphicsEnvironment env = GraphicsEnvironment + .getLocalGraphicsEnvironment(); + if (env.isHeadlessInstance()) { + System.out.println("Graphics not supported"); + System.exit(0); + } + + this.window = new JFrame("Worms"); + window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + window.addWindowListener(new WindowAdapter() { + @Override + public void windowGainedFocus(WindowEvent e) { + setFocusToCurrentScreen(); + } + + @Override + public void windowActivated(WindowEvent e) { + setFocusToCurrentScreen(); + } + + @Override + public void windowClosing(WindowEvent e) { + exit(); + }; + }); + window.setFocusable(false); + window.setFocusTraversalKeysEnabled(false); + + this.screenPanel = new JPanel(); + screenPanel.setLayout(new BorderLayout()); + screenPanel.setBackground(Color.WHITE); + screenPanel.setFocusable(false); + window.getContentPane().add(screenPanel); + + GraphicsDevice device = env.getDefaultScreenDevice(); + if (device.isFullScreenSupported() && !options.disableFullScreen) { + window.setUndecorated(true); + window.pack(); + device.setFullScreenWindow(window); + } else { + window.setUndecorated(false); + screenPanel.setPreferredSize(new Dimension( + GUIConstants.DEFAULT_WINDOW_WIDTH, + GUIConstants.DEFAULT_WINDOW_HEIGHT)); + window.pack(); + } + + window.setVisible(true); + } + + public void showError(String message) { + if (message == null) { + message = "(Unknown error)"; + } + ErrorScreen errorScreen = new ErrorScreen(this, message); + switchToScreen(errorScreen); + } + + public IFacade getFacade() { + return facade; + } + + public GUIOptions getOptions() { + return options; + } + + public int getWidth() { + return currentScreen.getScreenWidth(); + } + + public int getHeight() { + return currentScreen.getScreenHeight(); + } + + private void setFocusToCurrentScreen() { + if (currentScreen != null) { + currentScreen.getContents().requestFocusInWindow(); + } + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java new file mode 100644 index 0000000..c14aa86 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/DefaultActionHandler.java @@ -0,0 +1,94 @@ +package worms.internal.gui.game; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import worms.facade.IFacade; +import worms.internal.gui.GameState; +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.StartGame; +import worms.internal.gui.game.commands.Turn; +import worms.internal.gui.messages.MessageType; +import worms.model.Worm; + +class DefaultActionHandler implements IActionHandler { + + private final PlayGameScreen screen; + private final boolean userInitiated; + + private final ExecutorService executor = Executors + .newSingleThreadExecutor(); + + public DefaultActionHandler(PlayGameScreen screen, boolean userInitiated) { + this.screen = screen; + this.userInitiated = userInitiated; + } + + public PlayGameScreen getScreen() { + return screen; + } + + protected IFacade getFacade() { + return screen.getFacade(); + } + + protected GameState getGameState() { + return screen.getGameState(); + } + + @Override + public boolean turn(Worm worm, double angle) { + return executeCommand(new Turn(getFacade(), worm, angle, getScreen())); + } + + @Override + public boolean move(Worm worm) { + return executeCommand(new Move(getFacade(), worm, getScreen())); + } + + @Override + public boolean jump(Worm worm) { + return executeCommand(new Jump(getFacade(), worm, getScreen())); + } + + private boolean executeCommand(final Command cmd) { + if (userInitiated) { + executor.execute(new Runnable() { + + @Override + public void run() { + getGameState().executeImmediately(cmd); + } + }); + return true; + } else { + boolean result = getGameState().executeImmediately(cmd); + try { + Thread.sleep(500); + } catch (InterruptedException e) { + } + return result; + } + } + + @Override + public void print(String message) { + getScreen().addMessage(message, MessageType.INFO); + } + + public void changeName(Worm worm, String newName) { + executeCommand(new Rename(getFacade(), worm, newName, 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())); + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/IActionHandler.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/IActionHandler.java new file mode 100644 index 0000000..e0bb5c5 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/IActionHandler.java @@ -0,0 +1,44 @@ +package worms.internal.gui.game; + +import worms.model.Worm; + +/** + *

+ * An action handler executes the actions of a worm as if they were commanded by + * a user by pressing the corresponding keys. + *

+ * + *

+ * An action, when executed through an action handler, + *

    + *
  • shows periodic updates on the GUI (such as jump animations) + *
  • eventually calls the corresponding facade methods, exactly like what + * happens with a human player + *
  • returns true if the action has been completed successfully; false + * otherwise + *
+ *

+ *

+ * Execution is blocked until the action has been entirely performed. + *

+ */ +public interface IActionHandler { + + public boolean turn(Worm worm, double angle); + + public boolean move(Worm worm); + + public boolean jump(Worm worm); + + /** + * Print a message on the screen for a short amount of time. + * + * This is a utility method, and not an action. You are not required to use + * this method for printing messages; you should only use it if you want the + * given message to appear on the screen while playing. + * + * The method may return before the message has been removed from the screen + * again. + */ + public void print(String message); +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/ImageSprite.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/ImageSprite.java new file mode 100644 index 0000000..423e3b9 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/ImageSprite.java @@ -0,0 +1,140 @@ +package worms.internal.gui.game; + +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; + +import javax.imageio.ImageIO; + +import worms.internal.gui.GUIUtils; + +public abstract class ImageSprite extends Sprite { + + // original image, at original scale + private final BufferedImage originalImage; + + // only created when scale != 1.0 + private BufferedImage scaledImage; + // only create when necessary + private BufferedImage scaledImageHflipped; + + private boolean hflipped = false; + + private double scale; + + protected ImageSprite(PlayGameScreen screen, String filename) { + super(screen); + this.scale = 1.0; + this.originalImage = loadImage(filename); + this.scaledImage = originalImage; + } + + @Override + public double getWidth(Graphics2D g) { + return getImageWidth() * scale; + } + + @Override + public double getHeight(Graphics2D g) { + return getImageHeight() * scale; + } + + public int getImageWidth() { + return originalImage.getWidth(); + } + + public int getImageHeight() { + return originalImage.getHeight(); + } + + public void setScale(double newScale) { + if (newScale == this.scale) { + return; + } + + this.scale = newScale; + if (newScale != 1.0) { + this.scaledImage = toBufferedImage(originalImage.getScaledInstance( + (int) (newScale * originalImage.getWidth()), + (int) (newScale * originalImage.getHeight()), + Image.SCALE_SMOOTH)); + } else { + this.scaledImage = originalImage; + } + + if (isHflipped()) { + this.scaledImageHflipped = hflip(this.scaledImage); + } else { + this.scaledImageHflipped = null; + } + } + + public double getScale() { + return scale; + } + + protected Image getImageToDraw() { + Image imageToDraw = scaledImage; + if (isHflipped()) { + if (scaledImageHflipped == null) { + scaledImageHflipped = hflip(scaledImage); + } + imageToDraw = scaledImageHflipped; + } + return imageToDraw; + } + + protected BufferedImage loadImage(String filename) { + try { + InputStream inputStream = GUIUtils.openResource(filename); + BufferedImage result = ImageIO.read(inputStream); + inputStream.close(); + return result; + } catch (IOException e) { + throw new RuntimeException( + "Could not read file '" + filename + "'", e); + } + } + + public void setHflipped(boolean value) { + hflipped = value; + } + + public boolean isHflipped() { + return hflipped; + } + + protected static BufferedImage hflip(BufferedImage image) { + BufferedImage flippedImage = new BufferedImage(image.getWidth(), + image.getHeight(), image.getType()); + Graphics2D flippedGraphics = flippedImage.createGraphics(); + flippedGraphics.scale(-1, 1); + flippedGraphics.drawImage(image, -image.getWidth(null), 0, null); + flippedGraphics.dispose(); + return flippedImage; + } + + protected static BufferedImage toBufferedImage(Image img) { + if (img instanceof BufferedImage) { + return (BufferedImage) img; + } + + BufferedImage result = new BufferedImage(img.getWidth(null), + img.getHeight(null), BufferedImage.TYPE_INT_ARGB); + + Graphics2D resultGraphics = result.createGraphics(); + resultGraphics.drawImage(img, 0, 0, null); + resultGraphics.dispose(); + + return result; + } + + @Override + public void draw(Graphics2D g) { + int x = (int) (getCenterX() - getWidth(g) / 2); + int y = (int) (getCenterY() - getHeight(g) / 2); + g.drawImage(getImageToDraw(), x, y, null); + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java new file mode 100644 index 0000000..d7f60bd --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreen.java @@ -0,0 +1,332 @@ +package worms.internal.gui.game; + +import java.awt.Graphics2D; +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 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.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.sprites.WormSprite; +import worms.model.Worm; + +public class PlayGameScreen extends Screen { + + final PlayGameScreenPainter painter; + private final GameState gameState; + + private final Set> sprites = new HashSet>(); + private final DefaultActionHandler userActionHandler; + private final IActionHandler programActionHandler; + + public PlayGameScreen(WormsGUI gui, GameState state) { + super(gui); + this.gameState = state; + this.painter = createPainter(); + this.userActionHandler = createUserActionHandler(); + this.programActionHandler = createProgramActionHandler(); + } + + protected DefaultActionHandler createUserActionHandler() { + return new DefaultActionHandler(this, true); + } + + protected IActionHandler createProgramActionHandler() { + return new DefaultActionHandler(this, false); + } + + @Override + protected InputMode createDefaultInputMode() { + return new DefaultInputMode(this, null); + } + + @Override + public void screenStarted() { + runGameLoop(); + userActionHandler.startGame(); + } + + final AtomicLong lastUpdateTimestamp = new AtomicLong(); + + final TimerTask gameLoop = new TimerTask() { + + @Override + public void run() { + long now = System.currentTimeMillis(); + long delta = now - lastUpdateTimestamp.getAndSet(now); + double dt = delta / 1000.0 * GUIConstants.TIME_SCALE; + gameState.evolve(dt); + repaint(); + } + }; + + private void runGameLoop() { + Timer timer = new Timer(); + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread t, Throwable e) { + gameLoop.cancel(); + e.printStackTrace(); + getGUI().showError( + e.getClass().getName() + ": " + e.getMessage()); + } + }); + lastUpdateTimestamp.set(System.currentTimeMillis()); + timer.scheduleAtFixedRate(gameLoop, 0, 1000 / GUIConstants.FRAMERATE); + } + + public synchronized void update() { + addNewSprites(); + for (Sprite sprite : sprites) { + sprite.update(); + } + } + + protected void addNewSprites() { + addNewWormSprites(); + } + + private void addNewWormSprites() { + Collection worms = getGameState().getWorms(); + if (worms != null) { + for (Worm worm : worms) { + WormSprite sprite = getWormSprite(worm); + if (sprite == null) { + createWormSprite(worm); + } + } + } + } + + private void createWormSprite(Worm worm) { + WormSprite sprite = new WormSprite(this, worm); + addSprite(sprite); + } + + public GameState getGameState() { + return gameState; + } + + public IFacade getFacade() { + return getGameState().getFacade(); + } + + protected PlayGameScreenPainter createPainter() { + return new PlayGameScreenPainter(this); + } + + public > Set getSpritesOfType(Class type) { + Set result = new HashSet(); + for (Sprite sprite : sprites) { + if (type.isInstance(sprite)) { + result.add(type.cast(sprite)); + } + } + return result; + } + + public > SpriteType getSpriteOfTypeFor( + Class type, ObjectType object) { + if (object == null) { + return null; + } + for (SpriteType sprite : getSpritesOfType(type)) { + if (object.equals(sprite.getObject())) { + return sprite; + } + } + return null; + } + + public WormSprite getWormSprite(Worm worm) { + return getSpriteOfTypeFor(WormSprite.class, worm); + } + + public void move() { + Worm worm = getSelectedWorm(); + + if (worm != null) { + userActionHandler.move(worm); + } + } + + public void jump() { + Worm worm = getSelectedWorm(); + if (worm != null) { + userActionHandler.jump(worm); + } + + } + + public void turn(double angle) { + Worm worm = getSelectedWorm(); + angle = GUIUtils.restrictAngle(angle, -Math.PI); + + if (worm != null) { + userActionHandler.turn(worm, angle); + } + } + + public void changeName(String newName) { + Worm worm = getSelectedWorm(); + + if (worm != null) { + userActionHandler.changeName(worm, newName); + } + } + + public synchronized Worm getSelectedWorm() { + return getGameState().getSelectedWorm(); + } + + @Override + protected void paintScreen(Graphics2D g) { + painter.paint(g); + } + + public static PlayGameScreen create(WormsGUI gui, GameState gameState, + boolean debugMode) { + if (!debugMode) { + return new PlayGameScreen(gui, gameState); + } else { + return new PlayGameScreen(gui, gameState) { + @Override + protected PlayGameScreenPainter createPainter() { + return new PlayGameScreenDebugPainter(this); + } + }; + } + } + + public void addSprite(Sprite sprite) { + sprites.add(sprite); + } + + public void removeSprite(Sprite sprite) { + sprites.remove(sprite); + } + + /** + * Scale of the displayed world (in worm-meter per pixel) + */ + private double getDisplayScale() { + return GUIConstants.DISPLAY_SCALE; + } + + /** + * Distance in the world (worm-meter) to distance on the screen (pixels) + */ + public double worldToScreenDistance(double ds) { + return ds / getDisplayScale(); + } + + /** + * Distance on the screen (pixels) to distance in the world (worm-meter) + */ + public double screenToWorldDistance(double ds) { + return ds * getDisplayScale(); + } + + /** + * World x coordinate to screen x coordinate + */ + public double getScreenX(double x) { + return getScreenWidth()/2.0 + worldToScreenDistance(x); + } + + /** + * Screen x coordinate to world x coordinate + */ + public double getLogicalX(double screenX) { + return screenToWorldDistance(screenX - getScreenWidth()/2.0); + } + + /** + * World y coordinate to screen y coordinate + */ + public double getScreenY(double y) { + return getScreenHeight()/2.0 - worldToScreenDistance(y); + } + + /** + * Screen y coordinate to world y coordinate + */ + public double getLogicalY(double screenY) { + return screenToWorldDistance(getScreenHeight()/2.0 - screenY); + } + + public void paintTextEntry(Graphics2D g, String message, String enteredName) { + painter.paintTextEntry(g, message, enteredName); + } + + public void drawTurnAngleIndicator(Graphics2D g, WormSprite wormSprite, + double currentAngle) { + painter.drawTurnAngleIndicator(g, wormSprite, currentAngle); + } + + public > void removeSpriteFor(Class type, T object) { + S sprite = getSpriteOfTypeFor(type, object); + sprites.remove(sprite); + } + + @SuppressWarnings("unchecked") + @Override + public InputMode getCurrentInputMode() { + return (InputMode) super.getCurrentInputMode(); + } + + public void gameStarted() { + switchInputMode(new DefaultInputMode(this, getCurrentInputMode())); + } + + public void renameWorm() { + switchInputMode(new EnteringNameMode("Enter new name for worm: ", this, + getCurrentInputMode(), new EnteringNameMode.Callback() { + @Override + public void onNameEntered(String newName) { + changeName(newName); + } + })); + } + + public void showInstructions(Graphics2D g, String string) { + painter.paintInstructions(g, string); + } + + public WormSprite getSelectedWormSprite() { + return getWormSprite(getSelectedWorm()); + } + + public void selectNextWorm() { + getGameState().selectNextWorm(); + } + + public IActionHandler getProgramActionHandler() { + return programActionHandler; + } + + public void selectWorm(Worm worm) { + while (getSelectedWorm() != worm) { + selectNextWorm(); + } + } + + public void resizeWorm(int sign) { + Worm worm = getSelectedWorm(); + + if (worm != null) { + userActionHandler.resizeWorm(worm, sign); + } + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java new file mode 100644 index 0000000..19e64b5 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenDebugPainter.java @@ -0,0 +1,97 @@ +package worms.internal.gui.game; + +import java.awt.Color; +import java.awt.Shape; + +import worms.internal.gui.GUIUtils; +import worms.internal.gui.game.sprites.WormSprite; + +public class PlayGameScreenDebugPainter extends PlayGameScreenPainter { + + private static final int LOCATION_MARKER_SIZE = 4; + + public PlayGameScreenDebugPainter(PlayGameScreen screen) { + super(screen); + } + + @Override + protected void paintWorm(WormSprite sprite) { + + drawName(sprite); + + drawActionBar(sprite); + + drawOutline(sprite); + drawJumpMarkers(sprite); // also draw for other worms + + drawDirectionLine(sprite); + + drawLocationMarker(sprite); + + } + + @Override + protected void paintLevel() { + drawCrossMarker(getScreenX(0), getScreenY(0), 10, Color.BLUE); + } + + @Override + protected void drawJumpMarkers(WormSprite sprite) { + + double[][] xys = sprite.getJumpSteps(); + if (xys != null) { + double[] prevXY = xys[0]; + for (int i = 1; i < xys.length; i++) { + double[] xy = xys[i]; + if (xy != null && prevXY != null) { + double jumpX = getScreenX(xy[0]); + double jumpY = getScreenY(xy[1]); + currentGraphics.setColor(JUMP_MARKER_COLOR); + currentGraphics.drawLine((int) getScreenX(prevXY[0]), + (int) getScreenY(prevXY[1]), (int) jumpX, + (int) jumpY); + prevXY = xy; + drawCrossMarker(jumpX, jumpY, JUMP_MARKER_SIZE, + JUMP_MARKER_COLOR); + } + } + } + } + + /** + * Draw a marker at the current location of the worm (which is not + * necessarily equal to the sprite's location) + */ + protected void drawLocationMarker(WormSprite worm) { + double x = worm.getActualX(); + double y = worm.getActualY(); + + drawCrossMarker(getScreenX(x), getScreenY(y), LOCATION_MARKER_SIZE, + Color.YELLOW); + } + + protected void drawOutline(WormSprite sprite) { + double r = sprite.getRadius(); + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + + currentGraphics.setColor(Color.YELLOW); + Shape circle = GUIUtils.circleAt(x, y, getScreen() + .worldToScreenDistance(r)); + currentGraphics.draw(circle); + + } + + protected void drawDirectionLine(WormSprite sprite) { + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + double dist = sprite.getHeight(currentGraphics) / 2.0; + double direction = sprite.getOrientation(); + + currentGraphics.setColor(Color.YELLOW); + 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 new file mode 100644 index 0000000..5d4578a --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/PlayGameScreenPainter.java @@ -0,0 +1,260 @@ +package worms.internal.gui.game; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.awt.Shape; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Rectangle2D; +import java.awt.geom.RoundRectangle2D; +import java.util.ArrayList; +import java.util.List; +import java.util.StringTokenizer; + +import worms.internal.gui.AbstractPainter; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.GameState; +import worms.internal.gui.game.sprites.WormSprite; + +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 INVALID_TURN_ANGLE_MARKER_COLOR = Color.RED; + 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; + + 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_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_TEXT_COLOR = Color.WHITE; + protected static final Color JUMP_MARKER_COLOR = Color.GRAY; + + protected static final int JUMP_MARKER_SIZE = 1; + protected static final double DIRECTION_INDICATOR_SIZE = 10; + + protected Graphics2D currentGraphics; + + public PlayGameScreenPainter(PlayGameScreen screen) { + super(screen); + } + + protected GameState getState() { + return getScreen().getGameState(); + } + + public void paint(Graphics2D g) { + this.currentGraphics = g; + + paintLevel(); + + for (WormSprite sprite : getScreen().getSpritesOfType(WormSprite.class)) { + if (sprite.getWorm() == getScreen().getSelectedWorm()) { + drawSelection(sprite); + } + paintWorm(sprite); + } + + this.currentGraphics = null; + } + + protected void paintLevel() { + + } + + protected double getScreenX(double x) { + return getScreen().getScreenX(x); + } + + protected double getScreenY(double y) { + return getScreen().getScreenY(y); + } + + protected void paintWorm(WormSprite sprite) { + + sprite.draw(currentGraphics); + + drawName(sprite); + + drawActionBar(sprite); + + if (getScreen().getSelectedWorm() == sprite.getWorm()) { + drawDirectionIndicator(sprite); + drawJumpMarkers(sprite); + } + } + + protected void drawName(WormSprite sprite) { + final double voffset = sprite.getHeight(currentGraphics) / 2; + String name = sprite.getName(); + + if (name == null) { + name = "(null)"; + } + + 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); + currentGraphics.setColor(NAME_BAR_BACKGROUND); + currentGraphics.fill(nameBarFill); + + currentGraphics.setColor(NAME_BAR_TEXT); + + currentGraphics.drawString(name, (float) x, (float) (y)); + } + + protected void drawActionBar(WormSprite sprite) { + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + double spriteHeight = sprite.getHeight(currentGraphics); + + 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); + 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); + currentGraphics.setColor(BAR_OUTLINE_COLOR); + currentGraphics.draw(actionBar); + } + + protected void drawSelection(WormSprite sprite) { + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + double spriteHeight = Math.max(sprite.getWidth(currentGraphics), sprite.getHeight(currentGraphics)); + + currentGraphics.setColor(SELECTION_FILL_COLOR); + + Shape circle = GUIUtils.circleAt(x, y, spriteHeight / 2); + currentGraphics.fill(circle); + } + + protected void drawDirectionIndicator(WormSprite sprite) { + double x = sprite.getCenterX(); + double y = sprite.getCenterY(); + 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); + currentGraphics.fill(directionIndicator); + } + + 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; + distance += DIRECTION_INDICATOR_SIZE / 2; + 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); } + */ + 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); + graphics.fill(directionIndicator); + } + + protected void drawJumpMarkers(WormSprite sprite) { + double[][] xys = sprite.getJumpSteps(); + if (xys != null) { + for (double[] xy : xys) { + if (xy != null) { + double jumpX = getScreenX(xy[0]); + double jumpY = getScreenY(xy[1]); + drawCrossMarker(jumpX, jumpY, JUMP_MARKER_SIZE, JUMP_MARKER_COLOR); + } + } + } + } + + 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)); + } + + void paintTextEntry(Graphics2D g, String message, String enteredText) { + g.setColor(RENAME_BACKGROUND_COLOR); + 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); + } + + public void paintInstructions(Graphics2D g, String message) { + int lineHeight = 25; + Font oldFont = g.getFont(); + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 2 * lineHeight / 3)); + + StringTokenizer tok = new StringTokenizer(message, "\n"); + int nbLines = tok.countTokens(); + List lines = new ArrayList(nbLines); + while (tok.hasMoreTokens()) { + lines.add(tok.nextToken()); + } + + int maxWidth = 0; + for (String line : lines) { + Rectangle2D bounds = g.getFontMetrics().getStringBounds(line, g); + maxWidth = Math.max(maxWidth, (int) (bounds.getWidth() + 0.5)); + } + + int width = 2 * lineHeight + maxWidth; + int height = 2 * lineHeight + lineHeight * nbLines; + int top = 0; + int left = 0; + + g.setColor(new Color(0xa0565656, true)); + g.fillRect(left, top, width, height); + g.setColor(Color.WHITE); + + int y = top + 2 * lineHeight; + for (String line : lines) { + g.drawString(line, left + lineHeight, y); + y += lineHeight; + } + + g.setFont(oldFont); + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java new file mode 100644 index 0000000..d4ce2c3 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/Sprite.java @@ -0,0 +1,64 @@ +package worms.internal.gui.game; + +import java.awt.Graphics2D; + +import worms.facade.IFacade; + +public abstract class Sprite { + + private double x; + private double y; + private final PlayGameScreen screen; + + protected Sprite(PlayGameScreen screen) { + this.screen = screen; + } + + public PlayGameScreen getScreen() { + return screen; + } + + public abstract T getObject(); + + protected IFacade getFacade() { + return getScreen().getFacade(); + } + + public abstract void draw(Graphics2D g); + + /** + * Height (in pixels) of this sprite, when drawn to the given graphics object + * @return + */ + public abstract double getHeight(Graphics2D g); + + /** + * Width (in pixels) of this sprite, when drawn to the given graphics object + * @return + */ + public abstract double getWidth(Graphics2D g); + + public synchronized double[] getCenterLocation() { + return new double[] { getCenterX(), getCenterY() }; + } + + public synchronized void setCenterLocation(double x, double y) { + this.x = x; + this.y = y; + } + + public synchronized double getCenterX() { + return x; + } + + public synchronized double getCenterY() { + return y; + } + + /** + * Update attributes of this sprite with values from the object + */ + public synchronized void update() { + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..4fdb594 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Command.java @@ -0,0 +1,141 @@ +package worms.internal.gui.game.commands; + +import worms.facade.IFacade; +import worms.internal.gui.game.PlayGameScreen; + +public abstract class Command { + + private final IFacade facade; + private final PlayGameScreen screen; + + private double elapsedTime; + private boolean cancelled = false; + private boolean completed = false; + private boolean started = false; + + protected Command(IFacade facade, PlayGameScreen screen) { + this.facade = facade; + this.screen = screen; + } + + public PlayGameScreen getScreen() { + return screen; + } + + protected IFacade getFacade() { + return facade; + } + + public final void startExecution() { + if (canStart()) { + started = true; + doStartExecution(); + afterExecutionStarted(); + } else { + cancelExecution(); + } + } + + protected final void cancelExecution() { + cancelled = true; + afterExecutionCancelled(); + } + + protected final void completeExecution() { + completed = true; + afterExecutionCompleted(); + } + + public final void update(double dt) { + if (!isTerminated()) { + elapsedTime += dt; + doUpdate(dt); + if (isTerminated()) { + getScreen().update(); + } + } + } + + /** + * Returns the total time that has elapsed while executing this command + */ + public double getElapsedTime() { + return elapsedTime; + } + + /** + * Returns whether or not this command has been started + */ + public boolean hasBeenStarted() { + return started; + } + + /** + * 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()); + } + + /** + * Returns whether or not the execution of the command has been cancelled. + */ + public final boolean isExecutionCancelled() { + return cancelled; + } + + /** + * Returns whether or not the execution of the command has been completed + * successfully. + */ + public final boolean isExecutionCompleted() { + return completed; + } + + /** + * Returns whether or not the execution of the command can start + */ + protected abstract boolean canStart(); + + /** + * Start executing the command + */ + protected abstract void doStartExecution(); + + /** + * Called when the execution of the command has been completed successfully. + */ + protected void afterExecutionCompleted() { + } + + /** + * Called when the execution of the command has been cancelled. + */ + protected void afterExecutionCancelled() { + } + + /** + * Called when the execution of the command has been started. + */ + protected void afterExecutionStarted() { + } + + /** + * Update the execution of the command by the given time interval + * + * @param dt + */ + protected abstract void doUpdate(double dt); + + @Override + public String toString() { + 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/InstantaneousCommand.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/InstantaneousCommand.java new file mode 100644 index 0000000..e0919b6 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/InstantaneousCommand.java @@ -0,0 +1,20 @@ +package worms.internal.gui.game.commands; + +import worms.facade.IFacade; +import worms.internal.gui.game.PlayGameScreen; + +public abstract class InstantaneousCommand extends Command { + protected InstantaneousCommand(IFacade facade, PlayGameScreen screen) { + super(facade, screen); + } + + @Override + protected void afterExecutionStarted() { + completeExecution(); + getScreen().update(); + } + + @Override + protected final void doUpdate(double dt) { + } +} \ 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 new file mode 100644 index 0000000..b6ab2b9 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Jump.java @@ -0,0 +1,88 @@ +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 Jump extends Command { + private boolean hasJumped; + private final Worm worm; + private double jumpDuration; + + public Jump(IFacade facade, Worm worm, PlayGameScreen screen) { + super(facade, screen); + this.worm = worm; + } + + public Worm getWorm() { + return worm; + } + + + @Override + protected boolean canStart() { + return getWorm() != null; + } + + @Override + protected void doStartExecution() { + try { + this.jumpDuration = getFacade().getJumpTime(worm); + } catch (ModelException e) { + cancelExecution(); + } + } + + @Override + protected void afterExecutionCancelled() { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + sprite.setIsJumping(false); + } + getScreen().addMessage("This worm cannot jump :(", MessageType.ERROR); + } + + @Override + protected void afterExecutionCompleted() { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + sprite.setIsJumping(false); + } + } + + @Override + protected void doUpdate(double dt) { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + try { + sprite.setIsJumping(true); + 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)); + completeExecution(); + } + } else { + double[] xy = getFacade().getJumpStep(getWorm(), + getElapsedTime()); + sprite.setCenterLocation(getScreen().getScreenX(xy[0]), + getScreen().getScreenY(xy[1])); + } + } catch (ModelException e) { + e.printStackTrace(); + cancelExecution(); + } + } else { + cancelExecution(); + } + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..79d575e --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Move.java @@ -0,0 +1,97 @@ +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; +import worms.model.Worm; +import worms.util.ModelException; + +public class Move extends Command { + + private double startX; + private double startY; + + private double finalX; + private double finalY; + + private final Worm worm; + + public Move(IFacade facade, Worm worm, PlayGameScreen screen) { + super(facade, screen); + this.worm = worm; + } + + public Worm getWorm() { + return worm; + } + + @Override + protected boolean canStart() { + return getWorm() != null; + } + + private double getDuration() { + return GUIConstants.MOVE_DURATION; + } + + @Override + protected void doUpdate(double dt) { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + sprite.setIsMoving(true); + if (getElapsedTime() < getDuration()) { + double t = getElapsedTime() / getDuration(); + t = t * t * (3 - 2 * t); // smooth-step interpolation + double x = (1.0 - t) * startX + t * finalX; + double y = (1.0 - t) * startY + t * finalY; + sprite.setCenterLocation(x, y); + } else { + completeExecution(); + } + } else { + cancelExecution(); + } + } + + @Override + protected void afterExecutionCompleted() { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + sprite.setIsMoving(false); + } + } + + @Override + protected void afterExecutionCancelled() { + WormSprite sprite = getScreen().getWormSprite(getWorm()); + if (sprite != null) { + sprite.setIsMoving(false); + } + getScreen().addMessage("This worm cannot move like that :(", + 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()); + } catch (ModelException e) { + e.printStackTrace(); + cancelExecution(); + } + } + + protected double getObjectX() { + return getFacade().getX(getWorm()); + } + + protected double getObjectY() { + return getFacade().getY(getWorm()); + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Rename.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Rename.java new file mode 100644 index 0000000..098caae --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Rename.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.model.Worm; +import worms.util.ModelException; + +public class Rename extends InstantaneousCommand { + private final String newName; + private final Worm worm; + + public Rename(IFacade facade, Worm worm, String newName, + PlayGameScreen screen) { + super(facade, screen); + this.worm = worm; + this.newName = newName; + } + + @Override + protected boolean canStart() { + return worm != null; + } + + @Override + protected void doStartExecution() { + try { + getFacade().rename(worm, newName); + } catch (ModelException e) { + // an invalid name + getScreen().addMessage("Invalid name: " + newName, MessageType.ERROR); + } + } +} \ 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 new file mode 100644 index 0000000..246f725 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Resize.java @@ -0,0 +1,40 @@ +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/StartGame.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java new file mode 100644 index 0000000..121901b --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/StartGame.java @@ -0,0 +1,32 @@ +package worms.internal.gui.game.commands; + +import java.util.Collection; + +import worms.facade.IFacade; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.model.Worm; + +public class StartGame extends InstantaneousCommand { + + public StartGame(IFacade facade, PlayGameScreen screen) { + super(facade, screen); + } + + @Override + protected boolean canStart() { + Collection worms = getScreen().getGameState().getWorms(); + return worms != null && !worms.isEmpty(); + } + + @Override + protected void afterExecutionCancelled() { + getScreen().addMessage("Cannot start the game without worms", MessageType.ERROR); + } + + @Override + protected void doStartExecution() { + getScreen().gameStarted(); + } + +} 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 new file mode 100644 index 0000000..e5c21fd --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/commands/Turn.java @@ -0,0 +1,36 @@ +package worms.internal.gui.game.commands; + +import worms.facade.IFacade; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.messages.MessageType; +import worms.model.Worm; + +public class Turn extends InstantaneousCommand { + private final Worm worm; + private final double angle; + + public Turn(IFacade facade, Worm worm, double angle, PlayGameScreen screen) { + super(facade, screen); + this.worm = worm; + this.angle = angle; + } + + @Override + protected boolean canStart() { + return worm != null; + } + + @Override + protected void afterExecutionCancelled() { + getScreen().addMessage("This worm cannot perform that turn :(", + MessageType.ERROR); + } + + @Override + protected void doStartExecution() { + double direction = getFacade().getOrientation(worm); + double angleToTurn = GUIUtils.restrictDirection(direction + angle) - direction; + getFacade().turn(worm, angleToTurn); + } +} \ 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 new file mode 100644 index 0000000..c72a4ac --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/DefaultInputMode.java @@ -0,0 +1,89 @@ +package worms.internal.gui.game.modes; + +import java.awt.Point; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import worms.internal.gui.InputMode; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.game.sprites.WormSprite; +import worms.model.Worm; + +public class DefaultInputMode extends InputMode { + + /** + * @param playGameScreen + */ + public DefaultInputMode(PlayGameScreen playGameScreen, + InputMode previous) { + super(playGameScreen, previous); + } + + @Override + public void mouseClicked(MouseEvent e) { + if (getScreen().getGUI().getOptions().enableClickToSelect) { + Point point = e.getPoint(); + for (WormSprite sprite : getScreen().getSpritesOfType( + WormSprite.class)) { + Worm worm = sprite.getWorm(); + if (sprite.hitTest(point.getX(), point.getY())) { + getScreen().selectWorm(worm); + return; + } + } + } + } + + @Override + public void mouseDragged(MouseEvent e) { + getScreen().switchInputMode(new TurningMode(getScreen(), this)); + getScreen().getCurrentInputMode().mouseDragged(e); + } + + @Override + public void keyTyped(KeyEvent e) { + switch (e.getKeyChar()) { + case 'j': + case 'J': + getScreen().jump(); + break; + case 'n': + case 'N': + getScreen().renameWorm(); + break; + case '+': + getScreen().resizeWorm(+1); + break; + case '-': + getScreen().resizeWorm(-1); + break; + } + } + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_UP: + getScreen().move(); + break; + case KeyEvent.VK_ESCAPE: + getScreen().getGUI().exit(); + break; + case KeyEvent.VK_TAB: + getScreen().selectNextWorm(); + break; + } + } + + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_LEFT: + case KeyEvent.VK_RIGHT: + getScreen().switchInputMode(new TurningMode(getScreen(), this)); + getScreen().getCurrentInputMode().keyPressed(e); + break; + } + } + +} \ No newline at end of file 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 new file mode 100644 index 0000000..e7a0784 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/EnteringNameMode.java @@ -0,0 +1,62 @@ +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 EnteringNameMode extends InputMode { + + public static interface Callback { + public void onNameEntered(String newName); + } + + private final String message; + private final Callback callback; + + /** + * @param playGameScreen + */ + public EnteringNameMode(String message, PlayGameScreen playGameScreen, InputMode previous, Callback callback) { + super(playGameScreen, previous); + this.message = message; + this.callback = callback; + } + + private String enteredName = ""; + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ENTER: + if (callback != null) { + callback.onNameEntered(enteredName); + } + leaveInputMode(); + break; + case KeyEvent.VK_ESCAPE: + leaveInputMode(); + break; + } + } + + @Override + public void keyTyped(KeyEvent e) { + if (e.getKeyChar() == '\b') { + enteredName = enteredName.substring(0, + Math.max(0, enteredName.length() - 1)); + } else if (!Character.isISOControl(e.getKeyChar()) + && e.getKeyChar() != KeyEvent.CHAR_UNDEFINED) { + enteredName += e.getKeyChar(); + } + getScreen().repaint(); + } + + @Override + public void paintOverlay(Graphics2D g) { + super.paintOverlay(g); + getScreen().paintTextEntry(g, message, enteredName); + } + +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/TurningMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/TurningMode.java new file mode 100644 index 0000000..a9ee916 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/modes/TurningMode.java @@ -0,0 +1,116 @@ +package worms.internal.gui.game.modes; + +import java.awt.Graphics2D; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; + +import worms.internal.gui.GUIConstants; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.InputMode; +import worms.internal.gui.game.PlayGameScreen; +import worms.internal.gui.game.sprites.WormSprite; + +public class TurningMode extends InputMode { + + public TurningMode(PlayGameScreen playGameScreen, + InputMode previous) { + super(playGameScreen, previous); + } + + private double angle = 0; + + private long pressedSince = 0; // 0 if not turning + private boolean clockwise; + + private void startTurning(boolean clockwise) { + if (!isTurning()) { + pressedSince = System.currentTimeMillis(); + this.clockwise = clockwise; + } + } + + private void stopTurning() { + angle = getCurrentAngle(); + pressedSince = 0; + } + + private boolean isTurning() { + return pressedSince != 0; + } + + @Override + public void mouseDragged(MouseEvent e) { + WormSprite sprite = getScreen().getSelectedWormSprite(); + if (sprite != null) { + double[] wormXY = sprite.getCenterLocation(); + double currentOrientation = sprite.getOrientation(); + this.angle = Math.PI + - currentOrientation + + Math.atan2((e.getY() - wormXY[1]), (wormXY[0] - e.getX())); + } + } + + @Override + public void mouseReleased(MouseEvent e) { + finishTurn(); + } + + private void finishTurn() { + if (angle != 0) { + getScreen().turn(angle); + leaveInputMode(); + } + } + + @Override + public void keyPressed(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_RIGHT: + startTurning(true); + break; + case KeyEvent.VK_LEFT: + startTurning(false); + break; + } + } + + @Override + public void keyReleased(KeyEvent e) { + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + leaveInputMode(); + break; + case KeyEvent.VK_ENTER: + finishTurn(); + break; + case KeyEvent.VK_LEFT: // no-break + case KeyEvent.VK_RIGHT: + stopTurning(); + break; + } + } + + private double getCurrentAngle() { + double delta = 0; + if (isTurning()) { + long now = System.currentTimeMillis(); + delta = Math.max(GUIConstants.MIN_TURN_ANGLE, (now - pressedSince) + / 1000.0 * GUIConstants.ANGLE_TURNED_PER_SECOND); + if (clockwise) { + delta = -delta; + } + return GUIUtils.restrictAngle(angle + delta, -Math.PI); + } else { + return angle; + } + } + + @Override + public void paintOverlay(Graphics2D g) { + super.paintOverlay(g); + WormSprite sprite = getScreen().getSelectedWormSprite(); + if (sprite != null) { + getScreen().drawTurnAngleIndicator(g, sprite, getCurrentAngle()); + } + } +} \ No newline at end of file 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 new file mode 100644 index 0000000..25a7350 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/game/sprites/WormSprite.java @@ -0,0 +1,168 @@ +package worms.internal.gui.game.sprites; + +import worms.internal.gui.GUIUtils; +import worms.internal.gui.game.ImageSprite; +import worms.internal.gui.game.PlayGameScreen; +import worms.model.Worm; +import worms.util.ModelException; + +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 long actionPoints; + private long maxActionPoints; + private double actualX; + private double actualY; + private double radius; + + public WormSprite(PlayGameScreen screen, Worm worm) { + super(screen, "images/worm.png"); + this.worm = worm; + update(); + } + + @Override + public Worm getObject() { + return getWorm(); + } + + public Worm getWorm() { + return worm; + } + + private void setDirection(double newDirection) { + double direction = GUIUtils.restrictDirection(newDirection); + this.orientation = direction; + + if (Math.PI / 2 > direction || 3 * Math.PI / 2 < direction) { + setHflipped(true); + } else { + setHflipped(false); + } + } + + /** + * @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 = 0.8; + + double scaleFactor = fitFactor * 2 * radius / imageHeightInMeters; + + // limit scaling + scaleFactor = Math.max(MIN_SCALE, Math.min(scaleFactor, MAX_SCALE)); + + setScale(scaleFactor); + } + + public boolean hitTest(double screenX, double screenY) { + double radius = getScale() * Math.max(getImageWidth(), getImageHeight()) / 2.0; + double dx = screenX - getCenterX(); + double dy = screenY - getCenterY(); + return dx * dx + dy * dy <= radius * radius; + } + + @Override + public synchronized void update() { + if (isJumping || isMoving) { + // don't update the location here, because it may differ from the + // location in the model + } else { + setCenterLocation(getScreen().getScreenX(getFacade().getX(getWorm())), + getScreen().getScreenY(getFacade().getY(worm))); + } + this.actualX = getFacade().getX(getWorm()); + this.actualY = getFacade().getY(getWorm()); + setRadius(getFacade().getRadius(getWorm())); + setDirection(getFacade().getOrientation(getWorm())); + updateJumpTime(); + setName(getFacade().getName(getWorm())); + this.actionPoints = getFacade().getNbActionPoints(getWorm()); + this.maxActionPoints = getFacade().getMaxNbActionPoints(getWorm()); + } + + public void setIsJumping(boolean isJumping) { + this.isJumping = isJumping; + } + + public void setIsMoving(boolean isMoving) { + this.isMoving = isMoving; + } + + protected static final double JUMP_MARKER_TIME_DISTANCE = 0.1; // worm-seconds + + private void updateJumpTime() { + try { + double time = getFacade().getJumpTime(getWorm()); + if (time > 0) { + int n = 1 + (int) (time / JUMP_MARKER_TIME_DISTANCE); + xys = new double[n][]; + for (int i = 1; i <= n; i++) { + double dt = i * time / n; + double[] xy = getFacade().getJumpStep(getWorm(), dt); + xys[i - 1] = xy; + } + } else { + this.xys = null; + } + } catch (ModelException e) { + this.xys = null; + } + } + + public synchronized double[][] getJumpSteps() { + return xys; + } + + public synchronized double getOrientation() { + return orientation; + } + + public synchronized String getName() { + return name; + } + + private void setName(String name) { + this.name = name; + } + + public synchronized long getActionPoints() { + return actionPoints; + } + + public synchronized long getMaxActionPoints() { + return maxActionPoints; + } + + public synchronized double getActualX() { + return actualX; + } + + public synchronized double getActualY() { + return actualY; + } + + public synchronized double getRadius() { + return radius; + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/menu/AbstractMenuScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/menu/AbstractMenuScreen.java new file mode 100644 index 0000000..5dbc827 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/menu/AbstractMenuScreen.java @@ -0,0 +1,114 @@ +package worms.internal.gui.menu; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; + +import worms.internal.gui.GUIUtils; +import worms.internal.gui.InputMode; +import worms.internal.gui.Screen; +import worms.internal.gui.WormsGUI; + +public abstract class AbstractMenuScreen extends Screen { + + private static final int INSTRUCTIONS_AREA_HEIGHT = 100; + private static final int CHOICE_HEIGHT = 30; + + private static final Font DEFAULT_CHOICE_FONT = new Font(Font.SANS_SERIF, + Font.PLAIN, (CHOICE_HEIGHT * 4) / 6); + private static final Color DEFAULT_CHOICE_COLOR = Color.WHITE; + private static final Font SELECTED_CHOICE_FONT = new Font(Font.SANS_SERIF, + Font.PLAIN, (CHOICE_HEIGHT * 5) / 6); + private static final Color SELECTED_CHOICE_COLOR = Color.YELLOW; + + final Choice[] choices; + + BlockingQueue selection = new ArrayBlockingQueue(1); + int selectedIndex = 0; + + public AbstractMenuScreen(WormsGUI gui) { + super(gui); + this.choices = getChoices(); + } + + public void selectNext() { + selectedIndex = (selectedIndex + 1) % choices.length; + repaint(); + } + + public void selectPrevious() { + selectedIndex = (selectedIndex + choices.length - 1) % choices.length; + repaint(); + } + + public void selectCurrent() { + if (selection.isEmpty()) + selection.add(choices[selectedIndex]); + } + + @Override + protected InputMode> createDefaultInputMode() { + return new MenuInputMode, Choice>(this, null); + } + + protected abstract Choice[] getChoices(); + + protected abstract String getDisplayName(Choice choice); + + protected abstract String getInstructions(); + + public Choice select() { + try { + return selection.take(); + } catch (InterruptedException e) { + e.printStackTrace(); + return null; + } + } + + @Override + protected void paintScreen(Graphics2D g) { + paintInstructions(g); + + int maxNbChoicesOnScreen = (getScreenHeight() - INSTRUCTIONS_AREA_HEIGHT) + / CHOICE_HEIGHT - 1; + int start = 0; + if (selectedIndex >= maxNbChoicesOnScreen) { + start = selectedIndex - maxNbChoicesOnScreen + 1; + } + + int lastChoiceToDisplay = Math.min(start + maxNbChoicesOnScreen, + choices.length); + for (int index = start; index < lastChoiceToDisplay; index++) { + Choice choice = choices[index]; + String str = getDisplayName(choice); + if (index == selectedIndex) { + g.setColor(SELECTED_CHOICE_COLOR); + g.setFont(SELECTED_CHOICE_FONT); + str = "\u00bb " + str + " \u00ab"; + } else { + g.setColor(DEFAULT_CHOICE_COLOR); + g.setFont(DEFAULT_CHOICE_FONT); + } + GUIUtils.drawCenteredString(g, str, getScreenWidth(), + INSTRUCTIONS_AREA_HEIGHT + CHOICE_HEIGHT * (index - start)); + } + if (lastChoiceToDisplay < choices.length) { + g.setFont(DEFAULT_CHOICE_FONT); + g.setColor(DEFAULT_CHOICE_COLOR); + GUIUtils.drawCenteredString(g, "...", getScreenWidth(), + INSTRUCTIONS_AREA_HEIGHT + CHOICE_HEIGHT + * maxNbChoicesOnScreen); + } + } + + private void paintInstructions(Graphics2D g) { + g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 20)); + g.setColor(Color.WHITE); + GUIUtils.drawCenteredString(g, getInstructions(), getScreenWidth(), + INSTRUCTIONS_AREA_HEIGHT / 2); + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java new file mode 100644 index 0000000..3453d40 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MainMenuScreen.java @@ -0,0 +1,69 @@ +package worms.internal.gui.menu; + +import worms.internal.gui.GameState; +import worms.internal.gui.WormsGUI; +import worms.internal.gui.game.PlayGameScreen; + +enum MainMenuOption { + Play("Play worms"), PlayDebug("Play worms (debug mode)"), Exit("Exit"); + + private final String displayString; + + MainMenuOption(String displayString) { + this.displayString = displayString; + } + + public String getDisplayString() { + return displayString; + } +} + +public class MainMenuScreen extends AbstractMenuScreen { + + public MainMenuScreen(WormsGUI gui) { + super(gui); + } + + @Override + protected MainMenuOption[] getChoices() { + return MainMenuOption.values(); + } + + @Override + protected String getDisplayName(MainMenuOption option) { + return option.getDisplayString(); + } + + @Override + protected String getInstructions() { + return "Please make your choice"; + } + + @Override + public void screenStarted() { + MainMenuOption option = select(); + switch (option) { + case Play: + startGame(false); + break; + case PlayDebug: + startGame(true); + break; + case Exit: + getGUI().exit(); + } + } + + private void startGame(boolean debugMode) { + WormsGUI gui = getGUI(); + GameState gameState = new GameState(gui.getFacade(), + gui.getOptions().randomSeed, gui.getWidth(), gui.getHeight()); + + PlayGameScreen playGameScreen = PlayGameScreen.create(gui, gameState, + debugMode); + + gameState.startGame(); + getGUI().switchToScreen(playGameScreen); + } + +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/menu/MenuInputMode.java b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MenuInputMode.java new file mode 100644 index 0000000..52b79d0 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/menu/MenuInputMode.java @@ -0,0 +1,34 @@ +package worms.internal.gui.menu; + +import java.awt.event.KeyEvent; + +import worms.internal.gui.InputMode; + +public class MenuInputMode, Choice> extends + InputMode { + + public MenuInputMode(ST screen, + InputMode previous) { + super(screen, previous); + } + + @Override + public void keyReleased(KeyEvent e) { + + switch (e.getKeyCode()) { + case KeyEvent.VK_ESCAPE: + getScreen().getGUI().exit(); + break; + case KeyEvent.VK_DOWN: + getScreen().selectNext(); + + break; + case KeyEvent.VK_UP: + getScreen().selectPrevious(); + break; + case KeyEvent.VK_ENTER: + getScreen().selectCurrent(); + break; + } + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/messages/Message.java b/OGP1718-Worms/src-provided/worms/internal/gui/messages/Message.java new file mode 100644 index 0000000..f54ecec --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/messages/Message.java @@ -0,0 +1,50 @@ +package worms.internal.gui.messages; + +public class Message { + private final String message; + private final MessageType type; + + public Message(String message, MessageType type) { + this.message = message; + this.type = type; + } + + public String getText() { + return message; + } + public MessageType getType() { + return type; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + + ((message == null) ? 0 : message.hashCode()); + result = prime * result + + ((type == null) ? 0 : type.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + Message other = (Message) obj; + if (message == null) { + if (other.message != null) + return false; + } else if (!message.equals(other.message)) + return false; + if (type != other.type) + return false; + return true; + } + + +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageDisplay.java b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageDisplay.java new file mode 100644 index 0000000..13b8000 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageDisplay.java @@ -0,0 +1,51 @@ +package worms.internal.gui.messages; + +import java.util.LinkedList; + +import worms.internal.gui.GUIConstants; + +public class MessageDisplay { + private LinkedList messages = new LinkedList(); + private long currentMessageDisplayedSince; + + public MessageDisplay() { + } + + public void addMessage(String message, MessageType type) { + Message newMessage = new Message(message, type); + if (messages.isEmpty() || !messages.getLast().equals(newMessage)) + this.messages.add(newMessage); + } + + private boolean isDisplayingMessage() { + return currentMessageDisplayedSince > 0; + } + + private double currentDisplayTime() { + return (System.currentTimeMillis() - currentMessageDisplayedSince) / 1000.0; + } + + private Message currentMessage() { + return messages.peek(); + } + + private void gotoNextMessage() { + if (!messages.isEmpty()) { + currentMessageDisplayedSince = System.currentTimeMillis(); + } else { + currentMessageDisplayedSince = 0; + } + } + + public Message getMessage() { + if (isDisplayingMessage()) { + if (currentDisplayTime() >= GUIConstants.MESSAGE_DISPLAY_TIME) { + messages.remove(); + gotoNextMessage(); + } + } else { + gotoNextMessage(); + } + return currentMessage(); + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessagePainter.java b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessagePainter.java new file mode 100644 index 0000000..dd48413 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessagePainter.java @@ -0,0 +1,61 @@ +package worms.internal.gui.messages; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Graphics2D; +import java.util.StringTokenizer; + +import worms.internal.gui.AbstractPainter; +import worms.internal.gui.GUIUtils; +import worms.internal.gui.Screen; + +public class MessagePainter extends AbstractPainter { + + public MessagePainter(Screen screen) { + super(screen); + } + + protected static final Color ERROR_MESSAGE_BACKGROUND_COLOR = new Color( + 0x60a7130e, true); + protected static final Color NORMAL_MESSAGE_BACKGROUND_COLOR = new Color( + 0x600e13a7, true); + protected static final Color INFO_MESSAGE_BACKGROUND_COLOR = new Color( + 0x60565656, true); + protected static final Color MESSAGE_TEXT_COLOR = Color.WHITE; + + private static final int LINE_HEIGHT = 30; + + public void paintMessage(Graphics2D g, Message message) { + switch (message.getType()) { + case ERROR: + g.setColor(ERROR_MESSAGE_BACKGROUND_COLOR); + break; + case INFO: + g.setColor(INFO_MESSAGE_BACKGROUND_COLOR); + break; + default: + g.setColor(NORMAL_MESSAGE_BACKGROUND_COLOR); + } + + StringTokenizer tok = new StringTokenizer(message.getText(), "\n"); + int nbLines = tok.countTokens(); + + int height = LINE_HEIGHT * (nbLines + 2); + int top = (getScreen().getScreenHeight() - height) / 2; + + g.fillRect(0, top, getScreen().getScreenWidth(), height); + Font oldFont = g.getFont(); + g.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 2 * LINE_HEIGHT / 3)); + g.setColor(MESSAGE_TEXT_COLOR); + + int y = top + 2 * LINE_HEIGHT; + while (tok.hasMoreTokens()) { + String line = tok.nextToken(); + GUIUtils.drawCenteredString(g, line, getScreen().getScreenWidth(), + y); + y += LINE_HEIGHT; + } + + g.setFont(oldFont); + } +} diff --git a/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageType.java b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageType.java new file mode 100644 index 0000000..3038a98 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/internal/gui/messages/MessageType.java @@ -0,0 +1,5 @@ +package worms.internal.gui.messages; + +public enum MessageType { + INFO, NORMAL, ERROR +} \ No newline at end of file diff --git a/OGP1718-Worms/src-provided/worms/util/ModelException.java b/OGP1718-Worms/src-provided/worms/util/ModelException.java new file mode 100644 index 0000000..85cdce6 --- /dev/null +++ b/OGP1718-Worms/src-provided/worms/util/ModelException.java @@ -0,0 +1,21 @@ +package worms.util; + +@SuppressWarnings("serial") +/** + * Facade is not allowed to throw exceptions except for ModelException. + * + * Do not use ModelException outside of Facade. + */ +public class ModelException extends RuntimeException { + public ModelException(String message) { + super(message); + } + + public ModelException(Throwable cause) { + super(cause); + } + + public ModelException(String message, Throwable cause) { + super(message, cause); + } +} \ No newline at end of file diff --git a/OGP1718-Worms/src/.gitignore b/OGP1718-Worms/src/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java b/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java new file mode 100644 index 0000000..f0f2790 --- /dev/null +++ b/OGP1718-Worms/tests/worms/model/PartialFacadeTest.java @@ -0,0 +1,51 @@ +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); + } + +}