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

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

+ * + *

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

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

+ *

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

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