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 0000000..2e46497
Binary files /dev/null and b/OGP1718-Worms/images/worm.png differ
diff --git a/OGP1718-Worms/lib/AnnotationsDoclets.jar b/OGP1718-Worms/lib/AnnotationsDoclets.jar
new file mode 100644
index 0000000..b812d3a
Binary files /dev/null and b/OGP1718-Worms/lib/AnnotationsDoclets.jar differ
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).
+ *
+ *
+ *
For separating the code that you wrote from the code that was provided to
+ * you, put ALL your code in the src folder. The code that
+ * is provided to you stays in the src-provided folder. If you
+ * modify the provided code, you may need to manually merge any future bugfixes
+ * and update.
+ *
+ *
You should at least create the following classes for the code to compile:
+ *
+ *
a class Worm in the package worms.model for
+ * representing a worm
+ *
a class Facade in the package worms.facade that
+ * implements this interface (IFacade).
+ *
+ * You may, of course, add additional classes as you see fit.
+ *
+ *
The header of that Facade class should look as follows:
+ * class Facade implements IFacade { ... }
+ * Consult the
+ * Java tutorial for more information on interfaces, if necessary.
+ *
+ *
Each method defined in the interface IFacade must be
+ * implemented by the class Facade. For example, the implementation
+ * of getX should call a method of the given worm to
+ * retrieve its x-coordinate.
+ *
+ *
Your Facade class should offer a default constructor.
+ *
+ *
Methods in this interface are allowed to throw only
+ * ModelException. No other exception types are allowed. This
+ * exception can be thrown only if (1) calling the method with the given
+ * parameters would violate a precondition, or (2) if the method causes an
+ * exception to be thrown in your code (if so, wrap the exception in a
+ * ModelException).
+ *
+ *
ModelException should not be used anywhere outside of your Facade
+ * implementation.
+ *
+ *
Your Facade implementation should only contain trivial code (for
+ * example, calling a method, combining multiple return values into an array,
+ * creating @Value instances, catching exceptions and wrapping it in a
+ * ModelException). All non-trivial code should be placed in the other classes
+ * that you create.
+ *
+ *
The rules described above and the documentation described below for each
+ * method apply only to the class implementing IFacade. Your class for
+ * representing worms should follow the rules described in the assignment.
+ *
+ *
Do not modify the signatures of the methods defined in this
+ * interface.
+ *
+ *
+ */
+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 extends Screen> inputMode = getCurrentInputMode();
+ if (inputMode != null)
+ inputMode.paintOverlay(graphics);
+ }
+ };
+ result.setBackground(Color.BLACK);
+ return result;
+ }
+
+ public WormsGUI getGUI() {
+ return gui;
+ }
+
+ protected abstract InputMode extends Screen> createDefaultInputMode();
+
+ private InputMode extends Screen> currentInputMode;
+
+ public InputMode extends Screen> 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 extends AbstractMenuScreen> 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);
+ }
+
+}