Changes a bunch of code
All checks were successful
EpicKnarvik97/Rogue101/pipeline/head This commit looks good

Renames some badly named interfaces and classes
Changes a bunch of packages for better structure
Adds some missing comments
Improves path finding performance somewhat
Adds an NPC death sound
Makes fake walls noticeable
Makes all NPCs drop their items when they die (as the NPCs may steal items from a player)
This commit is contained in:
Kristian Knarvik 2023-08-13 14:24:53 +02:00
parent 74e03006c4
commit 5c19c3133c
118 changed files with 10314 additions and 10155 deletions

View File

@ -1,2 +1,3 @@
# Rogue101 # Rogue101
Originally a programming assignment in INF101. Added here for archival. Originally a programming assignment in INF101. Added here for archival.

View File

@ -1,12 +1,5 @@
package inf101.v18.gfx; package inf101.v18.gfx;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import inf101.v18.gfx.gfxmode.TurtlePainter; import inf101.v18.gfx.gfxmode.TurtlePainter;
import inf101.v18.gfx.textmode.Printer; import inf101.v18.gfx.textmode.Printer;
import inf101.v18.rogue101.AppInfo; import inf101.v18.rogue101.AppInfo;
@ -27,35 +20,64 @@ import javafx.stage.Stage;
import javafx.stage.StageStyle; import javafx.stage.StageStyle;
import javafx.stage.Window; import javafx.stage.Window;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
public class Screen { public class Screen {
private static final double STD_CANVAS_WIDTH = 1280; private static final double STD_CANVAS_WIDTH = 1280;
private static final List<Double> STD_ASPECTS = Arrays.asList(16.0 / 9.0, 16.0 / 10.0, 4.0 / 3.0); private static final List<Double> STD_ASPECTS = Arrays.asList(16.0 / 9.0, 16.0 / 10.0, 4.0 / 3.0);
/** 16:9 */ /**
* 16:9
*/
public static final int ASPECT_WIDE = 0; public static final int ASPECT_WIDE = 0;
/** 16:10 */ /**
* 16:10
*/
public static final int ASPECT_MEDIUM = 1; public static final int ASPECT_MEDIUM = 1;
/** 4:3 */ /**
* 4:3
*/
public static final int ASPECT_CLASSIC = 2; public static final int ASPECT_CLASSIC = 2;
public static final int ASPECT_NATIVE = 2; public static final int ASPECT_NATIVE = 2;
private static final int CONFIG_ASPECT_SHIFT = 0; private static final int CONFIG_ASPECT_SHIFT = 0;
/** Screen's initial aspect ratio should be 16:9 */ /**
* Screen's initial aspect ratio should be 16:9
*/
public static final int CONFIG_ASPECT_WIDE = 0 << CONFIG_ASPECT_SHIFT; public static final int CONFIG_ASPECT_WIDE = 0 << CONFIG_ASPECT_SHIFT;
/** Screen's initial aspect ratio should be 16:10 */ /**
* Screen's initial aspect ratio should be 16:10
*/
public static final int CONFIG_ASPECT_MEDIUM = 1 << CONFIG_ASPECT_SHIFT; public static final int CONFIG_ASPECT_MEDIUM = 1 << CONFIG_ASPECT_SHIFT;
/** Screen's initial aspect ratio should be 4:3 */ /**
* Screen's initial aspect ratio should be 4:3
*/
public static final int CONFIG_ASPECT_CLASSIC = 2 << CONFIG_ASPECT_SHIFT; public static final int CONFIG_ASPECT_CLASSIC = 2 << CONFIG_ASPECT_SHIFT;
/** Screen's initial aspect ratio should be the same as the device display. */ /**
* Screen's initial aspect ratio should be the same as the device display.
*/
public static final int CONFIG_ASPECT_DEVICE = 3 << CONFIG_ASPECT_SHIFT; public static final int CONFIG_ASPECT_DEVICE = 3 << CONFIG_ASPECT_SHIFT;
private static final int CONFIG_ASPECT_MASK = 3 << CONFIG_ASPECT_SHIFT; private static final int CONFIG_ASPECT_MASK = 3 << CONFIG_ASPECT_SHIFT;
private static final int CONFIG_SCREEN_SHIFT = 2; private static final int CONFIG_SCREEN_SHIFT = 2;
/** Screen should start in a window. */ /**
* Screen should start in a window.
*/
public static final int CONFIG_SCREEN_WINDOWED = 0 << CONFIG_SCREEN_SHIFT; public static final int CONFIG_SCREEN_WINDOWED = 0 << CONFIG_SCREEN_SHIFT;
/** Screen should start in a borderless window. */ /**
* Screen should start in a borderless window.
*/
public static final int CONFIG_SCREEN_BORDERLESS = 1 << CONFIG_SCREEN_SHIFT; public static final int CONFIG_SCREEN_BORDERLESS = 1 << CONFIG_SCREEN_SHIFT;
/** Screen should start in a transparent window. */ /**
* Screen should start in a transparent window.
*/
public static final int CONFIG_SCREEN_TRANSPARENT = 2 << CONFIG_SCREEN_SHIFT; public static final int CONFIG_SCREEN_TRANSPARENT = 2 << CONFIG_SCREEN_SHIFT;
/** Screen should start fullscreen. */ /**
* Screen should start fullscreen.
*/
public static final int CONFIG_SCREEN_FULLSCREEN = 3 << CONFIG_SCREEN_SHIFT; public static final int CONFIG_SCREEN_FULLSCREEN = 3 << CONFIG_SCREEN_SHIFT;
/** /**
* Screen should start fullscreen, without showing a "Press ESC to exit * Screen should start fullscreen, without showing a "Press ESC to exit
@ -67,7 +89,7 @@ public class Screen {
private static final int CONFIG_PIXELS_SHIFT = 5; private static final int CONFIG_PIXELS_SHIFT = 5;
/** /**
* Canvas size / number of pixels should be determined the default way. * Canvas size / number of pixels should be determined the default way.
* * <p>
* The default is {@link #CONFIG_PIXELS_DEVICE} for * The default is {@link #CONFIG_PIXELS_DEVICE} for
* {@link #CONFIG_SCREEN_FULLSCREEN} and {@link #CONFIG_COORDS_DEVICE}, and * {@link #CONFIG_SCREEN_FULLSCREEN} and {@link #CONFIG_COORDS_DEVICE}, and
* {@link #CONFIG_PIXELS_STEP_SCALED} otherwise. * {@link #CONFIG_PIXELS_STEP_SCALED} otherwise.
@ -76,12 +98,14 @@ public class Screen {
/** /**
* Canvas size / number of pixels will be an integer multiple or fraction of the * Canvas size / number of pixels will be an integer multiple or fraction of the
* logical canvas size that fits the native display size. * logical canvas size that fits the native display size.
* * <p>
* Scaling by whole integers makes it less likely that we get artifacts from * Scaling by whole integers makes it less likely that we get artifacts from
* rounding errors or JavaFX's antialiasing (e.g., fuzzy lines). * rounding errors or JavaFX's antialiasing (e.g., fuzzy lines).
*/ */
public static final int CONFIG_PIXELS_STEP_SCALED = 1 << CONFIG_PIXELS_SHIFT; public static final int CONFIG_PIXELS_STEP_SCALED = 1 << CONFIG_PIXELS_SHIFT;
/** Canvas size / number of pixels will the same as the native display size. */ /**
* Canvas size / number of pixels will the same as the native display size.
*/
public static final int CONFIG_PIXELS_DEVICE = 2 << CONFIG_PIXELS_SHIFT; public static final int CONFIG_PIXELS_DEVICE = 2 << CONFIG_PIXELS_SHIFT;
/** /**
* Canvas size / number of pixels will the same as the logical canvas size * Canvas size / number of pixels will the same as the logical canvas size
@ -100,7 +124,9 @@ public class Screen {
* pixels wide regardless of how many pixels wide the screen actually is) * pixels wide regardless of how many pixels wide the screen actually is)
*/ */
public static final int CONFIG_COORDS_LOGICAL = 0 << CONFIG_COORDS_SHIFT; public static final int CONFIG_COORDS_LOGICAL = 0 << CONFIG_COORDS_SHIFT;
/** The logical canvas coordinate system will match the display. */ /**
* The logical canvas coordinate system will match the display.
*/
public static final int CONFIG_COORDS_DEVICE = 1 << CONFIG_COORDS_SHIFT; public static final int CONFIG_COORDS_DEVICE = 1 << CONFIG_COORDS_SHIFT;
private static final int CONFIG_COORDS_MASK = 1 << CONFIG_COORDS_SHIFT; private static final int CONFIG_COORDS_MASK = 1 << CONFIG_COORDS_SHIFT;
@ -178,12 +204,11 @@ public class Screen {
/** /**
* Start the paint display system. * Start the paint display system.
* * <p>
* This will open a window on the screen, and set up background, text and paint * This will open a window on the screen, and set up background, text and paint
* layers, and listener to handle keyboard input. * layers, and listener to handle keyboard input.
* *
* @param stage * @param stage A JavaFX {@link javafx.stage.Stage}, typically obtained from the
* A JavaFX {@link javafx.stage.Stage}, typically obtained from the
* {@link javafx.application.Application#start(Stage)} method * {@link javafx.application.Application#start(Stage)} method
* @return A screen for drawing on * @return A screen for drawing on
*/ */
@ -193,12 +218,11 @@ public class Screen {
/** /**
* Start the paint display system. * Start the paint display system.
* * <p>
* This will open a window on the screen, and set up background, text and paint * This will open a window on the screen, and set up background, text and paint
* layers, and listener to handle keyboard input. * layers, and listener to handle keyboard input.
* *
* @param stage * @param stage A JavaFX {@link javafx.stage.Stage}, typically obtained from the
* A JavaFX {@link javafx.stage.Stage}, typically obtained from the
* {@link javafx.application.Application#start(Stage)} method * {@link javafx.application.Application#start(Stage)} method
* @return A screen for drawing on * @return A screen for drawing on
*/ */
@ -210,11 +234,12 @@ public class Screen {
int configFlags = (configuration & CONFIG_FLAG_MASK); int configFlags = (configuration & CONFIG_FLAG_MASK);
boolean debug = (configFlags & CONFIG_FLAG_DEBUG) != 0; boolean debug = (configFlags & CONFIG_FLAG_DEBUG) != 0;
if (configPixels == CONFIG_PIXELS_DEFAULT) { if (configPixels == CONFIG_PIXELS_DEFAULT) {
if (configCoords == CONFIG_COORDS_DEVICE || configScreen == CONFIG_SCREEN_FULLSCREEN) if (configCoords == CONFIG_COORDS_DEVICE || configScreen == CONFIG_SCREEN_FULLSCREEN) {
configPixels = CONFIG_PIXELS_DEVICE; configPixels = CONFIG_PIXELS_DEVICE;
else } else {
configPixels = CONFIG_PIXELS_STEP_SCALED; configPixels = CONFIG_PIXELS_STEP_SCALED;
} }
}
double rawWidth = getRawDisplayWidth(); double rawWidth = getRawDisplayWidth();
double rawHeight = getRawDisplayHeight(); double rawHeight = getRawDisplayHeight();
double width = getDisplayWidth() - 40; double width = getDisplayWidth() - 40;
@ -225,11 +250,12 @@ public class Screen {
double yScale = (width / canvasAspect) / (Screen.STD_CANVAS_WIDTH / canvasAspect); double yScale = (width / canvasAspect) / (Screen.STD_CANVAS_WIDTH / canvasAspect);
double scale = Math.min(xScale, yScale); double scale = Math.min(xScale, yScale);
if (configPixels == CONFIG_PIXELS_STEP_SCALED) { if (configPixels == CONFIG_PIXELS_STEP_SCALED) {
if (scale > 1.0) if (scale > 1.0) {
scale = Math.max(1, Math.floor(scale)); scale = Math.max(1, Math.floor(scale));
else if (scale < 1.0) } else if (scale < 1.0) {
scale = 1 / Math.max(1, Math.floor(1 / scale)); scale = 1 / Math.max(1, Math.floor(1 / scale));
} }
}
double winWidth = Math.floor(Screen.STD_CANVAS_WIDTH * scale); double winWidth = Math.floor(Screen.STD_CANVAS_WIDTH * scale);
double winHeight = Math.floor((Screen.STD_CANVAS_WIDTH / canvasAspect) * scale); double winHeight = Math.floor((Screen.STD_CANVAS_WIDTH / canvasAspect) * scale);
double canvasWidth = Screen.STD_CANVAS_WIDTH; double canvasWidth = Screen.STD_CANVAS_WIDTH;
@ -272,7 +298,7 @@ public class Screen {
pScene.hideFullScreenMouseCursor = (configFlags & CONFIG_FLAG_NO_AUTOHIDE_MOUSE) == 0; pScene.hideFullScreenMouseCursor = (configFlags & CONFIG_FLAG_NO_AUTOHIDE_MOUSE) == 0;
root.getChildren().add(pScene.subScene); root.getChildren().add(pScene.subScene);
boolean[] suppressKeyTyped = { false }; boolean[] suppressKeyTyped = {false};
switch (configScreen) { switch (configScreen) {
case CONFIG_SCREEN_WINDOWED: case CONFIG_SCREEN_WINDOWED:
@ -300,8 +326,9 @@ public class Screen {
if (!event.isConsumed() && pScene.keyPressedHandler != null && pScene.keyPressedHandler.test(event)) { if (!event.isConsumed() && pScene.keyPressedHandler != null && pScene.keyPressedHandler.test(event)) {
event.consume(); event.consume();
} }
if (pScene.logKeyEvents) if (pScene.logKeyEvents) {
System.err.println(event); System.err.println(event);
}
suppressKeyTyped[0] = event.isConsumed(); suppressKeyTyped[0] = event.isConsumed();
}); });
scene.setOnKeyTyped((KeyEvent event) -> { scene.setOnKeyTyped((KeyEvent event) -> {
@ -312,16 +339,18 @@ public class Screen {
if (!event.isConsumed() && pScene.keyTypedHandler != null && pScene.keyTypedHandler.test(event)) { if (!event.isConsumed() && pScene.keyTypedHandler != null && pScene.keyTypedHandler.test(event)) {
event.consume(); event.consume();
} }
if (pScene.logKeyEvents) if (pScene.logKeyEvents) {
System.err.println(event); System.err.println(event);
}
}); });
scene.setOnKeyReleased((KeyEvent event) -> { scene.setOnKeyReleased((KeyEvent event) -> {
suppressKeyTyped[0] = false; suppressKeyTyped[0] = false;
if (!event.isConsumed() && pScene.keyReleasedHandler != null && pScene.keyReleasedHandler.test(event)) { if (!event.isConsumed() && pScene.keyReleasedHandler != null && pScene.keyReleasedHandler.test(event)) {
event.consume(); event.consume();
} }
if (pScene.logKeyEvents) if (pScene.logKeyEvents) {
System.err.println(event); System.err.println(event);
}
}); });
return pScene; return pScene;
} }
@ -431,22 +460,30 @@ public class Screen {
return Math.floor(getRawHeight() / resolutionScale); return Math.floor(getRawHeight() / resolutionScale);
} }
/** @return the keyOverride */ /**
* @return the keyOverride
*/
public Predicate<KeyEvent> getKeyOverride() { public Predicate<KeyEvent> getKeyOverride() {
return keyOverride; return keyOverride;
} }
/** @return the keyHandler */ /**
* @return the keyHandler
*/
public Predicate<KeyEvent> getKeyPressedHandler() { public Predicate<KeyEvent> getKeyPressedHandler() {
return keyPressedHandler; return keyPressedHandler;
} }
/** @return the keyReleasedHandler */ /**
* @return the keyReleasedHandler
*/
public Predicate<KeyEvent> getKeyReleasedHandler() { public Predicate<KeyEvent> getKeyReleasedHandler() {
return keyReleasedHandler; return keyReleasedHandler;
} }
/** @return the keyTypedHandler */ /**
* @return the keyTypedHandler
*/
public Predicate<KeyEvent> getKeyTypedHandler() { public Predicate<KeyEvent> getKeyTypedHandler() {
return keyTypedHandler; return keyTypedHandler;
} }
@ -469,11 +506,12 @@ public class Screen {
public boolean isFullScreen() { public boolean isFullScreen() {
Window window = subScene.getScene().getWindow(); Window window = subScene.getScene().getWindow();
if (window instanceof Stage) if (window instanceof Stage) {
return ((Stage) window).isFullScreen(); return ((Stage) window).isFullScreen();
else } else {
return false; return false;
} }
}
public boolean minimalKeyHandler(KeyEvent event) { public boolean minimalKeyHandler(KeyEvent event) {
KeyCode code = event.getCode(); KeyCode code = event.getCode();
@ -528,18 +566,19 @@ public class Screen {
double vBorder = window.getHeight() - scene.getHeight(); double vBorder = window.getHeight() - scene.getHeight();
double myWidth = getRawWidth() * currentScale; double myWidth = getRawWidth() * currentScale;
double myHeight = getRawHeight() * currentScale; double myHeight = getRawHeight() * currentScale;
if (debug) if (debug) {
System.out.printf( System.out.printf(
"Resizing before: screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n", // "Resizing before: screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n", //
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(), javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(), javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(), subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight()); window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
}
// this.setWidth(myWidth); // this.setWidth(myWidth);
// this.setHeight(myHeight); // this.setHeight(myHeight);
window.setWidth(myWidth + hBorder); window.setWidth(myWidth + hBorder);
window.setHeight(myHeight + vBorder); window.setHeight(myHeight + vBorder);
if (debug) if (debug) {
System.out.printf( System.out.printf(
"Resizing after : screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n", "Resizing after : screen: %1.0fx%1.0f, screen: %1.0fx%1.0f, scene: %1.0fx%1.0f, window: %1.0fx%1.0f,%n border: %1.0fx%1.0f, new window size: %1.0fx%1.0f, canvas size: %1.0fx%1.0f%n",
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(), javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
@ -547,19 +586,22 @@ public class Screen {
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(), subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight()); window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
} }
}
if (debug) if (debug) {
System.out.printf("Rescaling: subscene %1.2fx%1.2f, scale %1.2f, aspect %.4f (%d), canvas %1.0fx%1.0f%n", System.out.printf("Rescaling: subscene %1.2fx%1.2f, scale %1.2f, aspect %.4f (%d), canvas %1.0fx%1.0f%n",
subScene.getWidth(), subScene.getHeight(), currentScale, aspects.get(aspect), aspect, getRawWidth(), subScene.getWidth(), subScene.getHeight(), currentScale, aspects.get(aspect), aspect, getRawWidth(),
getRawHeight()); getRawHeight());
}
for (Node n : root.getChildren()) { for (Node n : root.getChildren()) {
n.relocate(Math.floor(subScene.getWidth() / 2), n.relocate(Math.floor(subScene.getWidth() / 2),
Math.floor(subScene.getHeight() / 2 + (rawCanvasHeight - getRawHeight()) * currentScale / 2)); Math.floor(subScene.getHeight() / 2 + (rawCanvasHeight - getRawHeight()) * currentScale / 2));
n.setTranslateX(-Math.floor(rawCanvasWidth / 2)); n.setTranslateX(-Math.floor(rawCanvasWidth / 2));
n.setTranslateY(-Math.floor(rawCanvasHeight / 2)); n.setTranslateY(-Math.floor(rawCanvasHeight / 2));
if (debug) if (debug) {
System.out.printf(" * layout %1.2fx%1.2f, translate %1.2fx%1.2f%n", n.getLayoutX(), n.getLayoutY(), System.out.printf(" * layout %1.2fx%1.2f, translate %1.2fx%1.2f%n", n.getLayoutX(), n.getLayoutY(),
n.getTranslateX(), n.getTranslateY()); n.getTranslateX(), n.getTranslateY());
}
n.setScaleX(currentScale); n.setScaleX(currentScale);
n.setScaleY(currentScale); n.setScaleY(currentScale);
} }
@ -610,32 +652,28 @@ public class Screen {
} }
/** /**
* @param keyOverride * @param keyOverride the keyOverride to set
* the keyOverride to set
*/ */
public void setKeyOverride(Predicate<KeyEvent> keyOverride) { public void setKeyOverride(Predicate<KeyEvent> keyOverride) {
this.keyOverride = keyOverride; this.keyOverride = keyOverride;
} }
/** /**
* @param keyHandler * @param keyHandler the keyHandler to set
* the keyHandler to set
*/ */
public void setKeyPressedHandler(Predicate<KeyEvent> keyHandler) { public void setKeyPressedHandler(Predicate<KeyEvent> keyHandler) {
this.keyPressedHandler = keyHandler; this.keyPressedHandler = keyHandler;
} }
/** /**
* @param keyReleasedHandler * @param keyReleasedHandler the keyReleasedHandler to set
* the keyReleasedHandler to set
*/ */
public void setKeyReleasedHandler(Predicate<KeyEvent> keyReleasedHandler) { public void setKeyReleasedHandler(Predicate<KeyEvent> keyReleasedHandler) {
this.keyReleasedHandler = keyReleasedHandler; this.keyReleasedHandler = keyReleasedHandler;
} }
/** /**
* @param keyTypedHandler * @param keyTypedHandler the keyTypedHandler to set
* the keyTypedHandler to set
*/ */
public void setKeyTypedHandler(Predicate<KeyEvent> keyTypedHandler) { public void setKeyTypedHandler(Predicate<KeyEvent> keyTypedHandler) {
this.keyTypedHandler = keyTypedHandler; this.keyTypedHandler = keyTypedHandler;
@ -651,8 +689,9 @@ public class Screen {
public void zoomCycle() { public void zoomCycle() {
scaling++; scaling++;
if (scaling > maxScale) if (scaling > maxScale) {
scaling = ((int) scaling) % maxScale; scaling = ((int) scaling) % maxScale;
}
recomputeLayout(true); recomputeLayout(true);
} }

View File

@ -2,14 +2,13 @@ package inf101.v18.gfx.gfxmode;
/** /**
* @author anya * @author anya
*
*/ */
public class Direction { public class Direction {
/** /**
* Construct direction from an angle * Construct direction from an angle
* *
* @param degrees * @param degrees Angle in degrees, where 0 is (1,0)
* Angle in degrees, where 0 is (1,0)
*/ */
public static Direction fromDegrees(double degrees) { public static Direction fromDegrees(double degrees) {
return new Direction(degrees); return new Direction(degrees);
@ -18,10 +17,8 @@ public class Direction {
/** /**
* Construct direction from a vector * Construct direction from a vector
* *
* @param x * @param x X direction
* X direction * @param y Y direction
* @param y
* Y direction
*/ */
public static Direction fromVector(double x, double y) { public static Direction fromVector(double x, double y) {
return new Direction(x, y); return new Direction(x, y);
@ -33,11 +30,10 @@ public class Direction {
/** /**
* Create a new direction. * Create a new direction.
* * <p>
* The direction vector will be normalised to a vector of length 1. * The direction vector will be normalised to a vector of length 1.
* *
* @param degrees * @param degrees Angle of direction in degrees
* Angle of direction in degrees
*/ */
public Direction(double degrees) { public Direction(double degrees) {
double radians = Math.toRadians(degrees); double radians = Math.toRadians(degrees);
@ -48,13 +44,11 @@ public class Direction {
/** /**
* Create a new direction. * Create a new direction.
* * <p>
* The direction vector will be normalised to a vector of length 1. * The direction vector will be normalised to a vector of length 1.
* *
* @param xDir * @param xDir X-component of direction vector
* X-component of direction vector * @param yDir Y-component of direction vector
* @param yDir
* Y-component of direction vector
*/ */
public Direction(double xDir, double yDir) { public Direction(double xDir, double yDir) {
this.xDir = xDir; this.xDir = xDir;
@ -74,7 +68,7 @@ public class Direction {
/** /**
* @return X-component of direction vector * @return X-component of direction vector
* * <p>
* Same as the Math.cos(toRadians()) * Same as the Math.cos(toRadians())
*/ */
public double getX() { public double getX() {
@ -83,7 +77,7 @@ public class Direction {
/** /**
* @return Y-component of direction vector * @return Y-component of direction vector
* * <p>
* Same as the Math.sin(toRadians()) * Same as the Math.sin(toRadians())
*/ */
public double getY() { public double getY() {
@ -179,8 +173,7 @@ public class Direction {
/** /**
* Absolute turn * Absolute turn
* *
* @param degrees * @param degrees Angle in degrees, where 0 is (1,0)
* Angle in degrees, where 0 is (1,0)
*/ */
public Direction turnTo(double degrees) { public Direction turnTo(double degrees) {
return new Direction(degrees); return new Direction(degrees);
@ -189,10 +182,8 @@ public class Direction {
/** /**
* Turn slightly towards a directions * Turn slightly towards a directions
* *
* @param dir * @param dir A direction
* A direction * @param percent How much to turn (100.0 is the same as turnTo())
* @param percent
* How much to turn (100.0 is the same as turnTo())
*/ */
public Direction turnTowards(Direction dir, double percent) { public Direction turnTowards(Direction dir, double percent) {
return new Direction(xDir * (1.00 - percent / 100.0) + dir.xDir * (percent / 100.0), return new Direction(xDir * (1.00 - percent / 100.0) + dir.xDir * (percent / 100.0),
@ -202,4 +193,5 @@ public class Direction {
// turnTo(thisAngle*(1.00 - percent/100.0) + // turnTo(thisAngle*(1.00 - percent/100.0) +
// otherAngle*(percent/100.0)); // otherAngle*(percent/100.0));
} }
} }

View File

@ -1,5 +1,15 @@
package inf101.v18.gfx.gfxmode; package inf101.v18.gfx.gfxmode;
public enum Gravity { public enum Gravity {
NORTH, NORTHWEST, WEST, SOUTHWEST, SOUTH, SOUTHEAST, EAST, NORTHEAST, CENTER
NORTH,
NORTHWEST,
WEST,
SOUTHWEST,
SOUTH,
SOUTHEAST,
EAST,
NORTHEAST,
CENTER
} }

View File

@ -9,7 +9,8 @@ public interface IShape {
/** /**
* Add another point to the line path * Add another point to the line path
* *
* @param xy * @param x
* @param y
* @return * @return
*/ */
IShape addPoint(double x, double y); IShape addPoint(double x, double y);
@ -28,8 +29,7 @@ public interface IShape {
* <p> * <p>
* For use with {@link #arc()} * For use with {@link #arc()}
* *
* @param a * @param a The angle, in degrees
* The angle, in degrees
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -55,8 +55,7 @@ public interface IShape {
/** /**
* Set the (x,y)-coordinates of the next draw command * Set the (x,y)-coordinates of the next draw command
* *
* @param p * @param p Coordinates
* Coordinates
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -107,12 +106,11 @@ public interface IShape {
/** /**
* Set gravity for the subsequent draw commands * Set gravity for the subsequent draw commands
* * <p>
* Gravity determines the point on the shape that will be used for positioning * Gravity determines the point on the shape that will be used for positioning
* and rotation. * and rotation.
* *
* @param g * @param g The gravity
* The gravity
* @return * @return
*/ */
IShape gravity(Gravity g); IShape gravity(Gravity g);
@ -120,8 +118,7 @@ public interface IShape {
/** /**
* Set the height of the next draw command * Set the height of the next draw command
* *
* @param h * @param h The height
* The height
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -133,8 +130,7 @@ public interface IShape {
* <p> * <p>
* For use with {@link #line()} and {@link #arc()} * For use with {@link #line()} and {@link #arc()}
* *
* @param l * @param l The length
* The length
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -180,8 +176,7 @@ public interface IShape {
* <p> * <p>
* Shapes will be rotate around the {@link #gravity(Gravity)} point. * Shapes will be rotate around the {@link #gravity(Gravity)} point.
* *
* @param angle * @param angle Rotation in degrees
* Rotation in degrees
* @return * @return
*/ */
IShape rotation(double angle); IShape rotation(double angle);
@ -209,8 +204,7 @@ public interface IShape {
/** /**
* Set the width of the next draw command * Set the width of the next draw command
* *
* @param w * @param w The width
* The width
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -219,8 +213,7 @@ public interface IShape {
/** /**
* Set the x-coordinate of the next draw command * Set the x-coordinate of the next draw command
* *
* @param x * @param x Coordinate
* Coordinate
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */
@ -229,8 +222,7 @@ public interface IShape {
/** /**
* Set the y-coordinate of the next draw command * Set the y-coordinate of the next draw command
* *
* @param y * @param y Coordinate
* Coordinate
* @return <code>this</code>, for adding more drawing parameters or issuing the * @return <code>this</code>, for adding more drawing parameters or issuing the
* draw command * draw command
*/ */

View File

@ -3,7 +3,7 @@ package inf101.v18.gfx.gfxmode;
public interface ITurtle extends IPainter { public interface ITurtle extends IPainter {
/** /**
* This method is used to convert the turtle to an other type, determined by the * This method is used to convert the turtle to another type, determined by the
* class object given as an argument. * class object given as an argument.
* <p> * <p>
* This can be used to access extra functionality not provided by this * This can be used to access extra functionality not provided by this
@ -31,10 +31,8 @@ public interface ITurtle extends IPainter {
* <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90° * <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90°
* pointing up). * pointing up).
* *
* @param to * @param to Position to move to
* Position to move to * @param startControl Distance to the starting control point.
* @param startControl
* Distance to the starting control point.
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl); ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
@ -44,8 +42,7 @@ public interface ITurtle extends IPainter {
/** /**
* Move forward the given distance while drawing a line * Move forward the given distance while drawing a line
* *
* @param dist * @param dist Distance to move
* Distance to move
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle draw(double dist); ITurtle draw(double dist);
@ -53,10 +50,8 @@ public interface ITurtle extends IPainter {
/** /**
* Move to the given position while drawing a line * Move to the given position while drawing a line
* *
* @param x * @param x X-position to move to
* X-position to move to * @param y Y-position to move to
* @param y
* Y-position to move to
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle drawTo(double x, double y); ITurtle drawTo(double x, double y);
@ -64,8 +59,7 @@ public interface ITurtle extends IPainter {
/** /**
* Move to the given position while drawing a line * Move to the given position while drawing a line
* *
* @param to * @param to Position to move to
* Position to move to
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle drawTo(Point to); ITurtle drawTo(Point to);
@ -90,8 +84,7 @@ public interface ITurtle extends IPainter {
/** /**
* Move a distance without drawing. * Move a distance without drawing.
* *
* @param dist * @param dist Distance to move
* Distance to move
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle jump(double dist); ITurtle jump(double dist);
@ -99,10 +92,8 @@ public interface ITurtle extends IPainter {
/** /**
* Move a position without drawing. * Move a position without drawing.
* *
* @param x * @param x X position to move to
* X position to move to * @param y Y position to move to
* @param y
* Y position to move to
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle jumpTo(double x, double y); ITurtle jumpTo(double x, double y);
@ -110,8 +101,7 @@ public interface ITurtle extends IPainter {
/** /**
* Move a position without drawing. * Move a position without drawing.
* *
* @param to * @param to X,Y position to move to
* X,Y position to move to
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle jumpTo(Point to); ITurtle jumpTo(Point to);
@ -122,8 +112,7 @@ public interface ITurtle extends IPainter {
* <p> * <p>
* This method does not change the turtle position. * This method does not change the turtle position.
* *
* @param to * @param to Other end-point of the line
* Other end-point of the line
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle line(Point to); ITurtle line(Point to);
@ -131,8 +120,7 @@ public interface ITurtle extends IPainter {
/** /**
* Set the size of the turtle's pen * Set the size of the turtle's pen
* *
* @param pixels * @param pixels Line width, in pixels
* Line width, in pixels
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
* @requires pixels >= 0 * @requires pixels >= 0
*/ */
@ -224,8 +212,7 @@ public interface ITurtle extends IPainter {
* <p> * <p>
* 0° is due right, 90° is up. * 0° is due right, 90° is up.
* *
* @param degrees * @param degrees Bearing, in degrees
* Bearing, in degrees
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle turnTo(double degrees); ITurtle turnTo(double degrees);
@ -239,10 +226,8 @@ public interface ITurtle extends IPainter {
* <p> * <p>
* 0° is due right, 90° is up. * 0° is due right, 90° is up.
* *
* @param degrees * @param degrees Bearing, in degrees
* Bearing, in degrees * @param percent How far to turn, in degrees.
* @param percent
* How far to turn, in degrees.
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle turnTowards(double degrees, double percent); ITurtle turnTowards(double degrees, double percent);
@ -252,8 +237,7 @@ public interface ITurtle extends IPainter {
* <p> * <p>
* The new position will be equal to getPos().move(relPos). * The new position will be equal to getPos().move(relPos).
* *
* @param relPos * @param relPos A position, interpreted relative to current position
* A position, interpreted relative to current position
* @return {@code this}, for sending more draw commands * @return {@code this}, for sending more draw commands
*/ */
ITurtle jump(Point relPos); ITurtle jump(Point relPos);

View File

@ -46,10 +46,8 @@ public class Point {
/** /**
* Relative move * Relative move
* *
* @param dir * @param dir Direction
* Direction * @param distance Distance to move
* @param distance
* Distance to move
*/ */
public Point move(Direction dir, double distance) { public Point move(Direction dir, double distance) {
return new Point(x + dir.getX() * distance, y - dir.getY() * distance); return new Point(x + dir.getX() * distance, y - dir.getY() * distance);
@ -78,10 +76,8 @@ public class Point {
/** /**
* Change position * Change position
* *
* @param newX * @param newX the new X coordinate
* the new X coordinate * @param newY the new Y coordinate
* @param newY
* the new Y coordinate
* @return A new point at newX, newY * @return A new point at newX, newY
*/ */
public Point moveTo(double newX, double newY) { public Point moveTo(double newX, double newY) {
@ -100,17 +96,18 @@ public class Point {
* @return A new Point, (getX()*factor, getY()*factor) * @return A new Point, (getX()*factor, getY()*factor)
*/ */
public Point scale(double factor) { public Point scale(double factor) {
return new Point(x*factor, y*factor); return new Point(x * factor, y * factor);
} }
/** /**
* Find difference between points. * Find difference between points.
* <p> * <p>
* The returned value will be such that <code>this.move(deltaTo(point)).equals(point)</code>. * The returned value will be such that <code>this.move(deltaTo(point)).equals(point)</code>.
*
* @param point Another point * @param point Another point
* @return A new Point, (point.getX()-getX(), point.getY()-getY()) * @return A new Point, (point.getX()-getX(), point.getY()-getY())
*/ */
public Point deltaTo(Point point) { public Point deltaTo(Point point) {
return new Point(point.x-x, point.y-y); return new Point(point.x - x, point.y - y);
} }
} }

View File

@ -1,33 +1,27 @@
package inf101.v18.gfx.gfxmode; package inf101.v18.gfx.gfxmode;
import java.util.List;
import javafx.scene.canvas.GraphicsContext; import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Paint; import javafx.scene.paint.Paint;
import javafx.scene.shape.Shape; import javafx.scene.shape.Shape;
import java.util.List;
public class ShapePainter implements IShape { public class ShapePainter implements IShape {
private abstract static class DrawCommand { private abstract static class DrawCommand {
protected double calcX(Gravity g, double w) { protected double calcX(Gravity g, double w) {
switch (g) { switch (g) {
default: default:
case CENTER: case CENTER:
return w / 2; case SOUTH:
case EAST:
return w;
case NORTH: case NORTH:
return w / 2; return w / 2;
case EAST:
case NORTHEAST: case NORTHEAST:
return w;
case NORTHWEST:
return 0;
case SOUTH:
return w / 2;
case SOUTHEAST: case SOUTHEAST:
return w; return w;
case SOUTHWEST: case NORTHWEST:
return 0;
case WEST: case WEST:
case SOUTHWEST:
return 0; return 0;
} }
} }
@ -36,23 +30,17 @@ public class ShapePainter implements IShape {
switch (g) { switch (g) {
default: default:
case CENTER: case CENTER:
return h / 2;
case EAST: case EAST:
return h / 2;
case NORTH:
return 0;
case NORTHEAST:
return 0;
case NORTHWEST:
return 0;
case SOUTH:
return h;
case SOUTHEAST:
return h;
case SOUTHWEST:
return h;
case WEST: case WEST:
return h / 2; return h / 2;
case NORTH:
case NORTHWEST:
case NORTHEAST:
return 0;
case SOUTH:
case SOUTHWEST:
case SOUTHEAST:
return h;
} }
} }
@ -60,8 +48,9 @@ public class ShapePainter implements IShape {
ctx.save(); ctx.save();
ctx.setFill(p.fill); ctx.setFill(p.fill);
ctx.translate(p.x, p.y); ctx.translate(p.x, p.y);
if (p.rot != 0) if (p.rot != 0) {
ctx.rotate(-p.rot); ctx.rotate(-p.rot);
}
fillIt(ctx, p); fillIt(ctx, p);
ctx.restore(); ctx.restore();
} }
@ -75,11 +64,13 @@ public class ShapePainter implements IShape {
public void stroke(GraphicsContext ctx, ShapePainter p) { public void stroke(GraphicsContext ctx, ShapePainter p) {
ctx.save(); ctx.save();
ctx.setStroke(p.stroke); ctx.setStroke(p.stroke);
if (p.strokeWidth != 0) if (p.strokeWidth != 0) {
ctx.setLineWidth(p.strokeWidth); ctx.setLineWidth(p.strokeWidth);
}
ctx.translate(p.x, p.y); ctx.translate(p.x, p.y);
if (p.rot != 0) if (p.rot != 0) {
ctx.rotate(-p.rot); ctx.rotate(-p.rot);
}
strokeIt(ctx, p); strokeIt(ctx, p);
ctx.restore(); ctx.restore();
} }
@ -106,8 +97,8 @@ public class ShapePainter implements IShape {
public void fillIt(GraphicsContext ctx, ShapePainter p) { public void fillIt(GraphicsContext ctx, ShapePainter p) {
if (p.lineSegments != null) { if (p.lineSegments != null) {
int nPoints = (p.lineSegments.size() / 2) + 1; int nPoints = (p.lineSegments.size() / 2) + 1;
double xs[] = new double[nPoints]; double[] xs = new double[nPoints];
double ys[] = new double[nPoints]; double[] ys = new double[nPoints];
xs[0] = -calcX(p.gravity, p.w); xs[0] = -calcX(p.gravity, p.w);
ys[0] = -calcY(p.gravity, p.h); ys[0] = -calcY(p.gravity, p.h);
for (int i = 0; i < p.lineSegments.size(); i++) { for (int i = 0; i < p.lineSegments.size(); i++) {
@ -126,21 +117,22 @@ public class ShapePainter implements IShape {
ctx.strokeLine(x, y, x + p.w, y + p.h); ctx.strokeLine(x, y, x + p.w, y + p.h);
} else { } else {
int nPoints = (p.lineSegments.size() / 2) + 1; int nPoints = (p.lineSegments.size() / 2) + 1;
double xs[] = new double[nPoints]; double[] xs = new double[nPoints];
double ys[] = new double[nPoints]; double[] ys = new double[nPoints];
xs[0] = -calcX(p.gravity, p.w); xs[0] = -calcX(p.gravity, p.w);
ys[0] = -calcY(p.gravity, p.h); ys[0] = -calcY(p.gravity, p.h);
for (int i = 0; i < p.lineSegments.size(); i++) { for (int i = 0; i < p.lineSegments.size(); i++) {
xs[i] = p.lineSegments.get(i * 2) - p.x; xs[i] = p.lineSegments.get(i * 2) - p.x;
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y; ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
} }
if (p.closed) if (p.closed) {
ctx.strokePolygon(xs, ys, nPoints); ctx.strokePolygon(xs, ys, nPoints);
else } else {
ctx.strokePolyline(xs, ys, nPoints); ctx.strokePolyline(xs, ys, nPoints);
} }
} }
} }
}
private static class DrawRectangle extends DrawCommand { private static class DrawRectangle extends DrawCommand {
@ -155,8 +147,13 @@ public class ShapePainter implements IShape {
} }
} }
private double x = 0, y = 0, w = 0, h = 0, rot = 0, strokeWidth = 0; private double x = 0;
private List<Double> lineSegments = null; private double y = 0;
private double w = 0;
private double h = 0;
private double rot = 0;
private final double strokeWidth = 0;
private final List<Double> lineSegments = null;
private Paint fill = null; private Paint fill = null;
private Paint stroke = null; private Paint stroke = null;
@ -223,12 +220,14 @@ public class ShapePainter implements IShape {
@Override @Override
public void draw(GraphicsContext context) { public void draw(GraphicsContext context) {
if (cmd != null && context != null) { if (cmd != null && context != null) {
if (fill != null) if (fill != null) {
cmd.fill(context, this); cmd.fill(context, this);
if (stroke != null) }
if (stroke != null) {
cmd.stroke(context, this); cmd.stroke(context, this);
} }
} }
}
@Override @Override
public IShape ellipse() { public IShape ellipse() {
@ -238,8 +237,9 @@ public class ShapePainter implements IShape {
@Override @Override
public ShapePainter fill() { public ShapePainter fill() {
if (cmd != null && context != null) if (cmd != null && context != null) {
cmd.fill(context, this); cmd.fill(context, this);
}
return this; return this;
} }
@ -288,8 +288,9 @@ public class ShapePainter implements IShape {
@Override @Override
public ShapePainter stroke() { public ShapePainter stroke() {
if (cmd != null && context != null) if (cmd != null && context != null) {
cmd.stroke(context, this); cmd.stroke(context, this);
}
return this; return this;
} }

View File

@ -1,8 +1,5 @@
package inf101.v18.gfx.gfxmode; package inf101.v18.gfx.gfxmode;
import java.util.ArrayList;
import java.util.List;
import inf101.v18.gfx.IPaintLayer; import inf101.v18.gfx.IPaintLayer;
import inf101.v18.gfx.Screen; import inf101.v18.gfx.Screen;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
@ -12,6 +9,9 @@ import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin; import javafx.scene.shape.StrokeLineJoin;
import java.util.ArrayList;
import java.util.List;
public class TurtlePainter implements IPaintLayer, ITurtle { public class TurtlePainter implements IPaintLayer, ITurtle {
static class TurtleState { static class TurtleState {
@ -55,8 +55,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
} }
public TurtlePainter(Screen screen, Canvas canvas) { public TurtlePainter(Screen screen, Canvas canvas) {
if (screen == null || canvas == null) if (screen == null || canvas == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}
this.screen = screen; this.screen = screen;
this.canvas = canvas; this.canvas = canvas;
this.context = canvas.getGraphicsContext2D(); this.context = canvas.getGraphicsContext2D();
@ -72,19 +73,22 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T as(Class<T> clazz) { public <T> T as(Class<T> clazz) {
if (clazz == GraphicsContext.class) if (clazz == GraphicsContext.class) {
return (T) context; return (T) context;
if (clazz == getClass()) }
if (clazz == getClass()) {
return (T) this; return (T) this;
else } else {
return null; return null;
} }
}
@Override @Override
public void clear() { public void clear() {
if (context != null) if (context != null) {
context.clearRect(0, 0, getWidth(), getHeight()); context.clearRect(0, 0, getWidth(), getHeight());
} }
}
@Override @Override
public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) { public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) {
@ -181,8 +185,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
public ITurtle jump(double dist) { public ITurtle jump(double dist) {
state.inDir = state.dir; state.inDir = state.dir;
state.pos = state.pos.move(state.dir, dist); state.pos = state.pos.move(state.dir, dist);
if (path && context != null) if (path && context != null) {
context.moveTo(state.pos.getX(), state.pos.getY()); context.moveTo(state.pos.getX(), state.pos.getY());
}
return this; return this;
} }
@ -190,8 +195,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
public ITurtle jump(Point relPos) { public ITurtle jump(Point relPos) {
// TODO: state.inDir = state.dir; // TODO: state.inDir = state.dir;
state.pos = state.pos.move(relPos); state.pos = state.pos.move(relPos);
if (path && context != null) if (path && context != null) {
context.moveTo(state.pos.getX(), state.pos.getY()); context.moveTo(state.pos.getX(), state.pos.getY());
}
return this; return this;
} }
@ -212,15 +218,17 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
@Override @Override
public void layerToBack() { public void layerToBack() {
if (screen != null) if (screen != null) {
screen.moveToBack(this); screen.moveToBack(this);
} }
}
@Override @Override
public void layerToFront() { public void layerToFront() {
if (screen != null) if (screen != null) {
screen.moveToFront(this); screen.moveToFront(this);
} }
}
@Override @Override
public ITurtle line(Point to) { public ITurtle line(Point to) {
@ -256,8 +264,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
@Override @Override
public ITurtle setPenSize(double pixels) { public ITurtle setPenSize(double pixels) {
if (pixels < 0) if (pixels < 0) {
throw new IllegalArgumentException("Negative: " + pixels); throw new IllegalArgumentException("Negative: " + pixels);
}
state.penSize = pixels; state.penSize = pixels;
return this; return this;
} }
@ -286,8 +295,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
@Override @Override
public ITurtle turnLeft(double degrees) { public ITurtle turnLeft(double degrees) {
if (degrees < 0) if (degrees < 0) {
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())"); throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
}
state.dir = state.dir.turn(degrees); state.dir = state.dir.turn(degrees);
return this; return this;
} }
@ -299,8 +309,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
@Override @Override
public ITurtle turnRight(double degrees) { public ITurtle turnRight(double degrees) {
if (degrees < 0) if (degrees < 0) {
throw new IllegalArgumentException("Negative: " + degrees + " (use turn())"); throw new IllegalArgumentException("Negative: " + degrees + " (use turn())");
}
state.dir = state.dir.turn(-degrees); state.dir = state.dir.turn(-degrees);
return this; return this;
} }
@ -325,8 +336,9 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
} }
public ITurtle beginPath() { public ITurtle beginPath() {
if (path) if (path) {
throw new IllegalStateException("beginPath() after beginPath()"); throw new IllegalStateException("beginPath() after beginPath()");
}
path = true; path = true;
if (context != null) { if (context != null) {
context.setStroke(state.ink); context.setStroke(state.ink);
@ -337,25 +349,30 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
} }
public ITurtle closePath() { public ITurtle closePath() {
if (!path) if (!path) {
throw new IllegalStateException("closePath() without beginPath()"); throw new IllegalStateException("closePath() without beginPath()");
if (context != null) }
if (context != null) {
context.closePath(); context.closePath();
}
return this; return this;
} }
public ITurtle endPath() { public ITurtle endPath() {
if (!path) if (!path) {
throw new IllegalStateException("endPath() without beginPath()"); throw new IllegalStateException("endPath() without beginPath()");
}
path = false; path = false;
if (context != null) if (context != null) {
context.stroke(); context.stroke();
}
return this; return this;
} }
public ITurtle fillPath() { public ITurtle fillPath() {
if (!path) if (!path) {
throw new IllegalStateException("fillPath() without beginPath()"); throw new IllegalStateException("fillPath() without beginPath()");
}
path = false; path = false;
if (context != null) { if (context != null) {
context.save(); context.save();

View File

@ -22,10 +22,10 @@ public class BlocksAndBoxes {
} }
} }
public static final String[] unicodeBlocks = { " ", "", "", "", "", "", "", "", "", "", "", "", "", "", public static final String[] unicodeBlocks = {" ", "", "", "", "", "", "", "", "", "", "", "", "", "",
"", "", "" }; "", "", ""};
public static final int[] unicodeBlocks_NumPixels = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2 }; public static final int[] unicodeBlocks_NumPixels = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 2};
public static final String unicodeBlocksString = String.join("", unicodeBlocks); public static final String unicodeBlocksString = String.join("", unicodeBlocks);
public static final String BLOCK_EMPTY = " "; public static final String BLOCK_EMPTY = " ";
public static final String BLOCK_BOTTOM_RIGHT = ""; public static final String BLOCK_BOTTOM_RIGHT = "";
@ -50,10 +50,11 @@ public class BlocksAndBoxes {
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s); int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
if (i >= 0) { if (i >= 0) {
for (int bit : order) { for (int bit : order) {
if ((i & bit) == 0) if ((i & bit) == 0) {
return unicodeBlocks[i | bit]; return unicodeBlocks[i | bit];
} }
} }
}
return s; return s;
} }
@ -88,12 +89,10 @@ public class BlocksAndBoxes {
* "grey" block <code>""</code>, and <code>"*"</code> corresponds to the * "grey" block <code>""</code>, and <code>"*"</code> corresponds to the
* "black" block <code>""</code>. * "black" block <code>""</code>.
* *
* @param s * @param s A four character string, indicating which block character to
* A four character string, indicating which block character to
* select. * select.
* @return A Unicode block character * @return A Unicode block character
* @throws IllegalArgumentException * @throws IllegalArgumentException if the string isn't of the expected form
* if the string isn't of the expected form
*/ */
public static String blockChar(String s) { public static String blockChar(String s) {
switch (s.replaceAll("\n", s)) { switch (s.replaceAll("\n", s)) {
@ -167,34 +166,39 @@ public class BlocksAndBoxes {
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) { public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
int i1 = unicodeBlocksString.indexOf(b1); int i1 = unicodeBlocksString.indexOf(b1);
if (i1 < 0) if (i1 < 0) {
throw new IllegalArgumentException("Not a block character: " + b1); throw new IllegalArgumentException("Not a block character: " + b1);
}
int i2 = unicodeBlocksString.indexOf(b2); int i2 = unicodeBlocksString.indexOf(b2);
if (i2 < 0) if (i2 < 0) {
throw new IllegalArgumentException("Not a block character: " + b1); throw new IllegalArgumentException("Not a block character: " + b1);
if (i1 == 16 || i2 == 16) }
if (i1 == 16 || i2 == 16) {
return b2; return b2;
else } else {
return unicodeBlocks[op.apply(i1, i2)]; return unicodeBlocks[op.apply(i1, i2)];
} }
}
public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) { public static String blockComposeOrOverwrite(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
int i1 = unicodeBlocksString.indexOf(b1); int i1 = unicodeBlocksString.indexOf(b1);
int i2 = unicodeBlocksString.indexOf(b2); int i2 = unicodeBlocksString.indexOf(b2);
if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16) if (i1 < 0 || i2 < 0 || i1 == 16 || i2 == 16) {
return b2; return b2;
else } else {
return unicodeBlocks[op.apply(i1, i2)]; return unicodeBlocks[op.apply(i1, i2)];
} }
}
public static String blockRemoveOne(String s, PixelOrder order) { public static String blockRemoveOne(String s, PixelOrder order) {
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s); int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
if (i >= 0) { if (i >= 0) {
for (int bit : order) { for (int bit : order) {
if ((i & bit) != 0) if ((i & bit) != 0) {
return unicodeBlocks[i & ~bit]; return unicodeBlocks[i & ~bit];
} }
} }
}
return s; return s;
} }

View File

@ -1,5 +1,7 @@
package inf101.v18.gfx.textmode; package inf101.v18.gfx.textmode;
import javafx.scene.paint.Color;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
@ -11,8 +13,6 @@ import java.util.function.Consumer;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import javafx.scene.paint.Color;
public class ControlSequences { public class ControlSequences {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
@ -75,27 +75,31 @@ public class ControlSequences {
String argStr = matcher.groupCount() > 0 ? matcher.group(1) : ""; String argStr = matcher.groupCount() > 0 ? matcher.group(1) : "";
String[] args = argStr.split(";"); String[] args = argStr.split(";");
if (handler0 != null) { if (handler0 != null) {
if(DEBUG) if (DEBUG) {
System.out.println("Handling " + getDescription() + "."); System.out.println("Handling " + getDescription() + ".");
}
handler0.accept(printer); handler0.accept(printer);
} else if (handler1 != null) { } else if (handler1 != null) {
int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg; int arg = args.length > 0 && !args[0].equals("") ? Integer.valueOf(args[0]) : defaultArg;
if(DEBUG) if (DEBUG) {
System.out.println("Handling " + getDescription() + ": " + arg); System.out.println("Handling " + getDescription() + ": " + arg);
}
handler1.accept(printer, arg); handler1.accept(printer, arg);
} else if (handlerN != null) { } else if (handlerN != null) {
List<Integer> argList = new ArrayList<>(); List<Integer> argList = new ArrayList<>();
for (String s : args) { for (String s : args) {
if (s.equals("")) if (s.equals("")) {
argList.add(defaultArg); argList.add(defaultArg);
else } else {
argList.add(Integer.valueOf(s)); argList.add(Integer.valueOf(s));
} }
}
while (argList.size() < numArgs) { while (argList.size() < numArgs) {
argList.add(defaultArg); argList.add(defaultArg);
} }
if(DEBUG) if (DEBUG) {
System.out.println("Handling " + getDescription() + ": " + argList); System.out.println("Handling " + getDescription() + ": " + argList);
}
handlerN.accept(printer, argList); handlerN.accept(printer, argList);
} }
return true; return true;
@ -141,10 +145,11 @@ System.out.println("Handling " + getDescription() + ": " + argList);
}); });
public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display", public static final CsiPattern ED = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)J", 0, "erase in display",
(Printer p, Integer i) -> { (Printer p, Integer i) -> {
if (i == 2 || i == 3) if (i == 2 || i == 3) {
p.clear(); p.clear();
else } else {
System.err.println("Unimplemented: ED"); System.err.println("Unimplemented: ED");
}
}); });
public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line", public static final CsiPattern EK = CsiPattern.compile1("\u001b\\\u005b([0-9;]*)K", 0, "erase in line",
(Printer p, Integer i) -> { (Printer p, Integer i) -> {
@ -184,21 +189,21 @@ System.out.println("Handling " + getDescription() + ": " + argList);
Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), // Color.rgb(0, 0, 0), Color.rgb(0, 0, H), Color.rgb(0, H, 0), Color.rgb(0, H, H), //
Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), // Color.rgb(H, 0, 0), Color.rgb(H, 0, H), Color.rgb(H, L, 0), Color.rgb(H, H, H), //
Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), // Color.rgb(L, L, L), Color.rgb(L, L, F), Color.rgb(L, F, L), Color.rgb(L, F, F), //
Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F), }; Color.rgb(F, L, L), Color.rgb(F, L, F), Color.rgb(F, F, L), Color.rgb(F, F, F),};
public static final Color[] PALETTE_VGA = { // public static final Color[] PALETTE_VGA = { //
Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), // Color.rgb(0, 0, 0), Color.rgb(H, 0, 0), Color.rgb(0, H, 0), Color.rgb(H, H, 0), //
Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), // Color.rgb(0, 0, H), Color.rgb(H, 0, H), Color.rgb(0, H, H), Color.rgb(H, H, H), //
Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), // Color.rgb(L, L, L), Color.rgb(F, L, L), Color.rgb(L, F, L), Color.rgb(F, F, L), //
Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F), }; Color.rgb(L, L, F), Color.rgb(F, L, F), Color.rgb(L, F, F), Color.rgb(F, F, F),};
public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1, public static final CsiPattern SGR = CsiPattern.compileN("\u001b\\\u005b([0-9;]*)m", 0, -1,
"select graphics rendition", (Printer p, List<Integer> l) -> { "select graphics rendition", (Printer p, List<Integer> l) -> {
if (l.size() == 0) { if (l.size() == 0) {
l.add(0); l.add(0);
} }
int[] attrs = { 0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC, int[] attrs = {0, TextFont.ATTR_BRIGHT, TextFont.ATTR_FAINT, TextFont.ATTR_ITALIC,
TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0, TextFont.ATTR_UNDERLINE, TextFont.ATTR_BLINK, TextFont.ATTR_BLINK, TextFont.ATTR_INVERSE, 0,
TextFont.ATTR_LINE_THROUGH }; TextFont.ATTR_LINE_THROUGH};
Iterator<Integer> it = l.iterator(); Iterator<Integer> it = l.iterator();
while (it.hasNext()) { while (it.hasNext()) {
@ -240,10 +245,11 @@ System.out.println("Handling " + getDescription() + ": " + argList);
// System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC")); // System.out.println("Applying CSI: " + csi.replaceAll("\u001b", "ESC"));
if (csiPattern != null) { if (csiPattern != null) {
if (csiPattern.match(printer, csi)) if (csiPattern.match(printer, csi)) {
return true; return true;
else } else {
System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC")); System.err.println("Handler failed for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
}
} else { } else {
System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC")); System.err.println("No handler for escape sequence: " + csi.replaceAll("\u001b", "ESC"));
@ -257,12 +263,13 @@ System.out.println("Handling " + getDescription() + ": " + argList);
i = it.next(); i = it.next();
if (i == 5) { if (i == 5) {
i = it.next(); i = it.next();
if (i < 16) if (i < 16) {
return PALETTE_VGA[i]; return PALETTE_VGA[i];
else if (i < 232) } else if (i < 232) {
return Color.rgb(i / 36, (i / 6) % 6, i % 6); return Color.rgb(i / 36, (i / 6) % 6, i % 6);
else } else {
return Color.gray((i - 232) / 23.0); return Color.gray((i - 232) / 23.0);
}
} else if (i == 2) { } else if (i == 2) {
int r = it.next(); int r = it.next();
int g = it.next(); int g = it.next();

View File

@ -1,12 +1,12 @@
package inf101.v18.gfx.textmode; package inf101.v18.gfx.textmode;
import javafx.scene.paint.Color;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import javafx.scene.paint.Color;
public class DemoPages { public class DemoPages {
public static void printAnsiArt(Printer printer) { public static void printAnsiArt(Printer printer) {
printer.moveTo(1, 1); printer.moveTo(1, 1);
@ -30,27 +30,35 @@ public class DemoPages {
printer.setVideoAttrs(0); printer.setVideoAttrs(0);
int topLine = 8; int topLine = 8;
for (int x = 0; x < 16; x += 1) { for (int x = 0; x < 16; x += 1) {
if ((x & 1) > 0) if ((x & 1) > 0) {
printer.plot(4 * x + 1, 1 + (topLine - 1) * 2); printer.plot(4 * x + 1, 1 + (topLine - 1) * 2);
if ((x & 2) > 0) }
if ((x & 2) > 0) {
printer.plot(4 * x, 1 + (topLine - 1) * 2); printer.plot(4 * x, 1 + (topLine - 1) * 2);
if ((x & 4) > 0) }
if ((x & 4) > 0) {
printer.plot(4 * x + 1, (topLine - 1) * 2); printer.plot(4 * x + 1, (topLine - 1) * 2);
if ((x & 8) > 0) }
if ((x & 8) > 0) {
printer.plot(4 * x, (topLine - 1) * 2); printer.plot(4 * x, (topLine - 1) * 2);
}
printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]); printer.printAt(1 + 2 * x, topLine + 2, BlocksAndBoxes.unicodeBlocks[x]);
printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]); printer.printAt(1 + 2 * x, topLine + 4, BlocksAndBoxes.unicodeBlocks[15]);
printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]); printer.printAt(1 + 2 * x, topLine + 6, BlocksAndBoxes.unicodeBlocks[~x & +0xf]);
printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x)); printer.printAt(1 + 2 * x, topLine + 7, String.format("%X", x));
if ((x & 1) > 0) if ((x & 1) > 0) {
printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2); printer.unplot(4 * x + 1, 1 + (4 + topLine - 1) * 2);
if ((x & 2) > 0) }
if ((x & 2) > 0) {
printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2); printer.unplot(4 * x, 1 + (4 + topLine - 1) * 2);
if ((x & 4) > 0) }
if ((x & 4) > 0) {
printer.unplot(4 * x + 1, (4 + topLine - 1) * 2); printer.unplot(4 * x + 1, (4 + topLine - 1) * 2);
if ((x & 8) > 0) }
if ((x & 8) > 0) {
printer.unplot(4 * x, (4 + topLine - 1) * 2); printer.unplot(4 * x, (4 + topLine - 1) * 2);
} }
}
printer.printAt(1, 1, printer.printAt(1, 1,
"Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:"); "Plotting with Unicode Block Elements\n(ZX81-like Graphics)\n\nThe plot/print and unplot/inverse\nlines should be equal:");
printer.printAt(33, topLine, "plot"); printer.printAt(33, topLine, "plot");

View File

@ -1,10 +1,5 @@
package inf101.v18.gfx.textmode; package inf101.v18.gfx.textmode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
import inf101.v18.gfx.IPaintLayer; import inf101.v18.gfx.IPaintLayer;
import inf101.v18.gfx.Screen; import inf101.v18.gfx.Screen;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
@ -13,8 +8,15 @@ import javafx.scene.effect.BlendMode;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
import javafx.scene.paint.Paint; import javafx.scene.paint.Paint;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiFunction;
public class Printer implements IPaintLayer { public class Printer implements IPaintLayer {
private static class Char { private static class Char {
public int mode; public int mode;
public String s; public String s;
public Color fill; public Color fill;
@ -28,6 +30,7 @@ public class Printer implements IPaintLayer {
this.bg = bg; this.bg = bg;
this.mode = mode; this.mode = mode;
} }
} }
public static final TextFont FONT_MONOSPACED = new TextFont("Monospaced", 27.00, TextMode.CHAR_BOX_SIZE, 3.4000, public static final TextFont FONT_MONOSPACED = new TextFont("Monospaced", 27.00, TextMode.CHAR_BOX_SIZE, 3.4000,
@ -148,14 +151,15 @@ public class Printer implements IPaintLayer {
moveTo(leftMargin, topMargin); moveTo(leftMargin, topMargin);
for (Char[] line : lineBuffer) for (Char[] line : lineBuffer)
Arrays.fill(line, null); Arrays.fill(line, null);
if(textPage != null) { if (textPage != null) {
GraphicsContext context = textPage.getGraphicsContext2D(); GraphicsContext context = textPage.getGraphicsContext2D();
if (background != null && background != Color.TRANSPARENT) { if (background != null && background != Color.TRANSPARENT) {
context.setFill(background); context.setFill(background);
context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight()); context.fillRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
} else } else {
context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight()); context.clearRect(0.0, 0.0, textPage.getWidth(), textPage.getHeight());
} }
}
break; break;
case "\b": case "\b":
moveHoriz(-1); moveHoriz(-1);
@ -248,18 +252,21 @@ public class Printer implements IPaintLayer {
Arrays.fill(lineBuffer.get(y - 1), null); Arrays.fill(lineBuffer.get(y - 1), null);
dirty(1, y); dirty(1, y);
dirty(getLineWidth(), y); dirty(getLineWidth(), y);
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
} }
}
public void clearRegion(int x, int y, int width, int height) { public void clearRegion(int x, int y, int width, int height) {
if (x > getLineWidth() || y > getPageHeight()) if (x > getLineWidth() || y > getPageHeight()) {
return; return;
}
int x2 = Math.min(x + width - 1, getLineWidth()); int x2 = Math.min(x + width - 1, getLineWidth());
int y2 = Math.min(y + height - 1, getPageHeight()); int y2 = Math.min(y + height - 1, getPageHeight());
if (x2 < 1 || y2 < 1) if (x2 < 1 || y2 < 1) {
return; return;
}
int x1 = Math.max(1, x); int x1 = Math.max(1, x);
int y1 = Math.max(1, y); int y1 = Math.max(1, y);
// Char fillWith = new Char("*", Color.BLACK, Color.GREEN, Color.TRANSPARENT, // Char fillWith = new Char("*", Color.BLACK, Color.GREEN, Color.TRANSPARENT,
@ -269,9 +276,10 @@ public class Printer implements IPaintLayer {
} }
dirty(x1, y1); dirty(x1, y1);
dirty(x2, y2); dirty(x2, y2);
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
}
private int constrainX(int x) { private int constrainX(int x) {
return x; // Math.min(LINE_WIDTH_HIRES, Math.max(1, x)); return x; // Math.min(LINE_WIDTH_HIRES, Math.max(1, x));
@ -307,13 +315,15 @@ public class Printer implements IPaintLayer {
public void cycleMode(boolean adjustDisplayAspect) { public void cycleMode(boolean adjustDisplayAspect) {
textMode = textMode.nextMode(); textMode = textMode.nextMode();
if (adjustDisplayAspect && screen != null) if (adjustDisplayAspect && screen != null) {
screen.setAspect(textMode.getAspect()); screen.setAspect(textMode.getAspect());
}
dirty(1, 1); dirty(1, 1);
dirty(getLineWidth(), getPageHeight()); dirty(getLineWidth(), getPageHeight());
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
}
private void drawChar(int x, int y, Char c) { private void drawChar(int x, int y, Char c) {
if (useBuffer) { if (useBuffer) {
@ -339,10 +349,11 @@ public class Printer implements IPaintLayer {
context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.1)); context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.1));
for (int x = 0; x < getLineWidth(); x++) { for (int x = 0; x < getLineWidth(); x++) {
for (int y = 0; y < getPageHeight(); y++) { for (int y = 0; y < getPageHeight(); y++) {
if ((x + y) % 2 == 0) if ((x + y) % 2 == 0) {
context.fillRect(x * w, y * h, w, h); context.fillRect(x * w, y * h, w, h);
} }
} }
}
context.restore(); context.restore();
} }
} }
@ -353,10 +364,11 @@ public class Printer implements IPaintLayer {
c = lineBuffer.get(y - 1)[x - 1]; c = lineBuffer.get(y - 1)[x - 1];
} }
Color bg = Color.TRANSPARENT; Color bg = Color.TRANSPARENT;
if (c != null && c.bg instanceof Color) if (c != null && c.bg instanceof Color) {
bg = (Color) c.bg; bg = (Color) c.bg;
else if (background instanceof Color) } else if (background instanceof Color) {
bg = (Color) background; bg = (Color) background;
}
return bg; return bg;
} }
@ -369,11 +381,12 @@ public class Printer implements IPaintLayer {
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) { if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1]; c = lineBuffer.get(y - 1)[x - 1];
} }
if (c != null) if (c != null) {
return c.s; return c.s;
else } else {
return " "; return " ";
} }
}
public double getCharHeight() { public double getCharHeight() {
return textMode.getCharHeight(); return textMode.getCharHeight();
@ -388,11 +401,12 @@ public class Printer implements IPaintLayer {
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) { if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
c = lineBuffer.get(y - 1)[x - 1]; c = lineBuffer.get(y - 1)[x - 1];
} }
if (c != null) if (c != null) {
return c.fill; return c.fill;
else } else {
return fill; return fill;
} }
}
public TextFont getFont() { public TextFont getFont() {
return font; return font;
@ -539,14 +553,16 @@ public class Printer implements IPaintLayer {
* "ms/char", "mode", "indir", "inv", "fake"); for (int m = -1; m < 8; m++) { * "ms/char", "mode", "indir", "inv", "fake"); for (int m = -1; m < 8; m++) {
* long t0 = System.currentTimeMillis(); int n = 0; * long t0 = System.currentTimeMillis(); int n = 0;
*/ */
if(textPage == null) if (textPage == null) {
return; return;
}
GraphicsContext context = textPage.getGraphicsContext2D(); GraphicsContext context = textPage.getGraphicsContext2D();
double px0 = (x0 - 1) * getCharWidth(), py0 = (y0 - 1) * getCharHeight(); double px0 = (x0 - 1) * getCharWidth(), py0 = (y0 - 1) * getCharHeight();
double px1 = x1 * getCharWidth(), py1 = y1 * getCharHeight(); double px1 = x1 * getCharWidth(), py1 = y1 * getCharHeight();
if (DEBUG_REDRAW) if (DEBUG_REDRAW) {
System.out.printf("redrawTextPage(): Area to clear: (%2f,%2f)(%2f,%2f)%n", px0, py0, px1, py1); System.out.printf("redrawTextPage(): Area to clear: (%2f,%2f)(%2f,%2f)%n", px0, py0, px1, py1);
}
if (background != null && background != Color.TRANSPARENT) { if (background != null && background != Color.TRANSPARENT) {
context.setFill(background); context.setFill(background);
context.fillRect(px0, py0, px1 - px0, py1 - py0); context.fillRect(px0, py0, px1 - px0, py1 - py0);
@ -621,9 +637,10 @@ public class Printer implements IPaintLayer {
lineBuffer.add(0, remove); lineBuffer.add(0, remove);
dirty(1, 1); dirty(1, 1);
dirty(getLineWidth(), getPageHeight()); dirty(getLineWidth(), getPageHeight());
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
}
public void scrollUp() { public void scrollUp() {
Char[] remove = lineBuffer.remove(0); Char[] remove = lineBuffer.remove(0);
@ -631,9 +648,10 @@ public class Printer implements IPaintLayer {
lineBuffer.add(remove); lineBuffer.add(remove);
dirty(1, 1); dirty(1, 1);
dirty(getLineWidth(), getPageHeight()); dirty(getLineWidth(), getPageHeight());
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
}
public boolean setAutoScroll(boolean autoScroll) { public boolean setAutoScroll(boolean autoScroll) {
boolean old = autoscroll; boolean old = autoscroll;
@ -657,11 +675,12 @@ public class Printer implements IPaintLayer {
} }
public void setBold(boolean enabled) { public void setBold(boolean enabled) {
if (enabled) if (enabled) {
videoAttrs |= TextFont.ATTR_BRIGHT; videoAttrs |= TextFont.ATTR_BRIGHT;
else } else {
videoAttrs &= ~TextFont.ATTR_BRIGHT; videoAttrs &= ~TextFont.ATTR_BRIGHT;
} }
}
public Char setChar(int x, int y, String s) { public Char setChar(int x, int y, String s) {
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) { if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
@ -697,32 +716,34 @@ public class Printer implements IPaintLayer {
} }
public void setItalics(boolean enabled) { public void setItalics(boolean enabled) {
if (enabled) if (enabled) {
videoAttrs |= TextFont.ATTR_ITALIC; videoAttrs |= TextFont.ATTR_ITALIC;
else } else {
videoAttrs &= ~TextFont.ATTR_ITALIC; videoAttrs &= ~TextFont.ATTR_ITALIC;
} }
}
/** /**
*
*/ */
public void setLeftMargin() { public void setLeftMargin() {
this.leftMargin = x; this.leftMargin = x;
} }
/** /**
* @param leftMargin * @param leftMargin the leftMargin to set
* the leftMargin to set
*/ */
public void setLeftMargin(int leftMargin) { public void setLeftMargin(int leftMargin) {
this.leftMargin = constrainX(leftMargin); this.leftMargin = constrainX(leftMargin);
} }
public void setReverseVideo(boolean enabled) { public void setReverseVideo(boolean enabled) {
if (enabled) if (enabled) {
videoAttrs |= TextFont.ATTR_INVERSE; videoAttrs |= TextFont.ATTR_INVERSE;
else } else {
videoAttrs &= ~TextFont.ATTR_INVERSE; videoAttrs &= ~TextFont.ATTR_INVERSE;
} }
}
public void setStroke(Color stroke) { public void setStroke(Color stroke) {
this.stroke = stroke != null ? stroke : DEFAULT_STROKE; this.stroke = stroke != null ? stroke : DEFAULT_STROKE;
@ -733,24 +754,26 @@ public class Printer implements IPaintLayer {
} }
public void setTextMode(TextMode mode, boolean adjustDisplayAspect) { public void setTextMode(TextMode mode, boolean adjustDisplayAspect) {
if (mode == null) if (mode == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}
textMode = mode; textMode = mode;
if (adjustDisplayAspect && screen != null) if (adjustDisplayAspect && screen != null) {
screen.setAspect(textMode.getAspect()); screen.setAspect(textMode.getAspect());
}
dirty(1, 1); dirty(1, 1);
dirty(getLineWidth(), getPageHeight()); dirty(getLineWidth(), getPageHeight());
if (!useBuffer) if (!useBuffer) {
redrawDirty(); redrawDirty();
} }
}
public void setTopMargin() { public void setTopMargin() {
this.topMargin = y; this.topMargin = y;
} }
/** /**
* @param topMargin * @param topMargin the topMargin to set
* the topMargin to set
*/ */
public void setTopMargin(int topMargin) { public void setTopMargin(int topMargin) {
this.topMargin = constrainY(topMargin); this.topMargin = constrainY(topMargin);
@ -805,9 +828,10 @@ public class Printer implements IPaintLayer {
*/ */
public void redrawDirty() { public void redrawDirty() {
if (isDirty()) { if (isDirty()) {
if (DEBUG_REDRAW) if (DEBUG_REDRAW) {
System.out.printf("redrawDirty(): Dirty region is (%d,%d)(%d,%d)%n", dirtyX0, dirtyY0, dirtyX1, System.out.printf("redrawDirty(): Dirty region is (%d,%d)(%d,%d)%n", dirtyX0, dirtyY0, dirtyX1,
dirtyY1); dirtyY1);
}
redrawTextPage(dirtyX0, dirtyY0, dirtyX1, dirtyY1); redrawTextPage(dirtyX0, dirtyY0, dirtyX1, dirtyY1);
clean(); clean();
} }
@ -827,8 +851,7 @@ public class Printer implements IPaintLayer {
* With buffered printing, nothing is actually drawn until * With buffered printing, nothing is actually drawn until
* {@link #redrawDirty()} or {@link #redrawTextPage()} is called. * {@link #redrawDirty()} or {@link #redrawTextPage()} is called.
* *
* @param buffering * @param buffering Whether to use buffering
* Whether to use buffering
*/ */
public void setBuffering(boolean buffering) { public void setBuffering(boolean buffering) {
useBuffer = buffering; useBuffer = buffering;
@ -841,4 +864,5 @@ public class Printer implements IPaintLayer {
public boolean getBuffering() { public boolean getBuffering() {
return useBuffer; return useBuffer;
} }
} }

View File

@ -1,11 +1,5 @@
package inf101.v18.gfx.textmode; package inf101.v18.gfx.textmode;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javafx.geometry.Point2D; import javafx.geometry.Point2D;
import javafx.scene.SnapshotParameters; import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas; import javafx.scene.canvas.Canvas;
@ -18,28 +12,36 @@ import javafx.scene.paint.Color;
import javafx.scene.paint.Paint; import javafx.scene.paint.Paint;
import javafx.scene.shape.StrokeLineCap; import javafx.scene.shape.StrokeLineCap;
import javafx.scene.text.Font; import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.transform.Affine; import javafx.scene.transform.Affine;
import javafx.scene.transform.Transform; import javafx.scene.transform.Transform;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
/** /**
* TextFont for grid-based text / character graphics * TextFont for grid-based text / character graphics
*
* <p> * <p>
* TextFonts are used for drawing text and character graphics. Each character is * TextFonts are used for drawing text and character graphics. Each character is
* assumed to be uniform in size, fitting within a square with sides of length * assumed to be uniform in size, fitting within a square with sides of length
* {@link #getSquareSize()}. The given font file should contain a monospaced * {@link #getSquareSize()}. The given font file should contain a monospaced
* font of a type suitable for JavaFX, such as OpenType or TrueType. * font of a type suitable for JavaFX, such as OpenType or TrueType.
* </p>
* *
* <p> * <p>
* Additional horizontal and vertical positioning and scaling can be used to * Additional horizontal and vertical positioning and scaling can be used to
* make the font fit with the square-shaped concept. * make the font fit with the square-shaped concept.
* </p>
* *
* <p> * <p>
* See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics * See {@link #setGraphicsContext(GraphicsContext)} for setting up the graphics
* context for writing with the font, or * context for writing with the font, or
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* * </p>
*/ */
public class TextFont { public class TextFont {
public static final int ATTR_INVERSE = 0x01; public static final int ATTR_INVERSE = 0x01;
@ -56,7 +58,7 @@ public class TextFont {
public static final int ATTR_FAINT = 0x400; // NOT IMPLEMENTED public static final int ATTR_FAINT = 0x400; // NOT IMPLEMENTED
public static final int ATTR_BRIGHT = 0x800; public static final int ATTR_BRIGHT = 0x800;
private static final String[] searchPath = { "", "../", "fonts/" }; private static final String[] searchPath = {"", "../", "fonts/"};
private static final Map<String, String> loadedFonts = new HashMap<>(); private static final Map<String, String> loadedFonts = new HashMap<>();
private static final double thin = 2.0, thick = 4.0; private static final double thin = 2.0, thick = 4.0;
private static final String[] boxDrawingShapes = { // lines private static final String[] boxDrawingShapes = { // lines
@ -88,7 +90,7 @@ public class TextFont {
// short lines // short lines
"-...", "..-.", ".-..", "...-", "*...", "..*.", ".*..", "...*", "-...", "..-.", ".-..", "...-", "*...", "..*.", ".*..", "...*",
// thin/thick lines // thin/thick lines
"-*..", "..-*", "*-..", "..*-" }; "-*..", "..-*", "*-..", "..*-"};
public static void drawMasked(GraphicsContext ctx, Image src, Image mask, double x, double y, boolean invert, public static void drawMasked(GraphicsContext ctx, Image src, Image mask, double x, double y, boolean invert,
boolean blend) { boolean blend) {
@ -106,10 +108,12 @@ public class TextFont {
for (int py = 0; py < h; py++) { for (int py = 0; py < h; py++) {
int a = pixelMask.getArgb(px, py) >>> 24; int a = pixelMask.getArgb(px, py) >>> 24;
int rgb = pixelSrc.getArgb(px, py); int rgb = pixelSrc.getArgb(px, py);
if (invert) if (invert) {
a = ~a & 0xff; a = ~a & 0xff;
if (blend) }
if (blend) {
a = ((rgb >>> 24) * a) >>> 8; a = ((rgb >>> 24) * a) >>> 8;
}
pixelWriter.setArgb(px + dx, py + dy, (a << 24) | rgb); pixelWriter.setArgb(px + dx, py + dy, (a << 24) | rgb);
} }
@ -139,28 +143,26 @@ public class TextFont {
/** /**
* Load a font. * Load a font.
* * <p>
* Will first try to use the provided string as a file name, and load the font * Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from * from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed * file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails. * directly to JavaFX for loading. A fallback font is used if this fails.
* * <p>
* When looking for files, his method will search for the file in a search path * When looking for files, his method will search for the file in a search path
* starting with the folder containing the {@link TextFont} class file (using * starting with the folder containing the {@link TextFont} class file (using
* Java's standard {@link Class#getResourceAsStream(String)} system). The search * Java's standard {@link Class#getResourceAsStream(String)} system). The search
* path also includes ".." (parent directory) and "../fonts". * path also includes ".." (parent directory) and "../fonts".
* * <p>
* The loaded font will be cached, so that additional calls with the same file * The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again. * name will not cause the file to be loaded again.
* * <p>
* If the font cannot be loaded, a default font will be substituted. You may * If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or * check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}. * {@link Font#getFamily()}.
* *
* @param name * @param name Font name, or relative path to the file
* Font name, or relative path to the file * @param size Desired point size of the font
* @param size
* Desired point size of the font
* @return A JavaFX font * @return A JavaFX font
*/ */
public static final Font findFont(String name, double size) { public static final Font findFont(String name, double size) {
@ -169,38 +171,37 @@ public class TextFont {
/** /**
* Load a font. * Load a font.
* * <p>
* Will first try to use the provided string as a file name, and load the font * Will first try to use the provided string as a file name, and load the font
* from a font file (should be in a format supported by JavaFX). If loading from * from a font file (should be in a format supported by JavaFX). If loading from
* file fails, we assume we have been given the name of a font which is passed * file fails, we assume we have been given the name of a font which is passed
* directly to JavaFX for loading. A fallback font is used if this fails. * directly to JavaFX for loading. A fallback font is used if this fails.
* * <p>
* When looking for files, this method will search for the file in a search path * When looking for files, this method will search for the file in a search path
* starting with the folder containing the provided Java class (using Java's * starting with the folder containing the provided Java class (using Java's
* standard {@link Class#getResourceAsStream(String)} system). The search path * standard {@link Class#getResourceAsStream(String)} system). The search path
* also includes ".." (parent directory) and "../fonts". * also includes ".." (parent directory) and "../fonts".
* * <p>
* The loaded font will be cached, so that additional calls with the same file * The loaded font will be cached, so that additional calls with the same file
* name will not cause the file to be loaded again. * name will not cause the file to be loaded again.
* * <p>
* If the font cannot be loaded, a default font will be substituted. You may * If the font cannot be loaded, a default font will be substituted. You may
* check which font you got using {@link Font#getName()} or * check which font you got using {@link Font#getName()} or
* {@link Font#getFamily()}. * {@link Font#getFamily()}.
* *
* @param name * @param name Name of a font, or relative path to the font file
* Name of a font, or relative path to the font file * @param size Desired point size of the font
* @param size * @param clazz A class for finding files relative to
* Desired point size of the font
* @param clazz
* A class for finding files relative to
* @return A JavaFX font * @return A JavaFX font
*/ */
public static final Font findFont(String name, double size, Class<?> clazz) { public static final Font findFont(String name, double size, Class<?> clazz) {
if (name == null || size < 0) if (name == null || size < 0) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}
if (loadedFonts.containsKey(name)) if (loadedFonts.containsKey(name)) {
return Font.font(loadedFonts.get(name), size); return Font.font(loadedFonts.get(name), size);
}
for (String path : searchPath) { for (String path : searchPath) {
ClassLoader classloader = Thread.currentThread().getContextClassLoader(); ClassLoader classloader = Thread.currentThread().getContextClassLoader();
@ -219,10 +220,12 @@ public class TextFont {
} }
Font font = Font.font(name, size); Font font = Font.font(name, size);
if (font == null) if (font == null) {
font = Font.font(size); font = Font.font(size);
if (font == null) }
if (font == null) {
throw new RuntimeException("Even the default font seems to be unavailable this shouldn't happen! :("); throw new RuntimeException("Even the default font seems to be unavailable this shouldn't happen! :(");
}
if (font.getName().equals(Font.getDefault().getName())) { if (font.getName().equals(Font.getDefault().getName())) {
System.err.println("TextFont: Default font '" + font.getName() + "' substituted for '" + name + "'"); System.err.println("TextFont: Default font '" + font.getName() + "' substituted for '" + name + "'");
} }
@ -239,12 +242,14 @@ public class TextFont {
* The JavaFX font * The JavaFX font
*/ */
private Font font; private Font font;
/** Font size */ /**
* Font size
*/
private final double size; private final double size;
/** /**
* Horizontal positioning of letters. * Horizontal positioning of letters.
* * <p>
* Each letter should be approximately centered within its available * Each letter should be approximately centered within its available
* square-shaped space. * square-shaped space.
*/ */
@ -252,7 +257,7 @@ public class TextFont {
/** /**
* Vertical positioning of letters. * Vertical positioning of letters.
* * <p>
* Each letter should be positioned on the baseline so that ascenders and * Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space. * descenders fall within its available square-shaped space.
*/ */
@ -260,7 +265,7 @@ public class TextFont {
/** /**
* Horizontal scaling factor (1.0 means no scaling) * Horizontal scaling factor (1.0 means no scaling)
* * <p>
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit * Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape. * a square shape.
*/ */
@ -303,20 +308,13 @@ public class TextFont {
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* *
* * @param font Name of the font file. Will search for the file in the same folder
* @param font
* Name of the font file. Will search for the file in the same folder
* as the TextFont class, as well as ".." and "../fonts". * as the TextFont class, as well as ".." and "../fonts".
* @param squareSize * @param squareSize The width and height of a square defining the bounds of letters
* The width and height of a square defining the bounds of letters * @param xTranslate Horizontal positioning of letters
* @param xTranslate * @param yTranslate Vertical positioning of letters
* Horizontal positioning of letters * @param xScale Horizontal scaling factor
* @param yTranslate * @param yScale Vertical scaling factor
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/ */
public TextFont(Font font, double squareSize, double xTranslate, double yTranslate, double xScale, double yScale) { public TextFont(Font font, double squareSize, double xTranslate, double yTranslate, double xScale, double yScale) {
super(); super();
@ -350,19 +348,12 @@ public class TextFont {
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* *
* * @param size Point size of the font.
* @param size * @param squareSize The width and height of a square defining the bounds of letters
* Point size of the font. * @param xTranslate Horizontal positioning of letters
* @param squareSize * @param yTranslate Vertical positioning of letters
* The width and height of a square defining the bounds of letters * @param xScale Horizontal scaling factor
* @param xTranslate * @param yScale Vertical scaling factor
* Horizontal positioning of letters
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
*/ */
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate, public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
double xScale, double yScale) { double xScale, double yScale) {
@ -397,21 +388,13 @@ public class TextFont {
* {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly * {@link #setGraphicsContext(GraphicsContext, double)} for extra on-the-fly
* horizontal scaling, e.g. to make half-width letters ("hires" mode). * horizontal scaling, e.g. to make half-width letters ("hires" mode).
* *
* * @param size Point size of the font.
* @param size * @param squareSize The width and height of a square defining the bounds of letters
* Point size of the font. * @param xTranslate Horizontal positioning of letters
* @param squareSize * @param yTranslate Vertical positioning of letters
* The width and height of a square defining the bounds of letters * @param xScale Horizontal scaling factor
* @param xTranslate * @param yScale Vertical scaling factor
* Horizontal positioning of letters * @param deferLoading True if the font file shouldn't be loaded before the font is
* @param yTranslate
* Vertical positioning of letters
* @param xScale
* Horizontal scaling factor
* @param yScale
* Vertical scaling factor
* @param deferLoading
* True if the font file shouldn't be loaded before the font is
* actually used * actually used
*/ */
public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate, public TextFont(String fileName, double size, double squareSize, double xTranslate, double yTranslate,
@ -452,19 +435,17 @@ public class TextFont {
/** /**
* Draw the given text at position (0,0). * Draw the given text at position (0,0).
* * <p>
* The <code>ctx</code> should normally be translated to the appropriate text * The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method. * position before calling this method.
* * <p>
* Text will be clipped so each character fits its expected square-shaped area, * Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing. * and the area will be cleared to transparency before drwaing.
* * <p>
* The graphics context's current path will be overwritten. * The graphics context's current path will be overwritten.
* *
* @param ctx * @param ctx a grapics context
* a grapics context * @param text string to be printed
* @param text
* string to be printed
*/ */
public void drawText(GraphicsContext ctx, String text) { public void drawText(GraphicsContext ctx, String text) {
textAt(ctx, 0.0, 0.0, text, 1.0, true, true, ctx.getFill(), null, 0, null); textAt(ctx, 0.0, 0.0, text, 1.0, true, true, ctx.getFill(), null, 0, null);
@ -472,21 +453,18 @@ public class TextFont {
/** /**
* Draw the given text at position (0,0), with horizontal scaling. * Draw the given text at position (0,0), with horizontal scaling.
* * <p>
* The <code>ctx</code> should normally be translated to the appropriate text * The <code>ctx</code> should normally be translated to the appropriate text
* position before calling this method. * position before calling this method.
* * <p>
* Text will be clipped so each character fits its expected square-shaped area, * Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing. * and the area will be cleared to transparency before drwaing.
* * <p>
* The graphics context's current path will be overwritten. * The graphics context's current path will be overwritten.
* *
* @param ctx * @param ctx a grapics context
* a grapics context * @param text string to be printed
* @param text * @param xScaleFactor a horizontal scaling factor
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/ */
public void drawText(GraphicsContext ctx, String text, double xScaleFactor) { public void drawText(GraphicsContext ctx, String text, double xScaleFactor) {
textAt(ctx, 0.0, 0.0, text, xScaleFactor, true, false, ctx.getFill(), null, 0, null); textAt(ctx, 0.0, 0.0, text, xScaleFactor, true, false, ctx.getFill(), null, 0, null);
@ -494,20 +472,16 @@ public class TextFont {
/** /**
* Draw the given text at position (x,y). * Draw the given text at position (x,y).
* * <p>
* Text will be clipped so each character fits its expected square-shaped area, * Text will be clipped so each character fits its expected square-shaped area,
* and the area will be cleared to transparency before drwaing. * and the area will be cleared to transparency before drwaing.
* * <p>
* The graphics context's current path will be overwritten. * The graphics context's current path will be overwritten.
* *
* @param ctx * @param ctx a grapics context
* a grapics context * @param x X-position of the lower left corner of the text
* @param x * @param y Y-position of the lower left corner of the text
* X-position of the lower left corner of the text * @param text string to be printed
* @param y
* Y-position of the lower left corner of the text
* @param text
* string to be printed
*/ */
public void drawTextAt(GraphicsContext ctx, double x, double y, String text) { public void drawTextAt(GraphicsContext ctx, double x, double y, String text) {
textAt(ctx, x, y, text, 1.0, true, false, ctx.getFill(), null, 0, null); textAt(ctx, x, y, text, 1.0, true, false, ctx.getFill(), null, 0, null);
@ -515,21 +489,16 @@ public class TextFont {
/** /**
* Draw the given text at position (x, y), with horizontal scaling. * Draw the given text at position (x, y), with horizontal scaling.
* * <p>
* The area will be cleared to transparency before drawing. * The area will be cleared to transparency before drawing.
* * <p>
* The graphics context's current path will be overwritten. * The graphics context's current path will be overwritten.
* *
* @param ctx * @param ctx a graphics context
* a graphics context * @param x X-position of the lower left corner of the text
* @param x * @param y Y-position of the lower left corner of the text
* X-position of the lower left corner of the text * @param text string to be printed
* @param y * @param xScaleFactor a horizontal scaling factor
* Y-position of the lower left corner of the text
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/ */
public void drawTextAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode, public void drawTextAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode,
Paint bg) { Paint bg) {
@ -538,21 +507,16 @@ public class TextFont {
/** /**
* Draw the given text at position (x, y), with horizontal scaling. * Draw the given text at position (x, y), with horizontal scaling.
* * <p>
* The area will not be cleared to transparency before drawing. * The area will not be cleared to transparency before drawing.
* * <p>
* The graphics context's current path will be overwritten. * The graphics context's current path will be overwritten.
* *
* @param ctx * @param ctx a graphics context
* a graphics context * @param x X-position of the lower left corner of the text
* @param x * @param y Y-position of the lower left corner of the text
* X-position of the lower left corner of the text * @param text string to be printed
* @param y * @param xScaleFactor a horizontal scaling factor
* Y-position of the lower left corner of the text
* @param text
* string to be printed
* @param xScaleFactor
* a horizontal scaling factor
*/ */
public void drawTextNoClearAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode, public void drawTextNoClearAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, int mode,
Paint bg) { Paint bg) {
@ -675,8 +639,9 @@ public class TextFont {
* @return the font * @return the font
*/ */
public Font getFont() { public Font getFont() {
if (font == null) if (font == null) {
font = findFont(fileName, size); font = findFont(fileName, size);
}
return font; return font;
} }
@ -707,7 +672,7 @@ public class TextFont {
/** /**
* Horizontal scaling factor (1.0 means no scaling) * Horizontal scaling factor (1.0 means no scaling)
* * <p>
* Most fonts are relatively tall and narrow, and need horizontal scaling to fit * Most fonts are relatively tall and narrow, and need horizontal scaling to fit
* a square shape. * a square shape.
* *
@ -719,7 +684,7 @@ public class TextFont {
/** /**
* Horizontal positioning of letters. * Horizontal positioning of letters.
* * <p>
* Each letter should be approximately centered within its available * Each letter should be approximately centered within its available
* square-shaped space. * square-shaped space.
* *
@ -740,7 +705,7 @@ public class TextFont {
/** /**
* /** Vertical positioning of letters. * /** Vertical positioning of letters.
* * <p>
* Each letter should be positioned on the baseline so that ascenders and * Each letter should be positioned on the baseline so that ascenders and
* descenders fall within its available square-shaped space. * descenders fall within its available square-shaped space.
* *
@ -784,17 +749,16 @@ public class TextFont {
/** /**
* Set up a graphics context for drawing with this font. * Set up a graphics context for drawing with this font.
* * <p>
* Caller should call {@link GraphicsContext#save()} first, and then * Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the * {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling). * transformation matrix (i.e., translation, scaling).
* * <p>
* The GraphicsContext should be translated to the coordinates where the text * The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will * should appear <em>before</em> calling this method, since this method will
* modify the coordinate system. * modify the coordinate system.
* *
* @param ctx * @param ctx A GraphicsContext
* A GraphicsContext
*/ */
public void setGraphicsContext(GraphicsContext ctx) { public void setGraphicsContext(GraphicsContext ctx) {
ctx.setFont(getFont()); ctx.setFont(getFont());
@ -804,19 +768,17 @@ public class TextFont {
/** /**
* Set up a graphics context for drawing with this font. * Set up a graphics context for drawing with this font.
* * <p>
* Caller should call {@link GraphicsContext#save()} first, and then * Caller should call {@link GraphicsContext#save()} first, and then
* {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the * {@link GraphicsContext#restore()} afterwards, to clean up adjustments to the
* transformation matrix (i.e., translation, scaling). * transformation matrix (i.e., translation, scaling).
* * <p>
* The GraphicsContext should be translated to the coordinates where the text * The GraphicsContext should be translated to the coordinates where the text
* should appear <em>before</em> calling this method, since this method will * should appear <em>before</em> calling this method, since this method will
* modify the coordinate system. * modify the coordinate system.
* *
* @param ctx * @param ctx A GraphicsContext
* A GraphicsContext * @param xScaleFactor Additional horizontal scaling, normally 0.5 (for half-width
* @param xScaleFactor
* Additional horizontal scaling, normally 0.5 (for half-width
* characters) * characters)
*/ */
public void setGraphicsContext(GraphicsContext ctx, double xScaleFactor) { public void setGraphicsContext(GraphicsContext ctx, double xScaleFactor) {
@ -848,12 +810,15 @@ public class TextFont {
double otherWidth) { double otherWidth) {
if (c != '.') { if (c != '.') {
double factor = 1.0, dblFactor = 0.0; double factor = 1.0, dblFactor = 0.0;
if (c == 's' || c == 'z') if (c == 's' || c == 'z') {
factor = -1; factor = -1;
if (c == 'N') }
if (c == 'N') {
dblFactor = -2; dblFactor = -2;
if (c == 'M') }
if (c == 'M') {
dblFactor = 2; dblFactor = 2;
}
double x = squareSize * xScaleFactor / 2, y = -squareSize / 2; double x = squareSize * xScaleFactor / 2, y = -squareSize / 2;
x -= xDir * factor * (otherWidth / 2); x -= xDir * factor * (otherWidth / 2);
y -= yDir * factor * (otherWidth / 2); y -= yDir * factor * (otherWidth / 2);
@ -878,34 +843,25 @@ public class TextFont {
/** /**
* Draw text at the given position. * Draw text at the given position.
* * <p>
* For most cases, the simpler {@link #drawText(GraphicsContext, String)} or * For most cases, the simpler {@link #drawText(GraphicsContext, String)} or
* {@link #drawText(GraphicsContext, String, double)} will be easier to use. * {@link #drawText(GraphicsContext, String, double)} will be easier to use.
* * <p>
* If <code>clip</code> is true, the graphics context's current path will be * If <code>clip</code> is true, the graphics context's current path will be
* overwritten. * overwritten.
* *
* @param ctx * @param ctx A GraphicsContext
* A GraphicsContext * @param x X-position of the lower left corner of the text
* @param x * @param y Y-position of the lower left corner of the text
* X-position of the lower left corner of the text * @param text The text to be printed
* @param y * @param xScaleFactor Horizontal scaling factor, normally 1.0 (full width) or 0.5 (half
* Y-position of the lower left corner of the text
* @param text
* The text to be printed
* @param xScaleFactor
* Horizontal scaling factor, normally 1.0 (full width) or 0.5 (half
* width) * width)
* @param clear * @param clear True if the area should be cleared (to transparency) before
* True if the area should be cleared (to transparency) before
* drawing; normally true. * drawing; normally true.
* @param clip * @param clip True if the text drawing should be clipped to fit the expected
* True if the text drawing should be clipped to fit the expected
* printing area; normally true. * printing area; normally true.
* @param fill * @param fill True if the letter shapes should be filled; normally true.
* True if the letter shapes should be filled; normally true. * @param stroke True if the letter shapes should be stroked (outlined); normally
* @param stroke
* True if the letter shapes should be stroked (outlined); normally
* false. * false.
*/ */
public void textAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, boolean clear, public void textAt(GraphicsContext ctx, double x, double y, String text, double xScaleFactor, boolean clear,
@ -955,8 +911,9 @@ public class TextFont {
target.transform(new Affine(Transform.shear(-0.2, 0))); target.transform(new Affine(Transform.shear(-0.2, 0)));
} }
setGraphicsContext(target, xScaleFactor); setGraphicsContext(target, xScaleFactor);
if (fill != null) if (fill != null) {
target.fillText(text, 0.0, 0.0); target.fillText(text, 0.0, 0.0);
}
if ((mode & ATTR_BOLD) != 0) { if ((mode & ATTR_BOLD) != 0) {
// System.err.println("stroke2"); // System.err.println("stroke2");
target.save(); // save 4 target.save(); // save 4
@ -988,9 +945,10 @@ public class TextFont {
tmpCanvas.snapshot(snapshotParameters, img); tmpCanvas.snapshot(snapshotParameters, img);
if ((mode & ATTR_INVERSE) != 0) { if ((mode & ATTR_INVERSE) != 0) {
fillInverse(ctx, img, 0, -squareSize); fillInverse(ctx, img, 0, -squareSize);
} else } else {
ctx.drawImage(img, 0, -squareSize); ctx.drawImage(img, 0, -squareSize);
} }
}
ctx.restore(); // restore 1 ctx.restore(); // restore 1
} }

View File

@ -1,7 +1,6 @@
package inf101.v18.gfx.textmode; package inf101.v18.gfx.textmode;
import inf101.v18.gfx.Screen; import inf101.v18.gfx.Screen;
import inf101.v18.gfx.textmode.Printer;
import javafx.application.Application; import javafx.application.Application;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent; import javafx.scene.input.KeyEvent;
@ -33,7 +32,7 @@ public class TextFontAdjuster extends Application {
private boolean grid = true; private boolean grid = true;
private double adjustAmount = 0.1; private final double adjustAmount = 0.1;
private double adjustX(KeyCode code) { private double adjustX(KeyCode code) {
switch (code) { switch (code) {
@ -146,10 +145,11 @@ public class TextFontAdjuster extends Application {
drawBackgroundGrid(); drawBackgroundGrid();
return true; return true;
} else if (code == KeyCode.S) { } else if (code == KeyCode.S) {
if (event.isAltDown()) if (event.isAltDown()) {
screen.fitScaling(); screen.fitScaling();
else } else {
screen.zoomCycle(); screen.zoomCycle();
}
drawBackgroundGrid(); drawBackgroundGrid();
return true; return true;
} else if (code == KeyCode.A) { } else if (code == KeyCode.A) {

View File

@ -3,37 +3,50 @@ package inf101.v18.gfx.textmode;
import inf101.v18.gfx.Screen; import inf101.v18.gfx.Screen;
public enum TextMode { public enum TextMode {
/** Low resolution, wide screen (20:11, fits 16:9) text mode 40x22 */ /**
* Low resolution, wide screen (20:11, fits 16:9) text mode 40x22
*/
MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE), MODE_40X22(Constants.H40, Constants.V22, Screen.ASPECT_WIDE),
/** Low resolution, 16:10 aspect text mode 40x25 */ /**
* Low resolution, 16:10 aspect text mode 40x25
*/
MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM), MODE_40X25(Constants.H40, Constants.V25, Screen.ASPECT_MEDIUM),
/** Low resolution, 4:3 aspect text mode 40x30 */ /**
* Low resolution, 4:3 aspect text mode 40x30
*/
MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC), MODE_40X30(Constants.H40, Constants.V30, Screen.ASPECT_CLASSIC),
/** High resolution, wide screen (20:11, fits 16:9) text mode 80x22 */ /**
* High resolution, wide screen (20:11, fits 16:9) text mode 80x22
*/
MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE), MODE_80X22(Constants.H80, Constants.V22, Screen.ASPECT_WIDE),
/** High resolution, 16:10 aspect text mode 80x25 */ /**
* High resolution, 16:10 aspect text mode 80x25
*/
MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM), MODE_80X25(Constants.H80, Constants.V25, Screen.ASPECT_MEDIUM),
/** High resolution, 4:3 aspect text mode 80x30 */ /**
* High resolution, 4:3 aspect text mode 80x30
*/
MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC); MODE_80X30(Constants.H80, Constants.V30, Screen.ASPECT_CLASSIC);
protected static class Constants { protected static class Constants {
protected static final int H40 = 0, H80 = 1; protected static final int H40 = 0, H80 = 1;
protected static final int[] HREZ = { 40, 80 }; protected static final int[] HREZ = {40, 80};
protected static final int V22 = 0, V25 = 1, V30 = 2; protected static final int V22 = 0, V25 = 1, V30 = 2;
protected static final int[] VREZ = { 22, 25, 30 }; protected static final int[] VREZ = {22, 25, 30};
private static TextMode[] MODES = null; private static TextMode[] MODES = null;
// work around initialization order for statics and enums // work around initialization order for statics and enums
protected static TextMode getMode(int i) { protected static TextMode getMode(int i) {
if (MODES == null) if (MODES == null) {
MODES = new TextMode[] { MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30 }; MODES = new TextMode[]{MODE_40X22, MODE_40X25, MODE_40X30, MODE_80X22, MODE_80X25, MODE_80X30};
}
return MODES[(i + MODES.length) % MODES.length]; return MODES[(i + MODES.length) % MODES.length];
} }
} }
/** /**
* Size of the square-shaped "box" bounds of character cells. * Size of the square-shaped "box" bounds of character cells.
* * <p>
* For "high" resolution, characters will be squeezed horizontally to fit half * For "high" resolution, characters will be squeezed horizontally to fit half
* the width. * the width.
*/ */

View File

@ -4,8 +4,15 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
public enum GridDirection { public enum GridDirection {
EAST(0, 1, 0, 4), NORTH(90, 0, -1, 1), WEST(180, -1, 0, 8), SOUTH(270, 0, 1, 2), //
NORTHEAST(45, 1, -1, 5), NORTHWEST(135, -1, -1, 9), SOUTHWEST(225, -1, 1, 10), SOUTHEAST(315, 1, 1, 6), // EAST(0, 1, 0, 4),
NORTH(90, 0, -1, 1),
WEST(180, -1, 0, 8),
SOUTH(270, 0, 1, 2),
NORTHEAST(45, 1, -1, 5),
NORTHWEST(135, -1, -1, 9),
SOUTHWEST(225, -1, 1, 10),
SOUTHEAST(315, 1, 1, 6),
CENTER(0, 0, 0, 0); CENTER(0, 0, 0, 0);
/** /**

View File

@ -10,7 +10,7 @@ import java.util.stream.Stream;
* locations within the area. * locations within the area.
* <p> * <p>
* See {@link #location(int, int)} to covert (x,y)-coordinates to * See {@link #location(int, int)} to covert (x,y)-coordinates to
* {@link ILocation}s. * {@link Location}s.
* <p> * <p>
* An {@link IArea} does not <em>store</em> anything, it just defines valid * An {@link IArea} does not <em>store</em> anything, it just defines valid
* storage locations (e.g., for an {@link IGrid}), and the relationships between * storage locations (e.g., for an {@link IGrid}), and the relationships between
@ -18,14 +18,12 @@ import java.util.stream.Stream;
* *
* @author Anya Helene Bagge, UiB * @author Anya Helene Bagge, UiB
*/ */
public interface IArea extends Iterable<ILocation> { public interface IArea extends Iterable<Location> {
/** /**
* Check if a (x,y) is inside the area. * Check if a (x,y) is inside the area.
* *
* @param x * @param x X-coordinate
* X-coordinate * @param y Y-coordinate
* @param y
* Y-coordinate
* @return True if the (x,y) position lies within the area * @return True if the (x,y) position lies within the area
*/ */
boolean contains(int x, int y); boolean contains(int x, int y);
@ -33,8 +31,7 @@ public interface IArea extends Iterable<ILocation> {
/** /**
* Check if a position is inside the area. * Check if a position is inside the area.
* *
* @param pos * @param pos A position
* A position
* @return True if the position lies within the area * @return True if the position lies within the area
*/ */
boolean contains(IPosition pos); boolean contains(IPosition pos);
@ -51,9 +48,11 @@ public interface IArea extends Iterable<ILocation> {
* @param i * @param i
* @return A location * @return A location
*/ */
ILocation fromIndex(int i); Location fromIndex(int i);
/** @return Height of the area */ /**
* @return Height of the area
*/
int getHeight(); int getHeight();
/** /**
@ -63,7 +62,9 @@ public interface IArea extends Iterable<ILocation> {
*/ */
int getSize(); int getSize();
/** @return Width of the area */ /**
* @return Width of the area
*/
int getWidth(); int getWidth();
@Override @Override
@ -72,15 +73,12 @@ public interface IArea extends Iterable<ILocation> {
/** /**
* Get a location object corresponding to (x,y) * Get a location object corresponding to (x,y)
* *
* @param x * @param x X-coordinate
* X-coordinate * @param y Y-coordinate
* @param y
* Y-coordinate
* @return The location object associated with (x,y) * @return The location object associated with (x,y)
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if {@link #contains(int, int)} returns false for (x,y)
* if {@link #contains(int, int)} returns false for (x,y)
*/ */
ILocation location(int x, int y); Location location(int x, int y);
/** /**
* Get all locations in area * Get all locations in area
@ -89,11 +87,11 @@ public interface IArea extends Iterable<ILocation> {
* iterate over the locations. * iterate over the locations.
* <p> * <p>
* All locations in the list are guaranteed to be valid according to * All locations in the list are guaranteed to be valid according to
* {@link #isValid(ILocation)}. The returned list cannot be modified. * {@link #isValid(Location)}. The returned list cannot be modified.
* *
* @return An unmodifiable list with all the locations in the area * @return An unmodifiable list with all the locations in the area
*/ */
List<ILocation> locations(); List<Location> locations();
/** /**
* Return an object for iterating over all the neighbours of the given position, * Return an object for iterating over all the neighbours of the given position,
@ -105,29 +103,29 @@ public interface IArea extends Iterable<ILocation> {
* the same position two or eight times (if the area is wrapped horizontally, * the same position two or eight times (if the area is wrapped horizontally,
* vertically or both). * vertically or both).
* *
* @param pos * @param pos A position in the area
* A position in the area * @return An iterable over positions, with {@link #contains(Location)} being
* @return An iterable over positions, with {@link #contains(ILocation)} being
* true for each position. * true for each position.
* @throws IndexOutOfBoundsException if !contains(pos)
* @see #wrapsHorizontally(), {@link #wrapsVertically()} * @see #wrapsHorizontally(), {@link #wrapsVertically()}
* @throws IndexOutOfBoundsException
* if !contains(pos)
*/ */
Iterable<ILocation> neighboursOf(ILocation pos); Iterable<Location> neighboursOf(Location pos);
/** @return A (possibly) parallel {@link Stream} of all locations in the area */ /**
Stream<ILocation> parallelStream(); * @return A (possibly) parallel {@link Stream} of all locations in the area
*/
Stream<Location> parallelStream();
/** @return A {@link Stream} of all locations in the area */ /**
Stream<ILocation> stream(); * @return A {@link Stream} of all locations in the area
*/
Stream<Location> stream();
/** /**
* Convert a 2D coordinate to 1D * Convert a 2D coordinate to 1D
* *
* @param x * @param x X-coordinate
* X-coordinate * @param y Y-coordinate
* @param y
* Y-coordinate
* @return x + y*getWidth() * @return x + y*getWidth()
*/ */
int toIndex(int x, int y); int toIndex(int x, int y);

View File

@ -29,7 +29,7 @@ public interface IGrid<T> extends Iterable<T> {
/** /**
* Initialise the contents of all cells using an initialiser function. * Initialise the contents of all cells using an initialiser function.
* * <p>
* The function will be called with the (x,y) position of an element, and is * The function will be called with the (x,y) position of an element, and is
* expected to return the element to place at that position. For example: * expected to return the element to place at that position. For example:
* *
@ -38,14 +38,13 @@ public interface IGrid<T> extends Iterable<T> {
* grid.setAll((x, y) -> String.format("(%d,%d)", x, y)); * grid.setAll((x, y) -> String.format("(%d,%d)", x, y));
* </pre> * </pre>
* *
* @param initialiser * @param initialiser The initialiser function
* The initialiser function
*/ */
void fill(Function<ILocation, T> initialiser); void fill(Function<Location, T> initialiser);
/** /**
* Set the contents of all cells to <code>element</code> * Set the contents of all cells to <code>element</code>
* * <p>
* For example: * For example:
* *
* <pre> * <pre>
@ -59,91 +58,82 @@ public interface IGrid<T> extends Iterable<T> {
/** /**
* Get the contents of the cell in the given x,y location. * Get the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @throws IndexOutOfBoundsException if !isValid(pos)
* @throws IndexOutOfBoundsException
* if !isValid(pos)
*/ */
T get(ILocation pos); T get(Location pos);
/** /**
* Get the contents of the cell in the given x,y location. * Get the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param x * @param x The column of the cell to get the contents of.
* The column of the cell to get the contents of. * @param y The row of the cell to get contents of.
* @param y * @throws IndexOutOfBoundsException if !isValid(x,y)
* The row of the cell to get contents of.
* @throws IndexOutOfBoundsException
* if !isValid(x,y)
*/ */
T get(int x, int y); T get(int x, int y);
IArea getArea(); IArea getArea();
/** @return The height of the grid. */ /**
* @return The height of the grid.
*/
int getHeight(); int getHeight();
/** /**
* Get the contents of the cell in the given x,y location. * Get the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param defaultResult A default value to be substituted if the (x,y) is out of bounds or
* @param defaultResult
* A default value to be substituted if the (x,y) is out of bounds or
* contents == null. * contents == null.
*/ */
T getOrDefault(ILocation pos, T defaultResult); T getOrDefault(Location pos, T defaultResult);
/** /**
* Get the contents of the cell in the given x,y location. * Get the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param x * @param x The column of the cell to get the contents of.
* The column of the cell to get the contents of. * @param y The row of the cell to get contents of.
* @param y * @param defaultResult A default value to be substituted if the (x,y) is out of bounds or
* The row of the cell to get contents of.
* @param defaultResult
* A default value to be substituted if the (x,y) is out of bounds or
* contents == null. * contents == null.
*/ */
T getOrDefault(int x, int y, T defaultResult); T getOrDefault(int x, int y, T defaultResult);
/** @return The width of the grid. */ /**
* @return The width of the grid.
*/
int getWidth(); int getWidth();
/** /**
* Check if coordinates are valid. * Check if coordinates are valid.
* * <p>
* Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() < * Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() <
* getHeight(). * getHeight().
* *
* @param pos * @param pos A position
* A position
* @return true if the position is within the grid * @return true if the position is within the grid
*/ */
boolean isValid(ILocation pos); boolean isValid(Location pos);
/** /**
* Check if coordinates are valid. * Check if coordinates are valid.
* * <p>
* Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight(). * Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight().
* *
* @param x * @param x an x coordinate
* an x coordinate * @param y an y coordinate
* @param y
* an y coordinate
* @return true if the (x,y) position is within the grid * @return true if the (x,y) position is within the grid
*/ */
boolean isValid(int x, int y); boolean isValid(int x, int y);
@ -152,12 +142,12 @@ public interface IGrid<T> extends Iterable<T> {
* Create a parallel {@link Stream} with all the locations in this grid. * Create a parallel {@link Stream} with all the locations in this grid.
* <p> * <p>
* All locations obtained through the stream are guaranteed to be valid * All locations obtained through the stream are guaranteed to be valid
* according to {@link #isValid(ILocation)}. * according to {@link #isValid(Location)}.
* *
* @return A stream * @return A stream
* @see {@link java.util.Collection#parallelStream()} * @see {@link java.util.Collection#parallelStream()}
*/ */
Stream<ILocation> locationParallelStream(); Stream<Location> locationParallelStream();
/** /**
* Iterate over all grid locations * Iterate over all grid locations
@ -166,47 +156,42 @@ public interface IGrid<T> extends Iterable<T> {
* iterate over the elements. * iterate over the elements.
* <p> * <p>
* All locations obtained through the iterator are guaranteed to be valid * All locations obtained through the iterator are guaranteed to be valid
* according to {@link #isValid(ILocation)}. * according to {@link #isValid(Location)}.
* *
* @return An iterable for iterating over all the locations in the grid * @return An iterable for iterating over all the locations in the grid
*/ */
Iterable<ILocation> locations(); Iterable<Location> locations();
/** /**
* Create a {@link Stream} with all the locations in this grid. * Create a {@link Stream} with all the locations in this grid.
* <p> * <p>
* All locations obtained through the stream are guaranteed to be valid * All locations obtained through the stream are guaranteed to be valid
* according to {@link #isValid(ILocation)}. * according to {@link #isValid(Location)}.
* *
* @return A stream * @return A stream
*/ */
Stream<ILocation> locationStream(); Stream<Location> locationStream();
/** /**
* Set the contents of the cell in the given x,y location. * Set the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param element The contents the cell is to have.
* @param element * @throws IndexOutOfBoundsException if !isValid(pos)
* The contents the cell is to have.
* @throws IndexOutOfBoundsException
* if !isValid(pos)
*/ */
void set(ILocation pos, T element); void set(Location pos, T element);
/** /**
* Set the contents of the cell in the given x,y location. * Set the contents of the cell in the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param element * @param element The contents the cell is to have.
* The contents the cell is to have. * @throws IndexOutOfBoundsException if !isValid(x,y)
* @throws IndexOutOfBoundsException
* if !isValid(x,y)
*/ */
void set(int x, int y, T element); void set(int x, int y, T element);

View File

@ -1,107 +0,0 @@
package inf101.v18.grid;
import java.util.Collection;
import java.util.List;
/**
* Represents a location within an {@link IArea}.
* <p>
* <em>Immutable:</em> Locations are immutable; i.e., a particular
* <code>ILocation</code> will always have the same x- and y-coordinates and
* cannot be changed. Methods that find neighbours (such as
* {@link #go(GridDirection)}) will return another <code>ILocation</code>.
* <p>
* <em>Area:</em> All {@link ILocation}s are tied to an {@link IArea}, thus they
* will know whether they are on the edge of the area (and not all neighbouring
* grid coordinates will be valid locations within the area) and other
* coordinate system properties that are determined by the {@link IArea} (e.g.,
* if the coordinate system wraps around like on the surface of a donut).
* <p>
* <em>Unique objects:</em> All {@link ILocation} in an {@link IArea} are
* unique. This means that <code>area.location(x,y) == area.location(x,y)</code>
* for all <em>x</em> and <em>y</em>, and that:
*
* <pre>
* // equality is reference equality for locations in the same area
* if (l1.getArea() == l2.getArea())
* assertEquals(l1.equals(l2), (l1 == l2));
* </pre>
*
*
* @author Anya Helene Bagge, UiB
*/
public interface ILocation extends IPosition {
/**
* Iterate over neighbours in eight directions.
* <p>
* (The iterator may yield fewer than eight locations if the current location is
* at the edge of its containing area.
*
* @return The neighbours in the eight cardinal and intercardinal directions
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
* GridDirection#EAST}, @link GridDirection#WEST},
* {@link GridDirection#NORTHEAST}, @link
* GridDirection#NORTHWEST}, @link GridDirection#SOUTHEAST}, @link
* GridDirection#SOUTHWEST}, )
*/
Collection<ILocation> allNeighbours();
/**
* Checks whether you can go towards direction dir without going outside the
* area bounds
*
* @param dir
* @return True if go(dir) will succeed
*/
boolean canGo(GridDirection dir);
/**
* Iterate over north/south/east/west neighbours.
* <p>
* (The iterator may yield fewer than four locations if the current location is
* at the edge of its containing area.
*
* @return The neighbours in the four cardinal directions
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
* GridDirection#EAST}, @link GridDirection#WEST}
*/
Collection<ILocation> cardinalNeighbours();
IArea getArea();
int getIndex();
/**
* Return the next location in direction dir.
* <p>
* This <em>does not</em> change the location object; rather it returns the
* ILocation you would end up at if you go in the given direction from this
* ILocation.
*
* @param dir
* A direction
* @return A neighbouring location
* @throws IndexOutOfBoundsException
* if !canGo(dir)
*/
ILocation go(GridDirection dir);
/**
* Find the grid cells between this location (exclusive) and another location
* (inclusive).
*
* This is a list of length {@link #gridDistanceTo(other)}, containing
* the cells that a chess queen would visit when moving to <code>other</code>.
* <p>
* Computes the maximum of the horizontal and the vertical distance. For
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
*
* @param other
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
* other
*/
List<ILocation> gridLineTo(ILocation other);
}

View File

@ -8,29 +8,23 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Add to the cell at the given location. * Add to the cell at the given location.
* *
* @param loc * @param loc The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param element An element to be added to the cell.
* @param element * @throws IndexOutOfBoundsException if !isValid(loc)
* An element to be added to the cell.
* @throws IndexOutOfBoundsException
* if !isValid(loc)
*/ */
default void add(ILocation loc, T element) { default void add(Location loc, T element) {
get(loc).add(element); get(loc).add(element);
} }
/** /**
* Add to the cell at the given x,y location. * Add to the cell at the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param element An element to be added to the cell
* @param element * @throws IndexOutOfBoundsException if !isValid(x,y)
* An element to be added to the cell
* @throws IndexOutOfBoundsException
* if !isValid(x,y)
*/ */
default void add(int x, int y, T element) { default void add(int x, int y, T element) {
get(x, y).add(element); get(x, y).add(element);
@ -39,52 +33,42 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Check if a cell contains an element. * Check if a cell contains an element.
* *
* * @param loc The (x,y) position of the grid cell
* @param loc * @param predicate Search predicate.
* The (x,y) position of the grid cell
* @param predicate
* Search predicate.
* @return true if an element matching the predicate was found * @return true if an element matching the predicate was found
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(loc)
* if !isValid(loc)
*/ */
default boolean contains(ILocation loc, Predicate<T> predicate) { default boolean contains(Location loc, Predicate<T> predicate) {
for (T t : get(loc)) { for (T t : get(loc)) {
if (predicate.test(t)) if (predicate.test(t)) {
return true; return true;
} }
}
return false; return false;
} }
/** /**
* Check if a cell contains an element. * Check if a cell contains an element.
* *
* * @param loc The (x,y) position of the grid cell
* @param loc * @param element An element to search for.
* The (x,y) position of the grid cell
* @param element
* An element to search for.
* @return true if element is at the given location * @return true if element is at the given location
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(loc)
* if !isValid(loc)
*/ */
default boolean contains(ILocation loc, T element) { default boolean contains(Location loc, T element) {
return get(loc).contains(element); return get(loc).contains(element);
} }
/** /**
* Check if a cell contains an element. * Check if a cell contains an element.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param predicate Search predicate.
* @param predicate
* Search predicate.
* @return true if an element matching the predicate was found * @return true if an element matching the predicate was found
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(x,y)
* if !isValid(x,y)
*/ */
default boolean contains(int x, int y, Predicate<T> predicate) { default boolean contains(int x, int y, Predicate<T> predicate) {
return contains(this.getArea().location(x, y), predicate); return contains(this.getArea().location(x, y), predicate);
@ -92,17 +76,14 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Check if a cell contains an element. * Check if a cell contains an element.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param element An element to search for.
* @param element
* An element to search for.
* @return true if element is at the given location * @return true if element is at the given location
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(x,y)
* if !isValid(x,y)
*/ */
default boolean contains(int x, int y, T element) { default boolean contains(int x, int y, T element) {
return get(x, y).contains(element); return get(x, y).contains(element);
@ -111,32 +92,25 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Get all elements in a cell that match the predicate * Get all elements in a cell that match the predicate
* *
* * @param loc The (x,y) position of the grid cell
* @param loc * @param predicate Search predicate.
* The (x,y) position of the grid cell
* @param predicate
* Search predicate.
* @return true if an element matching the predicate was found * @return true if an element matching the predicate was found
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(loc)
* if !isValid(loc)
*/ */
default List<T> get(ILocation loc, Predicate<T> predicate) { default List<T> get(Location loc, Predicate<T> predicate) {
return get(loc).stream().filter(predicate).collect(Collectors.toList()); return get(loc).stream().filter(predicate).collect(Collectors.toList());
} }
/** /**
* Check if a cell contains an element. * Check if a cell contains an element.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell to get the contents of.
* The (x,y) position of the grid cell to get the contents of. * @param predicate Search predicate.
* @param predicate
* Search predicate.
* @return true if an element matching the predicate was found * @return true if an element matching the predicate was found
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(x,y)
* if !isValid(x,y)
*/ */
default List<T> get(int x, int y, Predicate<T> predicate) { default List<T> get(int x, int y, Predicate<T> predicate) {
return get(this.getArea().location(x, y), predicate); return get(this.getArea().location(x, y), predicate);
@ -145,15 +119,12 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Remove an element from the cell at the given location. * Remove an element from the cell at the given location.
* *
* @param loc * @param loc The location of the grid cell
* The location of the grid cell * @param predicate Predicate which should be true for elements to be removed
* @param predicate
* Predicate which should be true for elements to be removed
* @return Number of elements removed * @return Number of elements removed
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(loc)
* if !isValid(loc)
*/ */
default int remove(ILocation loc, Predicate<T> predicate) { default int remove(Location loc, Predicate<T> predicate) {
List<T> list = get(loc); List<T> list = get(loc);
int s = list.size(); int s = list.size();
get(loc).removeIf(predicate); get(loc).removeIf(predicate);
@ -163,31 +134,25 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Remove an element from the cell at the given location. * Remove an element from the cell at the given location.
* *
* @param loc * @param loc The location of the grid cell
* The location of the grid cell * @param element An element to be removed from the cell.
* @param element
* An element to be removed from the cell.
* @return Number of elements removed * @return Number of elements removed
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(loc)
* if !isValid(loc)
*/ */
default int remove(ILocation loc, T element) { default int remove(Location loc, T element) {
return get(loc).remove(element) ? 1 : 0; return get(loc).remove(element) ? 1 : 0;
} }
/** /**
* Remove an element from the cell at the given x,y location. * Remove an element from the cell at the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell
* The (x,y) position of the grid cell * @param predicate Predicate which should be true for elements to be removed
* @param predicate
* Predicate which should be true for elements to be removed
* @return Number of elements removed * @return Number of elements removed
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(x,y)
* if !isValid(x,y)
*/ */
default int remove(int x, int y, Predicate<T> predicate) { default int remove(int x, int y, Predicate<T> predicate) {
return remove(getArea().location(x, y), predicate); return remove(getArea().location(x, y), predicate);
@ -195,17 +160,14 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
/** /**
* Remove an element from the cell at the given x,y location. * Remove an element from the cell at the given x,y location.
* * <p>
* y must be greater than or equal to 0 and less than getHeight(). x must be * y must be greater than or equal to 0 and less than getHeight(). x must be
* greater than or equal to 0 and less than getWidth(). * greater than or equal to 0 and less than getWidth().
* *
* @param pos * @param pos The (x,y) position of the grid cell
* The (x,y) position of the grid cell * @param element An element to be removed from the cell
* @param element
* An element to be removed from the cell
* @return Number of elements removed * @return Number of elements removed
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if !isValid(x,y)
* if !isValid(x,y)
*/ */
default int remove(int x, int y, T element) { default int remove(int x, int y, T element) {
return get(x, y).remove(element) ? 1 : 0; return get(x, y).remove(element) ? 1 : 0;

View File

@ -3,8 +3,7 @@ package inf101.v18.grid;
public interface IPosition { public interface IPosition {
/** /**
* @param obj * @param obj Another object
* Another object
* @return true if obj is also an IPosition, and the x and y coordinates are * @return true if obj is also an IPosition, and the x and y coordinates are
* equal * equal
*/ */
@ -14,7 +13,7 @@ public interface IPosition {
/** /**
* Find the Euclidian distance between the midpoint of this position and another * Find the Euclidian distance between the midpoint of this position and another
* position. * position.
* * <p>
* The distance is computed with the Pythagorean formula, with the assumption * The distance is computed with the Pythagorean formula, with the assumption
* that the grid cells are square shaped with <em>width</em> = <em>height</em> = * that the grid cells are square shaped with <em>width</em> = <em>height</em> =
* 1. For example, the distance from (0,0) to (3,5) is (3²+5²) = 5.83. * 1. For example, the distance from (0,0) to (3,5) is (3²+5²) = 5.83.
@ -40,7 +39,7 @@ public interface IPosition {
/** /**
* Find the distance in grid cells to another location. * Find the distance in grid cells to another location.
* * <p>
* This is different from {@link #stepDistanceTo(IPosition)} in that diagonal * This is different from {@link #stepDistanceTo(IPosition)} in that diagonal
* steps are allowed, and is the same as the number of steps a queen would take * steps are allowed, and is the same as the number of steps a queen would take
* on a chess board. * on a chess board.
@ -61,7 +60,7 @@ public interface IPosition {
/** /**
* Find the number of non-diagonal steps needed to go from this location the * Find the number of non-diagonal steps needed to go from this location the
* other location. * other location.
* * <p>
* This is different from {@link #gridDistanceTo(IPosition)} in that only * This is different from {@link #gridDistanceTo(IPosition)} in that only
* non-diagonal steps are allowed, and is the same as the number of steps a rook * non-diagonal steps are allowed, and is the same as the number of steps a rook
* would take on a chess board. * would take on a chess board.
@ -76,7 +75,9 @@ public interface IPosition {
*/ */
int stepDistanceTo(IPosition other); int stepDistanceTo(IPosition other);
/** @return Position as a string, "(x,y)" */ /**
* @return Position as a string, "(x,y)"
*/
@Override @Override
String toString(); String toString();

View File

@ -0,0 +1,104 @@
package inf101.v18.grid;
import java.util.Collection;
import java.util.List;
/**
* Represents a location within an {@link IArea}.
* <p>
* <em>Immutable:</em> Locations are immutable; i.e., a particular
* <code>ILocation</code> will always have the same x- and y-coordinates and
* cannot be changed. Methods that find neighbours (such as
* {@link #go(GridDirection)}) will return another <code>ILocation</code>.
* <p>
* <em>Area:</em> All {@link Location}s are tied to an {@link IArea}, thus they
* will know whether they are on the edge of the area (and not all neighbouring
* grid coordinates will be valid locations within the area) and other
* coordinate system properties that are determined by the {@link IArea} (e.g.,
* if the coordinate system wraps around like on the surface of a donut).
* <p>
* <em>Unique objects:</em> All {@link Location} in an {@link IArea} are
* unique. This means that <code>area.location(x,y) == area.location(x,y)</code>
* for all <em>x</em> and <em>y</em>, and that:
*
* <pre>
* // equality is reference equality for locations in the same area
* if (l1.getArea() == l2.getArea())
* assertEquals(l1.equals(l2), (l1 == l2));
* </pre>
*
* @author Anya Helene Bagge, UiB
*/
public interface Location extends IPosition {
/**
* Iterate over neighbours in eight directions.
* <p>
* (The iterator may yield fewer than eight locations if the current location is
* at the edge of its containing area.
*
* @return The neighbours in the eight cardinal and intercardinal directions
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
* GridDirection#EAST}, @link GridDirection#WEST},
* {@link GridDirection#NORTHEAST}, @link
* GridDirection#NORTHWEST}, @link GridDirection#SOUTHEAST}, @link
* GridDirection#SOUTHWEST}, )
*/
Collection<Location> allNeighbours();
/**
* Checks whether you can go towards direction dir without going outside the
* area bounds
*
* @param dir
* @return True if go(dir) will succeed
*/
boolean canGo(GridDirection dir);
/**
* Iterate over north/south/east/west neighbours.
* <p>
* (The iterator may yield fewer than four locations if the current location is
* at the edge of its containing area.
*
* @return The neighbours in the four cardinal directions
* ({@link GridDirection#NORTH}, @link GridDirection#SOUTH}, @link
* GridDirection#EAST}, @link GridDirection#WEST}
*/
Collection<Location> cardinalNeighbours();
IArea getArea();
int getIndex();
/**
* Return the next location in direction dir.
* <p>
* This <em>does not</em> change the location object; rather it returns the
* ILocation you would end up at if you go in the given direction from this
* ILocation.
*
* @param dir A direction
* @return A neighbouring location
* @throws IndexOutOfBoundsException if !canGo(dir)
*/
Location go(GridDirection dir);
/**
* Find the grid cells between this location (exclusive) and another location
* (inclusive).
* <p>
* This is a list of length {@link #gridDistanceTo(other)}, containing
* the cells that a chess queen would visit when moving to <code>other</code>.
* <p>
* Computes the maximum of the horizontal and the vertical distance. For
* example, to go from (0,0) to (3,5), you could go three steps SOUTHEAST and
* two steps SOUTH, so the {@link #gridDistanceTo(IPosition)} is 5.
*
* @param other
* @return Number of horizontal/vertical/diagonal (<em>queen</em>-like) steps to
* other
*/
List<Location> gridLineTo(Location other);
}

View File

@ -6,14 +6,16 @@ import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Stream; import java.util.stream.Stream;
/** A Grid contains a set of cells in a square 2D matrix. */ /**
* A Grid contains a set of cells in a square 2D matrix.
*/
public class MyGrid<T> implements IGrid<T> { public class MyGrid<T> implements IGrid<T> {
private final IArea area; private final IArea area;
private final List<T> cells; private final List<T> cells;
/** /**
* Construct a grid with the given dimensions. * Construct a grid with the given dimensions.
* * <p>
* The initialiser function will be called with the (x,y) position of an * The initialiser function will be called with the (x,y) position of an
* element, and is expected to return the element to place at that position. For * element, and is expected to return the element to place at that position. For
* example: * example:
@ -24,17 +26,16 @@ public class MyGrid<T> implements IGrid<T> {
* </pre> * </pre>
* *
* @param area * @param area
* @param initialiser * @param initialiser The initialiser function
* The initialiser function
*/ */
public MyGrid(IArea area, Function<ILocation, T> initialiser) { public MyGrid(IArea area, Function<Location, T> initialiser) {
if (area == null || initialiser == null) { if (area == null || initialiser == null) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
} }
this.area = area; this.area = area;
this.cells = new ArrayList<T>(area.getSize()); this.cells = new ArrayList<T>(area.getSize());
for (ILocation loc : area) { for (Location loc : area) {
cells.add(initialiser.apply(loc)); cells.add(initialiser.apply(loc));
} }
} }
@ -43,8 +44,7 @@ public class MyGrid<T> implements IGrid<T> {
* Construct a grid with the given dimensions. * Construct a grid with the given dimensions.
* *
* @param area * @param area
* @param initElement * @param initElement What the cells should initially hold (possibly null)
* What the cells should initially hold (possibly null)
*/ */
public MyGrid(IArea area, T initElement) { public MyGrid(IArea area, T initElement) {
if (area == null) { if (area == null) {
@ -60,7 +60,7 @@ public class MyGrid<T> implements IGrid<T> {
/** /**
* Construct a grid with the given dimensions. * Construct a grid with the given dimensions.
* * <p>
* The initialiser function will be called with the (x,y) position of an * The initialiser function will be called with the (x,y) position of an
* element, and is expected to return the element to place at that position. For * element, and is expected to return the element to place at that position. For
* example: * example:
@ -72,10 +72,9 @@ public class MyGrid<T> implements IGrid<T> {
* *
* @param width * @param width
* @param height * @param height
* @param initialiser * @param initialiser The initialiser function
* The initialiser function
*/ */
public MyGrid(int width, int height, Function<ILocation, T> initialiser) { public MyGrid(int width, int height, Function<Location, T> initialiser) {
this(new RectArea(width, height), initialiser); this(new RectArea(width, height), initialiser);
} }
@ -84,8 +83,7 @@ public class MyGrid<T> implements IGrid<T> {
* *
* @param width * @param width
* @param height * @param height
* @param initElement * @param initElement What the cells should initially hold (possibly null)
* What the cells should initially hold (possibly null)
*/ */
public MyGrid(int width, int height, T initElement) { public MyGrid(int width, int height, T initElement) {
this(new RectArea(width, height), initElement); this(new RectArea(width, height), initElement);
@ -107,9 +105,10 @@ public class MyGrid<T> implements IGrid<T> {
} }
@Override @Override
public void fill(Function<ILocation, T> initialiser) { public void fill(Function<Location, T> initialiser) {
if (initialiser == null) if (initialiser == null) {
throw new NullPointerException(); throw new NullPointerException();
}
for (int i = 0; i < area.getSize(); i++) { for (int i = 0; i < area.getSize(); i++) {
cells.set(i, initialiser.apply(area.fromIndex(i))); cells.set(i, initialiser.apply(area.fromIndex(i)));
@ -124,12 +123,13 @@ public class MyGrid<T> implements IGrid<T> {
} }
@Override @Override
public T get(ILocation loc) { public T get(Location loc) {
if (loc.getArea() == area) if (loc.getArea() == area) {
return cells.get(loc.getIndex()); return cells.get(loc.getIndex());
else } else {
return cells.get(area.toIndex(loc.getX(), loc.getY())); return cells.get(area.toIndex(loc.getX(), loc.getY()));
} }
}
@Override @Override
public T get(int x, int y) { public T get(int x, int y) {
@ -147,13 +147,14 @@ public class MyGrid<T> implements IGrid<T> {
} }
@Override @Override
public T getOrDefault(ILocation loc, T defaultResult) { public T getOrDefault(Location loc, T defaultResult) {
if (loc.getArea() == area) { if (loc.getArea() == area) {
T r = cells.get(loc.getIndex()); T r = cells.get(loc.getIndex());
if (r != null) if (r != null) {
return r; return r;
else } else {
return defaultResult; return defaultResult;
}
} else { } else {
return getOrDefault(loc.getX(), loc.getY(), defaultResult); return getOrDefault(loc.getX(), loc.getY(), defaultResult);
} }
@ -162,13 +163,15 @@ public class MyGrid<T> implements IGrid<T> {
@Override @Override
public T getOrDefault(int x, int y, T defaultResult) { public T getOrDefault(int x, int y, T defaultResult) {
T r = null; T r = null;
if (isValid(x, y)) if (isValid(x, y)) {
r = get(x, y); r = get(x, y);
if (r != null) }
if (r != null) {
return r; return r;
else } else {
return defaultResult; return defaultResult;
} }
}
@Override @Override
public int getWidth() { public int getWidth() {
@ -176,7 +179,7 @@ public class MyGrid<T> implements IGrid<T> {
} }
@Override @Override
public boolean isValid(ILocation loc) { public boolean isValid(Location loc) {
return loc.getArea() == area || area.contains(loc.getX(), loc.getY()); return loc.getArea() == area || area.contains(loc.getX(), loc.getY());
} }
@ -191,22 +194,22 @@ public class MyGrid<T> implements IGrid<T> {
} }
@Override @Override
public Stream<ILocation> locationParallelStream() { public Stream<Location> locationParallelStream() {
return area.parallelStream(); return area.parallelStream();
} }
@Override @Override
public Iterable<ILocation> locations() { public Iterable<Location> locations() {
return area; return area;
} }
@Override @Override
public Stream<ILocation> locationStream() { public Stream<Location> locationStream() {
return area.stream(); return area.stream();
} }
@Override @Override
public void set(ILocation loc, T element) { public void set(Location loc, T element) {
if (loc.getArea() == area) { if (loc.getArea() == area) {
cells.set(loc.getIndex(), element); cells.set(loc.getIndex(), element);
} else { } else {

View File

@ -2,9 +2,21 @@
## Changes from the Cellular Automaton version ## Changes from the Cellular Automaton version
* If you look at `MyGrid.java`, you will notice that it no longer uses `IList<T>`/`MyList<T>` to hold the grid cells, and both `IList.java` and `MyList.java` have been removed. Instead we use standard built-in Java lists, with the `List<T>` interface (and `ArrayList<T>` as the class for new list objects). * If you look at `MyGrid.java`, you will notice that it no longer uses `IList<T>`/`MyList<T>` to hold the grid cells,
* This saves us the work of maintaining and improving our own list, and makes it immediately compatible with useful standard Java library operations on lists. and both `IList.java` and `MyList.java` have been removed. Instead we use standard built-in Java lists, with
* In general, you should always use built-in APIs (such as the Java Collections API which provides lists and similar) when available. Not only does it save time, but they're likely to be much better tested (and probably better designed) than you can do yourself, so they're less likely to have, e.g., stupid bugs that only show up in corner cases. You may also get better performance. the `List<T>` interface (and `ArrayList<T>` as the class for new list objects).
* You might want to do things yourself if you want to learn how stuff works; also, in some cases, there may not be a suitable API available or the API doesn't really fully fit with what we want to do. While Java has one-dimensional lists and arrays, it doesn't really have built-in classes for grids, so we're making our own. * This saves us the work of maintaining and improving our own list, and makes it immediately compatible with useful
* An alternative to IGrid/MyGrid would be to use two-dimensional arrays but these aren't *actually* two-dimensional arrays, but rather arrays-of-arrays, which makes them fit less well with the concept of a “grid” (the same goes for making a list of lists). In addition to being a bit inconvenient, they're also much less efficient in use and they won't let us do useful things like “please give me the cell to the north of this cell” or “please give me all cells neighbouring this cell”. standard Java library operations on lists.
* In general, you should always use built-in APIs (such as the Java Collections API which provides lists and
similar) when available. Not only does it save time, but they're likely to be much better tested (and probably
better designed) than you can do yourself, so they're less likely to have, e.g., stupid bugs that only show up in
corner cases. You may also get better performance.
* You might want to do things yourself if you want to learn how stuff works; also, in some cases, there may not be a
suitable API available or the API doesn't really fully fit with what we want to do. While Java has
one-dimensional lists and arrays, it doesn't really have built-in classes for grids, so we're making our own.
* An alternative to IGrid/MyGrid would be to use two-dimensional arrays but these aren't *actually*
two-dimensional arrays, but rather arrays-of-arrays, which makes them fit less well with the concept of a “grid” (
the same goes for making a list of lists). In addition to being a bit inconvenient, they're also much less
efficient in use and they won't let us do useful things like “please give me the cell to the north of this cell”
or “please give me all cells neighbouring this cell”.

View File

@ -8,12 +8,18 @@ import java.util.List;
import java.util.stream.Stream; import java.util.stream.Stream;
public class RectArea implements IArea { public class RectArea implements IArea {
/** A class to represent an (x, y)-location on a grid. */ /**
class Location implements ILocation { * A class to represent an (x, y)-location on a grid.
*/
class Location implements inf101.v18.grid.Location {
/** value of the x-coordinate */ /**
* value of the x-coordinate
*/
protected final int x; protected final int x;
/** value of the y-coordinate */ /**
* value of the y-coordinate
*/
protected final int y; protected final int y;
protected final int idx; protected final int idx;
protected final int edgeMask; protected final int edgeMask;
@ -22,14 +28,10 @@ public class RectArea implements IArea {
* Main constructor. Initializes a new {@link #Location} objects with the * Main constructor. Initializes a new {@link #Location} objects with the
* corresponding values of x and y. * corresponding values of x and y.
* *
* @param x * @param x X coordinate
* X coordinate * @param y Y coordinate
* @param y * @param idx 1-dimensional index
* Y coordinate * @param edgeMask mask with bits {@link RectArea#N}, {@link RectArea#S},
* @param idx
* 1-dimensional index
* @param edgeMask
* mask with bits {@link RectArea#N}, {@link RectArea#S},
* {@link RectArea#E}, {@link RectArea#W} set if we're on the * {@link RectArea#E}, {@link RectArea#W} set if we're on the
* corresponding edge of the area * corresponding edge of the area
*/ */
@ -41,12 +43,13 @@ public class RectArea implements IArea {
} }
@Override @Override
public Collection<ILocation> allNeighbours() { public Collection<inf101.v18.grid.Location> allNeighbours() {
Collection<ILocation> ns = new ArrayList<>(8); Collection<inf101.v18.grid.Location> ns = new ArrayList<>(8);
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) { for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) {
if (canGo(d)) if (canGo(d)) {
ns.add(go(d)); ns.add(go(d));
} }
}
return ns; return ns;
} }
@ -56,12 +59,13 @@ public class RectArea implements IArea {
} }
@Override @Override
public Collection<ILocation> cardinalNeighbours() { public Collection<inf101.v18.grid.Location> cardinalNeighbours() {
Collection<ILocation> ns = new ArrayList<>(4); Collection<inf101.v18.grid.Location> ns = new ArrayList<>(4);
for (GridDirection d : GridDirection.FOUR_DIRECTIONS) { for (GridDirection d : GridDirection.FOUR_DIRECTIONS) {
if (canGo(d)) if (canGo(d)) {
ns.add(go(d)); ns.add(go(d));
} }
}
return ns; return ns;
} }
@ -109,7 +113,7 @@ public class RectArea implements IArea {
} }
@Override @Override
public ILocation go(GridDirection dir) { public inf101.v18.grid.Location go(GridDirection dir) {
return location(x + dir.getDx(), y + dir.getDy()); return location(x + dir.getDx(), y + dir.getDy());
} }
@ -119,15 +123,17 @@ public class RectArea implements IArea {
} }
@Override @Override
public List<ILocation> gridLineTo(ILocation other) { public List<inf101.v18.grid.Location> gridLineTo(inf101.v18.grid.Location other) {
if (!contains(other)) if (!contains(other)) {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
}
int distX = other.getX() - x; int distX = other.getX() - x;
int distY = other.getY() - y; int distY = other.getY() - y;
int length = Math.max(Math.abs(distX), Math.abs(distY)); int length = Math.max(Math.abs(distX), Math.abs(distY));
List<ILocation> line = new ArrayList<>(length); List<inf101.v18.grid.Location> line = new ArrayList<>(length);
if (length == 0) if (length == 0) {
return line; return line;
}
double dx = (double) distX / (double) length; double dx = (double) distX / (double) length;
double dy = (double) distY / (double) length; double dy = (double) distY / (double) length;
// System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length); // System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length);
@ -161,7 +167,7 @@ public class RectArea implements IArea {
protected final int width; protected final int width;
protected final int height; protected final int height;
protected final int size; protected final int size;
protected final List<ILocation> locs; protected final List<inf101.v18.grid.Location> locs;
protected final boolean hWrap, vWrap; protected final boolean hWrap, vWrap;
@ -178,7 +184,7 @@ public class RectArea implements IArea {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.size = width * height; this.size = width * height;
List<ILocation> l = new ArrayList<>(size); List<inf101.v18.grid.Location> l = new ArrayList<>(size);
for (int y = 0, i = 0; y < height; y++) { for (int y = 0, i = 0; y < height; y++) {
// set N or S bits if we're on the northern or southern edge // set N or S bits if we're on the northern or southern edge
int edge = (y == 0 ? GridDirection.NORTH.getMask() : 0) int edge = (y == 0 ? GridDirection.NORTH.getMask() : 0)
@ -194,11 +200,9 @@ public class RectArea implements IArea {
} }
/** /**
* @param x * @param x X-coordinate
* X-coordinate
* @return The same x, wrapped to wrapX(x) * @return The same x, wrapped to wrapX(x)
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if coordinate is out of range, and wrapping is not enabled
* if coordinate is out of range, and wrapping is not enabled
*/ */
protected int checkX(int x) { protected int checkX(int x) {
x = wrapX(x); x = wrapX(x);
@ -210,11 +214,9 @@ public class RectArea implements IArea {
} }
/** /**
* @param y * @param y Y-coordinate
* Y-coordinate
* @return The same y, wrapped to wrapY(y) * @return The same y, wrapped to wrapY(y)
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException if coordinate is out of range, and wrapping is not enabled
* if coordinate is out of range, and wrapping is not enabled
*/ */
protected int checkY(int y) { protected int checkY(int y) {
y = wrapY(y); y = wrapY(y);
@ -233,16 +235,17 @@ public class RectArea implements IArea {
@Override @Override
public boolean contains(IPosition pos) { public boolean contains(IPosition pos) {
return (pos instanceof ILocation && ((ILocation) pos).getArea() == this) || contains(pos.getX(), pos.getY()); return (pos instanceof inf101.v18.grid.Location && ((inf101.v18.grid.Location) pos).getArea() == this) || contains(pos.getX(), pos.getY());
} }
@Override @Override
public ILocation fromIndex(int i) { public inf101.v18.grid.Location fromIndex(int i) {
if (i >= 0 && i < size) if (i >= 0 && i < size) {
return locs.get(i); return locs.get(i);
else } else {
throw new IndexOutOfBoundsException("" + i); throw new IndexOutOfBoundsException("" + i);
} }
}
@Override @Override
public int getHeight() { public int getHeight() {
@ -260,35 +263,36 @@ public class RectArea implements IArea {
} }
@Override @Override
public Iterator<ILocation> iterator() { public Iterator<inf101.v18.grid.Location> iterator() {
return locs.iterator(); return locs.iterator();
} }
@Override @Override
public ILocation location(int x, int y) { public inf101.v18.grid.Location location(int x, int y) {
if (x < 0 || x >= width || y < 0 || y >= height) if (x < 0 || x >= width || y < 0 || y >= height) {
throw new IndexOutOfBoundsException("(" + x + "," + y + ")"); throw new IndexOutOfBoundsException("(" + x + "," + y + ")");
}
int i = x + y * width; int i = x + y * width;
return locs.get(i); return locs.get(i);
} }
@Override @Override
public List<ILocation> locations() { public List<inf101.v18.grid.Location> locations() {
return locs; // (OK since locs has been through Collections.unmodifiableList()) return locs; // (OK since locs has been through Collections.unmodifiableList())
} }
@Override @Override
public Iterable<ILocation> neighboursOf(ILocation pos) { public Iterable<inf101.v18.grid.Location> neighboursOf(inf101.v18.grid.Location pos) {
return pos.allNeighbours(); return pos.allNeighbours();
} }
@Override @Override
public Stream<ILocation> parallelStream() { public Stream<inf101.v18.grid.Location> parallelStream() {
return locs.parallelStream(); return locs.parallelStream();
} }
@Override @Override
public Stream<ILocation> stream() { public Stream<inf101.v18.grid.Location> stream() {
return locs.stream(); return locs.stream();
} }

View File

@ -3,7 +3,9 @@ package inf101.v18.rogue101;
import javafx.application.Application; import javafx.application.Application;
public class Launcher { public class Launcher {
public static void main(String[] args) { public static void main(String[] args) {
Application.launch(Main.class, args); Application.launch(Main.class, args);
} }
} }

View File

@ -1,13 +1,10 @@
package inf101.v18.rogue101; package inf101.v18.rogue101;
import java.io.PrintWriter;
import java.io.StringWriter;
import inf101.v18.gfx.Screen; import inf101.v18.gfx.Screen;
import inf101.v18.gfx.gfxmode.ITurtle; import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer; import inf101.v18.gfx.textmode.Printer;
import inf101.v18.gfx.textmode.TextMode; import inf101.v18.gfx.textmode.TextMode;
import inf101.v18.rogue101.game.Game; import inf101.v18.rogue101.game.RogueGame;
import javafx.animation.KeyFrame; import javafx.animation.KeyFrame;
import javafx.animation.Timeline; import javafx.animation.Timeline;
import javafx.application.Application; import javafx.application.Application;
@ -17,7 +14,11 @@ import javafx.scene.input.KeyEvent;
import javafx.stage.Stage; import javafx.stage.Stage;
import javafx.util.Duration; import javafx.util.Duration;
import java.io.PrintWriter;
import java.io.StringWriter;
public class Main extends Application { public class Main extends Application {
// you might want to tune these options // you might want to tune these options
public static final boolean MAIN_USE_BACKGROUND_GRID = false; public static final boolean MAIN_USE_BACKGROUND_GRID = false;
public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true; public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true;
@ -25,15 +26,13 @@ public class Main extends Application {
public static final TextMode MAIN_TEXT_MODE = TextMode.MODE_80X25; public static final TextMode MAIN_TEXT_MODE = TextMode.MODE_80X25;
public static final boolean DEBUG_TIME = false; public static final boolean DEBUG_TIME = false;
public static final int LINE_MAP_BOTTOM = 20;
public static final int LINE_STATUS = 21; public static final int LINE_STATUS = 21;
public static final int LINE_MSG1 = 22; public static final int LINE_MSG1 = 22;
public static final int LINE_MSG2 = 23; public static final int LINE_MSG2 = 23;
public static final int LINE_MSG3 = 24; public static final int LINE_MSG3 = 24;
public static final int LINE_DEBUG = 25; public static final int LINE_DEBUG = 25;
public static final int COLUMN_MAP_END = 40; public static final int COLUMN_MAP_END = 40;
public static final int COLUMN_RIGHTSIDE_START = 43; public static final int COLUMN_RIGHT_SIDE_START = 43;
public static void main(String[] args) { public static void main(String[] args) {
launch(args); launch(args);
@ -43,7 +42,7 @@ public class Main extends Application {
private ITurtle painter; private ITurtle painter;
private Printer printer; private Printer printer;
private Game game; private RogueGame game;
private boolean grid = MAIN_USE_BACKGROUND_GRID; private boolean grid = MAIN_USE_BACKGROUND_GRID;
private boolean autoNextTurn = false; private boolean autoNextTurn = false;
@ -52,7 +51,7 @@ public class Main extends Application {
private void setup() { private void setup() {
// //
game = new Game(screen, painter, printer); game = new RogueGame(screen, painter, printer);
game.draw(); game.draw();
// //
@ -89,8 +88,9 @@ public class Main extends Application {
// Font with emojis need separate download // Font with emojis need separate download
printer.setFont(Printer.FONT_SYMBOLA); printer.setFont(Printer.FONT_SYMBOLA);
if (grid) if (grid) {
printer.drawCharCells(); printer.drawCharCells();
}
printer.setAutoScroll(false); printer.setAutoScroll(false);
screen.setKeyPressedHandler((KeyEvent event) -> { screen.setKeyPressedHandler((KeyEvent event) -> {
KeyCode code = event.getCode(); KeyCode code = event.getCode();
@ -99,22 +99,25 @@ public class Main extends Application {
System.exit(0); System.exit(0);
} else if (code == KeyCode.R) { } else if (code == KeyCode.R) {
printer.cycleMode(true); printer.cycleMode(true);
if (grid) if (grid) {
printer.drawCharCells(); printer.drawCharCells();
}
game.draw(); game.draw();
printer.redrawDirty(); printer.redrawDirty();
return true; return true;
} else if (code == KeyCode.A) { } else if (code == KeyCode.A) {
screen.cycleAspect(); screen.cycleAspect();
if (grid) if (grid) {
printer.drawCharCells(); printer.drawCharCells();
}
return true; return true;
} else if (code == KeyCode.G) { } else if (code == KeyCode.G) {
grid = !grid; grid = !grid;
if (grid) if (grid) {
printer.drawCharCells(); printer.drawCharCells();
else } else {
screen.clearBackground(); screen.clearBackground();
}
return true; return true;
} else if (code == KeyCode.F) { } else if (code == KeyCode.F) {
screen.setFullScreen(!screen.isFullScreen()); screen.setFullScreen(!screen.isFullScreen());
@ -172,8 +175,9 @@ public class Main extends Application {
public void doTurn() { public void doTurn() {
long t = System.currentTimeMillis(); long t = System.currentTimeMillis();
boolean waitForPlayer = game.doTurn(); boolean waitForPlayer = game.doTurn();
if (DEBUG_TIME) if (DEBUG_TIME) {
System.out.println("doTurn() took " + (System.currentTimeMillis() - t) + "ms"); System.out.println("doTurn() took " + (System.currentTimeMillis() - t) + "ms");
}
long t2 = System.currentTimeMillis(); long t2 = System.currentTimeMillis();
game.draw(); game.draw();
printer.redrawDirty(); printer.redrawDirty();
@ -182,9 +186,10 @@ public class Main extends Application {
System.out.println("doTurn()+draw() took " + (System.currentTimeMillis() - t) + "ms"); System.out.println("doTurn()+draw() took " + (System.currentTimeMillis() - t) + "ms");
System.out.println("waiting for player? " + waitForPlayer); System.out.println("waiting for player? " + waitForPlayer);
} }
if (!waitForPlayer) if (!waitForPlayer) {
smallStepTimeline.playFromStart(); // this will kickstart a new turn in a few milliseconds smallStepTimeline.playFromStart(); // this will kickstart a new turn in a few milliseconds
} }
}
public static String BUILTIN_MAP = "40 20\n" // public static String BUILTIN_MAP = "40 20\n" //
+ "########################################\n" // + "########################################\n" //

View File

@ -1,29 +1,29 @@
package inf101.v18.rogue101.enemies; package inf101.v18.rogue101.enemies;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.Main; import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.game.Game; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.RogueGame;
import inf101.v18.rogue101.items.Backpack; import inf101.v18.rogue101.items.weapon.BasicSword;
import inf101.v18.rogue101.items.Sword; import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.state.AttackType;
import inf101.v18.rogue101.objects.INonPlayer; import inf101.v18.util.NPCHelper;
import inf101.v18.rogue101.shared.NPC;
import inf101.v18.rogue101.states.Attack;
public class Boss implements INonPlayer { /**
private int hp = getMaxHealth(); * A boss enemy that's harder than any other enemy
private final Backpack backpack = new Backpack(); */
private ILocation loc; public class Boss extends Enemy {
/**
* Instantiates a new boss character
*/
public Boss() { public Boss() {
backpack.add(new Sword()); backpack.add(new BasicSword());
} }
@Override @Override
public void doTurn(IGame game) { public void doTurn(Game game) {
loc = game.getLocation(); currentLocation = game.getLocation();
NPC.tryAttack(game, 1, Attack.MELEE); NPCHelper.tryAttack(game, 1, AttackType.MELEE);
} }
@Override @Override
@ -36,23 +36,13 @@ public class Boss implements INonPlayer {
return 15; return 15;
} }
@Override
public IItem getItem(Class<?> type) {
for (IItem item : backpack.getContent()) {
if (type.isInstance(item)) {
return item;
}
}
return null;
}
@Override @Override
public int getCurrentHealth() { public int getCurrentHealth() {
return hp; return hp;
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 40; return 40;
} }
@ -87,21 +77,13 @@ public class Boss implements INonPlayer {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; super.handleDamage(game, source, amount);
if (hp < 0 && backpack.size() > 0) { //Will be useful in a dungeon with several bosses if (hp < 0) {
boolean dropped = false; ((RogueGame) game).win();
for (IItem item : backpack.getContent()) {
if (game.dropAt(loc, item)) {
dropped = true;
} }
} game.getPrinter().printAt(Main.COLUMN_RIGHT_SIDE_START, 19, "Boss HP: " + NPCHelper.hpBar(this));
if (dropped) {
game.displayMessage(getName() + " dropped something");
}
((Game)game).win();
}
game.getPrinter().printAt(Main.COLUMN_RIGHTSIDE_START, 19, "Boss HP: " + NPC.hpBar(this));
return amount; return amount;
} }
} }

View File

@ -0,0 +1,46 @@
package inf101.v18.rogue101.enemies;
import inf101.v18.grid.Location;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.items.container.Backpack;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.object.NonPlayerCharacter;
import inf101.v18.rogue101.state.Sound;
public abstract class Enemy implements NonPlayerCharacter {
protected int hp = getMaxHealth();
protected final Backpack backpack = new Backpack();
protected Location currentLocation;
@Override
public Item getItem(Class<?> type) {
for (Item item : backpack.getContent()) {
if (type.isInstance(item)) {
return item;
}
}
return null;
}
@Override
public int handleDamage(Game game, Item source, int amount) {
hp -= amount;
if (hp < 0) {
Sound.NPC_DEATH.play();
}
if (hp < 0 && backpack.size() > 0) { //Will be useful in a dungeon with several bosses
boolean dropped = false;
for (Item item : backpack.getContent()) {
if (game.dropAt(currentLocation, item)) {
dropped = true;
}
}
if (dropped) {
game.displayMessage(getName() + " dropped something");
}
}
return amount;
}
}

View File

@ -1,89 +1,105 @@
package inf101.v18.rogue101.enemies; package inf101.v18.rogue101.enemies;
import inf101.v18.grid.GridDirection; import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame; import inf101.v18.grid.Location;
import inf101.v18.rogue101.items.*; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.items.buff.BuffItem;
import inf101.v18.rogue101.objects.INonPlayer; import inf101.v18.rogue101.items.weapon.BasicBow;
import inf101.v18.rogue101.shared.NPC; import inf101.v18.rogue101.items.weapon.BasicSword;
import inf101.v18.rogue101.states.Age; import inf101.v18.rogue101.items.weapon.FireStaff;
import inf101.v18.rogue101.states.Attack; import inf101.v18.rogue101.items.weapon.MagicWeapon;
import inf101.v18.rogue101.states.Occupation; import inf101.v18.rogue101.items.weapon.MeleeWeapon;
import inf101.v18.rogue101.states.Personality; import inf101.v18.rogue101.items.weapon.RangedWeapon;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.state.AttackType;
import inf101.v18.rogue101.state.LifeStage;
import inf101.v18.rogue101.state.Occupation;
import inf101.v18.rogue101.state.Personality;
import inf101.v18.util.NPCHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
public class Girl implements INonPlayer { /**
* An enemy NPC
*/
public class Girl extends Enemy {
private final String name = randomName(); private final String name = randomName();
private final Age age = Age.getRandom(); private final LifeStage lifeStage = LifeStage.getRandom();
private final Personality personality = Personality.getRandom(); private final Personality personality = Personality.getRandom();
private final Occupation occupation = Occupation.getRandom(); private final Occupation occupation = Occupation.getRandom();
private int maxhp; private int maxHp;
private int hp;
private int attack; private int attack;
private int defence; private int defence;
private int visibility; private int visibility;
private int damage; private int damage;
private final Backpack backpack = new Backpack(); private List<GridDirection> damageDirections;
private static final Random random = new Random(); private static final Random random = new Random();
private List<Class<?>> validItems; private List<Class<?>> validItems;
private static final String[] namelist = {"Milk", "Salad", "Mikan", "Rima", "Suki", "Miki", "Hinata", "Haruna", "Asuna"}; private static final String[] nameList = {"Milk", "Salad", "Mikan", "Rima", "Suki", "Miki", "Hinata", "Haruna",
"Asuna"};
/**
* Instantiates a new Girl enemy NPC
*/
public Girl() { public Girl() {
setStats(); setStats();
setValidItems(); setValidItems();
} }
private void setStats() { private void setStats() {
switch (age) { // Set stats based on the NPC's age
switch (lifeStage) {
case TODDLER: case TODDLER:
maxhp = 30; maxHp = 30;
attack = 10; attack = 10;
defence = 50; defence = 50;
damage = 1 + random.nextInt(5); damage = 1 + random.nextInt(5);
visibility = 1; visibility = 1;
break; break;
case CHILD: case CHILD:
maxhp = 50; maxHp = 50;
attack = 20; attack = 20;
defence = 40; defence = 40;
damage = 2 + random.nextInt(5); damage = 2 + random.nextInt(5);
visibility = 2; visibility = 2;
break; break;
case TEEN: case TEEN:
maxhp = 70; maxHp = 70;
attack = 25; attack = 25;
defence = 30; defence = 30;
damage = 3 + random.nextInt(5); damage = 3 + random.nextInt(5);
visibility = 3; visibility = 3;
break; break;
case ADULT: case ADULT:
maxhp = 100; maxHp = 100;
attack = 30; attack = 30;
defence = 20; defence = 20;
damage = 4 + random.nextInt(5); damage = 4 + random.nextInt(5);
visibility = 4; visibility = 4;
break; break;
case ELDER: case ELDER:
maxhp = 70; maxHp = 70;
attack = 15; attack = 15;
defence = 35; defence = 35;
damage = 3 + random.nextInt(5); damage = 3 + random.nextInt(5);
visibility = 2; visibility = 2;
break; break;
} }
// Improve stats based on occupation
if (occupation == Occupation.KNIGHT) { if (occupation == Occupation.KNIGHT) {
attack += 10; //Knights are quite powerful. attack += 10; //Knights are quite powerful.
} else if (occupation == Occupation.MAGE) { } else if (occupation == Occupation.MAGE) {
attack += 5; // Mages have lesser range than bowsmen, but more damage. attack += 5; // Mages have lesser range than archers, but more damage.
} }
maxhp += (int)(random.nextGaussian() * 10); // Add random stat improvements
hp = maxhp; maxHp += (int) (random.nextGaussian() * 10);
attack += (int)(random.nextGaussian() * 5); hp = maxHp;
defence += (int)(random.nextGaussian() * 5); attack += (int) (random.nextGaussian() * 5);
defence += (int) (random.nextGaussian() * 5);
} }
/** /**
@ -92,17 +108,20 @@ public class Girl implements INonPlayer {
* @param lvl The current map level * @param lvl The current map level
*/ */
public void giveWeapon(int lvl) { public void giveWeapon(int lvl) {
if (random.nextInt(5) < lvl) { // Randomly grant a weapon based on occupation.
// The chance of being granted a weapon increases for each level
if (random.nextInt(5) >= lvl) {
return;
}
switch (occupation) { switch (occupation) {
case KNIGHT: case KNIGHT:
backpack.add(new Sword()); backpack.add(new BasicSword());
break; break;
case MAGE: case MAGE:
backpack.add(new Staff()); backpack.add(new FireStaff());
break; break;
case BOWSMAN: case ARCHER:
backpack.add(new Bow()); backpack.add(new BasicBow());
}
} }
} }
@ -112,16 +131,16 @@ public class Girl implements INonPlayer {
private void setValidItems() { private void setValidItems() {
validItems = new ArrayList<>(); validItems = new ArrayList<>();
switch (occupation) { switch (occupation) {
case BOWSMAN: case ARCHER:
validItems.add(IRangedWeapon.class); validItems.add(RangedWeapon.class);
break; break;
case MAGE: case MAGE:
validItems.add(IMagicWeapon.class); validItems.add(MagicWeapon.class);
break; break;
case KNIGHT: case KNIGHT:
validItems.add(IMeleeWeapon.class); validItems.add(MeleeWeapon.class);
} }
validItems.add(IBuffItem.class); validItems.add(BuffItem.class);
} }
/** /**
@ -130,30 +149,52 @@ public class Girl implements INonPlayer {
* @return A random name * @return A random name
*/ */
private String randomName() { private String randomName() {
return namelist[random.nextInt(namelist.length)] + "-chan"; return nameList[random.nextInt(nameList.length)] + "-chan";
} }
@Override @Override
public void doTurn(IGame game) { public void doTurn(Game game) {
currentLocation = game.getLocation();
List<GridDirection> damageDirections = this.damageDirections;
this.damageDirections = null;
// If this NPC has space, pick up any items on the ground
if (backpack.hasSpace()) { if (backpack.hasSpace()) {
IItem item = NPC.pickUp(game, validItems); Item item = NPCHelper.pickUp(game, validItems);
if (item != null) { if (item != null) {
backpack.add(item); backpack.add(item);
return; return;
} }
if (NPC.trackItem(game, validItems)) { if (NPCHelper.trackItem(game, validItems)) {
return; return;
} }
} }
if (personality == Personality.AFRAID && NPC.flee(game)) { // TODO: If attacked on the last turn, flee from the attacker if not within sight range
if ((personality == Personality.AFRAID || hp < 0.1 * maxHp)) {
// Flee from the nearest enemy
if (NPCHelper.flee(game)) {
return; return;
} }
// Flee in the direction this NPC was damaged from. This allows the NPC to flee from ranged attacks outside
// visible range.
if (damageDirections != null) {
for (GridDirection direction : damageDirections) {
if (game.canGo(direction)) {
game.move(direction);
return;
}
}
}
}
// Attack if necessary
if (willAttack(game) && attack(game)) { if (willAttack(game) && attack(game)) {
return; return;
} }
// Move a random direction if possible
List<GridDirection> possibleMoves = game.getPossibleMoves(); List<GridDirection> possibleMoves = game.getPossibleMoves();
if (!possibleMoves.isEmpty()) { if (!possibleMoves.isEmpty()) {
Collections.shuffle(possibleMoves); Collections.shuffle(possibleMoves);
@ -167,19 +208,19 @@ public class Girl implements INonPlayer {
* @param game An IGame object * @param game An IGame object
* @return True if an attack occurred. False otherwise * @return True if an attack occurred. False otherwise
*/ */
private boolean attack(IGame game) { private boolean attack(Game game) {
switch (occupation) { switch (occupation) {
case KNIGHT: case KNIGHT:
if (NPC.tryAttack(game, 1, Attack.MELEE)) { if (NPCHelper.tryAttack(game, 1, AttackType.MELEE)) {
return true; return true;
} }
break; break;
case MAGE: case MAGE:
if (NPC.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC)) { if (NPCHelper.tryAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, AttackType.MAGIC)) {
return true; return true;
} }
case BOWSMAN: case ARCHER:
if (NPC.tryAttack(game, getVision(), Attack.RANGED)) { if (NPCHelper.tryAttack(game, getVision(), AttackType.RANGED)) {
return true; return true;
} }
} }
@ -192,20 +233,23 @@ public class Girl implements INonPlayer {
* @param game An IGame object * @param game An IGame object
* @return True if the girl will attack. False otherwise * @return True if the girl will attack. False otherwise
*/ */
private boolean willAttack(IGame game) { private boolean willAttack(Game game) {
boolean attack = false; boolean attack = false;
switch (personality) { switch (personality) {
case CALM: case CALM:
if (hp < maxhp) { // Attack if previously attacked
if (hp < maxHp) {
attack = true; attack = true;
} }
break; break;
case AFRAID: case AFRAID:
// Attack if cornered
if (game.getPossibleMoves().isEmpty()) { if (game.getPossibleMoves().isEmpty()) {
attack = true; attack = true;
} }
break; break;
case AGRESSIVE: case AGGRESSIVE:
// Always attack
attack = true; attack = true;
break; break;
} }
@ -228,13 +272,13 @@ public class Girl implements INonPlayer {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return defence; return defence;
} }
@Override @Override
public int getMaxHealth() { public int getMaxHealth() {
return maxhp; return maxHp;
} }
@Override @Override
@ -263,18 +307,12 @@ public class Girl implements INonPlayer {
} }
@Override @Override
public IItem getItem(Class<?> type) { public int handleDamage(Game game, Item source, int amount) {
for (IItem item : backpack.getContent()) { super.handleDamage(game, source, amount);
if (type.isInstance(item)) { Location location = game.getMap().getLocation(source);
return item; Location location2 = game.getMap().getLocation(this);
} damageDirections = game.locationDirection(location, location2);
}
return null;
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount; return amount;
} }
} }

View File

@ -1,80 +1,37 @@
package inf101.v18.rogue101.events; package inf101.v18.rogue101.events;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
/** /**
* Example implementation of events could be used to have more complex * Example implementation of events could be used to have more complex
* behaviour than just attack/defend/get damaged/pickup/drop. * behaviour than just attack/defend/get damaged/pickup/drop.
* *
* @param <T> Relevant extra data for this particular event
* @author anya * @author anya
*
* @param <T>
* Relevant extra data for this particular event
*/ */
public class GameEvent<T> implements IEvent<T> { public class GameEvent<T> implements IEvent<T> {
public static final String ATTACK_FAILURE = "attackFailure";
public static final String ATTACK_SUCCESS = "attackSuccess";
public static final String DEFEND_FAILURE = "defendFailure";
public static final String DEFEND_SUCCESS = "defendSuccess";
public static final String EATEN = "eaten";
/**
* Create and send events for an attack.
* <p>
* Both attacker and defender will receive appropriate events (ATTACK_* or
* DEFEND_*), depending on who won. The amount of damage is available through
* {@link #getData()}.
* <p>
* Attacker will be sent the "damage" that was actually done (as returned by
* defender's event handler)
* <p>
* Methods such as these could sensible be placed in IGame/Game.
*
* @param success
* True if attack succeeded (attacker "won")
* @param attacker
* @param defender
* @param damage
*/
public static void triggerAttack(boolean success, IItem attacker, IItem defender, int damage) {
if (success) {
Integer result = defender
.handleEvent(new GameEvent<Integer>(DEFEND_FAILURE, null, attacker, defender, damage));
if (result != null)
damage = result;
attacker.handleEvent(new GameEvent<Integer>(ATTACK_SUCCESS, null, attacker, defender, damage));
} else {
attacker.handleEvent(new GameEvent<Integer>(ATTACK_FAILURE, null, attacker, defender, 0));
defender.handleEvent(new GameEvent<Integer>(DEFEND_SUCCESS, null, attacker, defender, 0));
}
}
private final String name; private final String name;
private final IItem source; private final Item source;
private final IItem target; private final Item target;
private T value; private T value;
private final IGame game; private final Game game;
/** /**
* Create a new game event * Create a new game event
* *
* @param name * @param name The name is used when checking which event this is / determine its
* The name is used when checking which event this is / determine its
* meaning * meaning
* @param game * @param game The game, or <code>null</code> if unknown/not relevant
* The game, or <code>null</code> if unknown/not relevant * @param source The item that caused the event, or <code>null</code> if
* @param source
* The item that caused the event, or <code>null</code> if
* unknown/not relevant * unknown/not relevant
* @param target * @param target The item that receives the event, or <code>null</code> if
* The item that receives the event, or <code>null</code> if
* unknown/not relevant * unknown/not relevant
* @param value * @param value Arbitrary extra data
* Arbitrary extra data
*/ */
public GameEvent(String name, IGame game, IItem source, IItem target, T value) { public GameEvent(String name, Game game, Item source, Item target, T value) {
this.name = name; this.name = name;
this.game = game; this.game = game;
this.source = source; this.source = source;
@ -85,30 +42,25 @@ public class GameEvent<T> implements IEvent<T> {
/** /**
* Create a new game event * Create a new game event
* *
* @param name * @param name The name is used when checking which event this is / determine its
* The name is used when checking which event this is / determine its
* meaning * meaning
* @param source * @param source The item that caused the event, or <code>null</code> if
* The item that caused the event, or <code>null</code> if
* unknown/not relevant * unknown/not relevant
*/ */
public GameEvent(String name, IItem source) { public GameEvent(String name, Item source) {
this(name, null, source, null, null); this(name, null, source, null, null);
} }
/** /**
* Create a new game event * Create a new game event
* *
* @param name * @param name The name is used when checking which event this is / determine its
* The name is used when checking which event this is / determine its
* meaning * meaning
* @param source * @param source The item that caused the event, or <code>null</code> if
* The item that caused the event, or <code>null</code> if
* unknown/not relevant * unknown/not relevant
* @param value * @param value Arbitrary extra data
* Arbitrary extra data
*/ */
public GameEvent(String name, IItem source, T value) { public GameEvent(String name, Item source, T value) {
this(name, null, source, null, value); this(name, null, source, null, value);
} }
@ -123,17 +75,17 @@ public class GameEvent<T> implements IEvent<T> {
} }
@Override @Override
public IGame getGame() { public Game getGame() {
return game; return game;
} }
@Override @Override
public IItem getSource() { public Item getSource() {
return source; return source;
} }
@Override @Override
public IItem getTarget() { public Item getTarget() {
return target; return target;
} }

View File

@ -1,7 +1,7 @@
package inf101.v18.rogue101.events; package inf101.v18.rogue101.events;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
/** /**
* An event is something that happens in the game, typically due to an actor * An event is something that happens in the game, typically due to an actor
@ -20,10 +20,8 @@ import inf101.v18.rogue101.objects.IItem;
* <p> * <p>
* This system is fairly simplistic, and you're not expected to make use of it. * This system is fairly simplistic, and you're not expected to make use of it.
* *
* @param <T> Type of the extra data
* @author anya * @author anya
*
* @param <T>
* Type of the extra data
*/ */
public interface IEvent<T> { public interface IEvent<T> {
/** /**
@ -44,7 +42,7 @@ public interface IEvent<T> {
* *
* @return The game associated with this event, or null. * @return The game associated with this event, or null.
*/ */
IGame getGame(); Game getGame();
/** /**
* The source is the item that caused the event * The source is the item that caused the event
@ -53,7 +51,7 @@ public interface IEvent<T> {
* *
* @return The source of this event * @return The source of this event
*/ */
IItem getSource(); Item getSource();
/** /**
* The target is the item that is affected by the event * The target is the item that is affected by the event
@ -62,11 +60,10 @@ public interface IEvent<T> {
* *
* @return The target of this event, or null * @return The target of this event, or null
*/ */
IItem getTarget(); Item getTarget();
/** /**
* @param value * @param value Extra data to store in this event
* Extra data to store in this event
*/ */
void setData(T value); void setData(T value);
} }

View File

@ -1,11 +1,11 @@
package inf101.v18.rogue101.examples; package inf101.v18.rogue101.examples;
import inf101.v18.gfx.gfxmode.ITurtle; import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import javafx.scene.paint.Color; import javafx.scene.paint.Color;
public class Carrot implements IItem { public class Carrot implements Item {
private int hp = getMaxHealth(); private int hp = getMaxHealth();
public void doTurn() { public void doTurn() {
@ -39,7 +39,7 @@ public class Carrot implements IItem {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -69,7 +69,7 @@ public class Carrot implements IItem {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
if (hp < 0) { if (hp < 0) {

View File

@ -1,51 +0,0 @@
package inf101.v18.rogue101.examples;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
public class ExampleItem implements IItem {
private int hp = getMaxHealth();
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 1;
}
@Override
public String getName() {
return "strange model of an item";
}
@Override
public int getSize() {
return 1;
}
@Override
public String getSymbol() {
return "X";
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -1,27 +1,28 @@
package inf101.v18.rogue101.examples; package inf101.v18.rogue101.examples;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.object.NonPlayerCharacter;
import inf101.v18.rogue101.state.AttackType;
import inf101.v18.util.NPCHelper;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import inf101.v18.gfx.gfxmode.ITurtle; public class Rabbit implements NonPlayerCharacter {
import inf101.v18.grid.GridDirection;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.shared.NPC;
import inf101.v18.rogue101.states.Attack;
public class Rabbit implements INonPlayer {
private int food = 0; private int food = 0;
private int hp = getMaxHealth(); private int hp = getMaxHealth();
private static final List<Class<?>> validItems = new ArrayList<>(); private static final List<Class<?>> validItems = new ArrayList<>();
static { static {
validItems.add(Carrot.class); validItems.add(Carrot.class);
} }
@Override @Override
public void doTurn(IGame game) { public void doTurn(Game game) {
if (food == 0) { if (food == 0) {
hp--; hp--;
} else { } else {
@ -31,11 +32,11 @@ public class Rabbit implements INonPlayer {
return; return;
} }
if (NPC.tryAttack(game, 1, Attack.MELEE)) { if (NPCHelper.tryAttack(game, 1, AttackType.MELEE)) {
return; return;
} }
for (IItem item : game.getLocalItems()) { for (Item item : game.getLocalItems()) {
if (item instanceof Carrot) { if (item instanceof Carrot) {
System.out.println("found carrot!"); System.out.println("found carrot!");
int eaten = item.handleDamage(game, this, getDamage()); int eaten = item.handleDamage(game, this, getDamage());
@ -48,7 +49,7 @@ public class Rabbit implements INonPlayer {
} }
} }
if (NPC.trackItem(game, validItems)) { if (NPCHelper.trackItem(game, validItems)) {
return; return;
} }
@ -80,7 +81,7 @@ public class Rabbit implements INonPlayer {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 10; return 10;
} }
@ -105,7 +106,7 @@ public class Rabbit implements INonPlayer {
} }
@Override @Override
public IItem getItem(Class<?> type) { public Item getItem(Class<?> type) {
return null; return null;
} }
@ -115,7 +116,7 @@ public class Rabbit implements INonPlayer {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
return amount; return amount;
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,340 +0,0 @@
package inf101.v18.rogue101.game;
import java.util.List;
import java.util.Random;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.map.IMapView;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.INonPlayer;
import inf101.v18.rogue101.objects.IPlayer;
import inf101.v18.rogue101.states.Attack;
/**
* Game interface
* <p>
* The game has a map and a current {@link IActor} (the player or non-player
* whose turn it currently is). The game also knows the current location of the
* actor. Most methods that deal with the map will use this current location
* they are meant to be used by the current actor for exploring or interacting
* with its surroundings.
* <p>
* In other words, you should avoid calling most of these methods if you're not
* the current actor. You know you're the current actor when you're inside your
* {@link IPlayer#keyPressed()} or {@link INonPlayer#doTurn()} method.
*
* @author anya
*
*/
public interface IGame {
/**
* Add an item to the current location
* <p>
* If the item is an actor, it won't move until the next turn.
*
* @param item
*/
void addItem(IItem item);
/**
* Add a new item (identified by symbol) to the current location
* <p>
* If the item is an actor, it won't move until the next turn.
*
* @param sym
*/
void addItem(String sym);
/**
* Perform an attack on the target.
* <p>
* Will use your {@link IActor#getAttack()} against the target's
* {@link IItem#getDefence()}, and then possiblyu find the damage you're dealing
* with {@link IActor#getDamage()} and inform the target using
* {@link IItem#handleDamage(IGame, IItem, int)}
*
* @param dir
* Direction
* @param target
* A target item, which should be in the neighbouring square in the
* given direction
* @return Your new location if the attack resulted in you moving
*/
ILocation attack(GridDirection dir, IItem target);
/**
* @param dir
* @return True if it's possible to move in the given direction
*/
boolean canGo(GridDirection dir);
/**
* Create a new item based on a text symbol
* <p>
* The item won't be added to the map unless you call {@link #addItem(IItem)}.
*
* @param symbol
* @return The new item
*/
IItem createItem(String symbol);
/**
* Displays a message in the debug area on the screen (bottom line)
*
* @param s
* A message
*/
void displayDebug(String s);
/**
* Displays a message in the message area on the screen (below the map and the
* status line)
*
* @param s
* A message
*/
void displayMessage(String s);
/**
* Displays a status message in the status area on the screen (right below the
* map)
*
* @param s
* A message
*/
void displayStatus(String s);
/**
* Displays a message in the message area on the screen (below the map and the
* status line)
*
* @param s
* A message
* @see String#format(String, Object...)
*/
void formatDebug(String s, Object... args);
/**
* Displays a formatted message in the message area on the screen (below the map
* and the status line)
*
* @param s
* A message
* @see String#format(String, Object...)
*/
void formatMessage(String s, Object... args);
/**
* Displays a formatted status message in the status area on the screen (right
* below the map)
*
* @param s
* A message
* @see String#format(String, Object...)
*/
void formatStatus(String s, Object... args);
/**
* Pick up an item
* <p>
* This should be used by IActors who want to pick up an item and carry it. The
* item will be returned if picking it succeeded (the actor <em>might</em> also
* make a mistake and pick up the wrong item!).
*
* @param item
* An item, should be in the current map location
* @return The item that was picked up (normally <code>item</code>), or
* <code>null</code> if it failed
*/
IItem pickUp(IItem item);
/**
* Drop an item
* <p>
* This should be used by IActors who are carrying an item and want to put it on
* the ground. Check the return value to see if it succeeded.
*
* @param item
* An item, should be carried by the current actor and not already be
* on the map
* @return True if the item was placed on the map, false means you're still
* holding it
*/
boolean drop(IItem item);
/**
* Does the same as drop, but for a specified location.
*
* @param loc The location to drop the location
* @param item The item to drop
* @return True if the item was dropped. False otherwise
*/
boolean dropAt(ILocation loc, IItem item);
/**
* Clear the unused graphics area (you can fill it with whatever you want!)
*/
void clearFreeGraphicsArea();
/**
* Clear the unused text area (you can fill it with whatever you want!)
*/
void clearFreeTextArea();
/**
* Get the bounds of the free graphics area.
* <p>
* You can fill this with whatever you want, using {@link #getPainter()} and
* {@link #clearFreeGraphicsArea()}.
*
* @return Array of coordinates; ([0],[1]) are the top-left corner, and
* ([2],[3]) are the bottom-right corner (exclusive).
*/
double[] getFreeGraphicsAreaBounds();
/**
* Get the bounds of the free text area.
* <p>
* You can fill this with whatever you want, using {@link #getPrinter()} and
* {@link #clearFreeTextArea()}.
* <p>
* You'll probably want to use something like:
*
* <pre>
* int[] bounds = getFreeTextArea();
* int x = bounds[0];
* int y = bounds[1];
* game.getPrinter().printAt(x, y++, "Hello");
* game.getPrinter().printAt(x, y++, "Do you have any carrot cake?", Color.ORANGE);
* </pre>
*
* @return Array of column/line numbers; ([0],[1]) is the top-left corner, and
* ([2],[3]) is the bottom-right corner (inclusive).
*/
int[] getFreeTextAreaBounds();
/**
* See {@link #getFreeGraphicsAreaBounds()}, {@link #clearFreeGraphicsArea()}.
*
* @return A Turtle, for painting graphics
*/
ITurtle getPainter();
/**
* See {@link #getFreeTextAreaBounds()}, {@link #clearFreeTextArea()}.
*
* @return A printer, for printing text
*/
Printer getPrinter();
/**
* @return The height of the map
*/
int getHeight();
/**
* @return A list of the non-actor items at the current map location
*/
List<IItem> getLocalItems();
/**
* Get the current actor's location.
* <p>
* You should only call this from an IActor that is currently doing its move.
*
* @return Location of the current actor
*/
ILocation getLocation();
/**
* Get the current actor
* <p>
* You can check if it's your move by doing game.getActor()==this.
*
* @return The current actor (i.e., the (IPlayer/INonPlayer) player who's turn it currently is)
*/
IActor getActor();
/**
* Get the neighbouring map location in direction <code>dir</code>
* <p>
* Same as <code>getLocation().go(dir)</code>
*
* @param dir
* A direction
* @return A location, or <code>null</code> if the location would be outside the
* map
*/
ILocation getLocation(GridDirection dir);
/**
* @return The map
*/
IMapView getMap();
/**
* @return A list of directions we can move in, for use with
* {@link #move(GridDirection)}
*/
List<GridDirection> getPossibleMoves();
/**
* Get a list of all locations that are visible from the current location.
* <p>
* The location list is sorted so that nearby locations come earlier in the
* list. E.g., if <code>l = getVisible()<code> and <code>i < j</code>then
* <code>getLocation().gridDistanceTo(l.get(i)) < getLocation().gridDistanceTo(l.get(j))</code>
*
* @return A list of grid cells visible from the {@link #getLocation()}
*/
List<ILocation> getVisible();
/**
* @return Width of the map
*/
int getWidth();
/**
* Move the current actor in the given direction.
* <p>
* The new location will be returned.
*
* @param dir
* @return A new location
* @throws IllegalMoveException
* if moving in that direction is illegal
*/
ILocation move(GridDirection dir);
/**
* Perform a ranged attack on the target.
* <p>
* Rules for this are up to you!
*
* @param dir
* Direction
* @param target
* A target item, which should in some square in the given direction
* @return Your new location if the attack resulted in you moving (unlikely)
*/
ILocation rangedAttack(GridDirection dir, IItem target, Attack type);
/**
* @return A random generator
*/
Random getRandom();
/**
* Gets a list of the best directions to go from current to neighbour.
* If the target is positioned diagonally from the start, the direction requiring the most steps is chosen.
*
* @param current The location to go from
* @param neighbour The location to go to
* @return A direction
*/
List<GridDirection> locationDirection(ILocation current, ILocation neighbour);
}

View File

@ -5,7 +5,6 @@ package inf101.v18.rogue101.game;
* for example to a position outside the map. * for example to a position outside the map.
* *
* @author larsjaffke * @author larsjaffke
*
*/ */
public class IllegalMoveException extends RuntimeException { public class IllegalMoveException extends RuntimeException {

View File

@ -0,0 +1,852 @@
package inf101.v18.rogue101.game;
import inf101.v18.gfx.Screen;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.gfxmode.TurtlePainter;
import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.IGrid;
import inf101.v18.grid.Location;
import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.enemies.Boss;
import inf101.v18.rogue101.enemies.Girl;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.examples.Rabbit;
import inf101.v18.rogue101.items.buff.Binoculars;
import inf101.v18.rogue101.items.buff.BuffItem;
import inf101.v18.rogue101.items.buff.Shield;
import inf101.v18.rogue101.items.consumable.HealthPotion;
import inf101.v18.rogue101.items.consumable.Manga;
import inf101.v18.rogue101.items.container.Chest;
import inf101.v18.rogue101.items.container.Static;
import inf101.v18.rogue101.items.weapon.BasicBow;
import inf101.v18.rogue101.items.weapon.BasicSword;
import inf101.v18.rogue101.items.weapon.FireStaff;
import inf101.v18.rogue101.items.weapon.MagicWeapon;
import inf101.v18.rogue101.items.weapon.MeleeWeapon;
import inf101.v18.rogue101.items.weapon.RangedWeapon;
import inf101.v18.rogue101.items.weapon.Weapon;
import inf101.v18.rogue101.map.AGameMap;
import inf101.v18.rogue101.map.GameMap;
import inf101.v18.rogue101.map.MapReader;
import inf101.v18.rogue101.map.MapView;
import inf101.v18.rogue101.object.Actor;
import inf101.v18.rogue101.object.Dust;
import inf101.v18.rogue101.object.FakeWall;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.object.NonPlayerCharacter;
import inf101.v18.rogue101.object.Player;
import inf101.v18.rogue101.object.PlayerCharacter;
import inf101.v18.rogue101.object.StairsDown;
import inf101.v18.rogue101.object.StairsUp;
import inf101.v18.rogue101.object.Wall;
import inf101.v18.rogue101.state.AttackType;
import inf101.v18.rogue101.state.Sound;
import inf101.v18.util.NPCHelper;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyCode;
import javafx.scene.paint.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.function.Supplier;
public class RogueGame implements Game {
/**
* All the IActors that have things left to do this turn
*/
private List<Actor> actors = Collections.synchronizedList(new ArrayList<>());
/**
* For fancy solution to factory problem
*/
private final Map<String, Supplier<Item>> itemFactories = new HashMap<>();
/**
* Useful random generator
*/
private final Random random = new Random();
/**
* Saves the last three messages
*/
private final List<String> lastMessages = new ArrayList<>();
/**
* The game map. {@link GameMap} gives us a few more details than
* {@link MapView} (write access to item lists); the game needs this but
* individual items don't.
*/
private GameMap map;
private final List<GameMap> maps = new ArrayList<>();
private int currentLVL = 0;
private Actor currentActor;
private Location currentLocation;
private int movePoints = 0;
private final ITurtle painter;
private final Printer printer;
private int numberOfPlayers = 0;
private boolean won = false;
private List<Location> visible = null;
public RogueGame(Screen screen, ITurtle painter, Printer printer) {
this.painter = painter;
this.printer = printer;
addFactory();
// NOTE: in a more realistic situation, we will have multiple levels (one map
// per level), and (at least for a Roguelike game) the levels should be
// generated
//
// inputGrid will be filled with single-character strings indicating what (if
// anything)
// should be placed at that map square
loadMap("level1.txt", 1);
loadMap("level2.txt", 2);
loadMap("level3.txt", 3);
loadMap("level4.txt", 4);
loadMap("level5.txt", 5);
map = maps.get(0);
map.add(map.getLocation(3, 3), new Player()); //We must be sure there is never more than one player
// Prints some helpful information.
String[] info = {"Controls:", "WASD or arrow keys for movement", "E to pick up an item", "Q to drop an item", "1-0 to choose an item (10=0)", "N to change name", "ENTER to confirm", "R to use a ranged attack", "F to use a magic attack", "C to use a consumable"};
for (int i = 0; i < info.length; i++) {
this.printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 1 + i, info[i]);
}
}
public RogueGame(String mapString) {
printer = new Printer(1280, 720);
painter = new TurtlePainter(1280, 720);
addFactory();
IGrid<String> inputGrid = MapReader.readString(mapString);
this.map = new AGameMap(inputGrid.getArea());
for (Location loc : inputGrid.locations()) {
Item item = createItem(inputGrid.get(loc));
if (item != null) {
map.add(loc, item);
}
}
}
@Override
public void addItem(Item item) {
map.add(currentLocation, item);
}
@Override
public void addItem(String sym) {
Item item = createItem(sym);
if (item != null) {
map.add(currentLocation, item);
}
}
/**
* Goes up one floor if possible.
*/
public void goUp() {
if (currentLVL + 1 < maps.size()) {
map = maps.get(++currentLVL);
if (!map.has(currentLocation, currentActor)) {
map.add(currentLocation, currentActor);
}
actors = new ArrayList<>();
displayMessage("You went up the stairs");
}
}
/**
* Goes down one floor if possible.
*/
public void goDown() {
if (currentLVL > 0) {
map = maps.get(--currentLVL);
if (!map.has(currentLocation, currentActor)) {
map.add(currentLocation, currentActor);
}
actors = new ArrayList<>();
displayMessage("You went down the stairs");
}
}
/**
* Calculates the attack of an IActor based on equipped items.
*
* @return The attack
*/
private int getAttack(AttackType type) {
int attack = currentActor.getAttack() + random.nextInt(20) + 1;
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
if (weapon != null) {
attack += weapon.getWeaponAttack();
}
return attack;
}
/**
* Gets the defence of the current target.
*
* @param target The target to evaluate
* @return The defence of the target
*/
private int getDefence(Item target) {
int defence = target.getDefense() + 10;
Actor actor = (Actor) target;
BuffItem item = (BuffItem) actor.getItem(BuffItem.class);
if (item != null) {
defence += item.getBuffDefene();
}
return defence;
}
/**
* Gets the damage done against the current target.
*
* @param target The target to evaluate.
* @return The damage done to the target.
*/
private int getDamage(Item target, AttackType type) {
int damage = currentActor.getDamage();
Weapon weapon = NPCHelper.getWeapon(type, currentActor);
if (weapon != null) {
damage += weapon.getWeaponDamage();
}
BuffItem buff = (BuffItem) currentActor.getItem(BuffItem.class);
if (buff != null) {
damage += buff.getBuffDamage();
}
BuffItem item = (BuffItem) ((Actor) target).getItem(BuffItem.class);
if (item != null) {
damage -= item.getBuffDamageReduction();
}
return damage;
}
@Override
public Location attack(GridDirection dir, Item target) {
Location loc = currentLocation.go(dir);
if (!map.has(loc, target)) {
throw new IllegalMoveException("Target isn't there!");
}
Weapon weapon = (Weapon) currentActor.getItem(MeleeWeapon.class);
if (weapon != null) {
weapon.getSound().play();
} else {
Sound.MELEE_NO_WEAPON.play();
}
if (getAttack(AttackType.MELEE) >= getDefence(target)) {
int actualDamage = target.handleDamage(this, target, getDamage(target, AttackType.MELEE));
if (currentActor != null) {
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
}
} else {
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
}
map.clean(loc);
if (target.isDestroyed() && currentLocation != null) {
return move(dir);
} else {
movePoints--;
return currentLocation;
}
}
@Override
public Location rangedAttack(GridDirection dir, Item target, AttackType type) {
Location loc = currentLocation;
Weapon weapon = null;
switch (type) {
case MAGIC:
weapon = (Weapon) currentActor.getItem(MagicWeapon.class);
break;
case RANGED:
weapon = (Weapon) currentActor.getItem(RangedWeapon.class);
}
if (weapon != null) {
weapon.getSound().play();
} else {
Sound.RANGED_NO_WEAPON.play();
}
if (getAttack(type) >= getDefence(target)) {
int damage = getDamage(target, type) / loc.gridDistanceTo(map.getLocation(target));
int actualDamage = target.handleDamage(this, target, damage);
formatMessage("%s hits %s for %d damage", currentActor.getName(), target.getName(), actualDamage);
} else {
formatMessage("%s tried to hit %s, but missed", currentActor.getName(), target.getName());
}
map.clean(map.getLocation(target));
if (target.isDestroyed() && map.has(currentLocation.go(dir), target)) {
return move(dir);
} else {
return currentLocation;
}
}
/**
* Begin a new game turn, or continue to the previous turn
*
* @return True if the game should wait for more user input
*/
public boolean doTurn() {
do {
if (actors.isEmpty()) {
// System.err.println("new turn!");
// no one in the queue, we're starting a new turn!
// first collect all the actors:
beginTurn();
}
/*if (random.nextInt(100) < 20) {
ILocation loc = map.getLocation(random.nextInt(map.getWidth()), random.nextInt(map.getHeight()));
if (!map.hasActors(loc) && !map.hasItems(loc) && !map.hasWall(loc)) {
map.add(loc, new Carrot());
}
}*/ //We don't want this in the actual game.
// process actors one by one; for the IPlayer, we return and wait for keypresses
// Possible for INonPlayer, we could also return early (returning
// *false*), and then insert a little timer delay between each non-player move
// (the timer
// is already set up in Main)
if (numberOfPlayers == 0 && !won) {
kill();
}
while (!actors.isEmpty()) {
// get the next player or non-player in the queue
currentActor = actors.remove(0);
if (currentActor == null) {
return false; //TODO: Find out why a girl suddenly becomes null (only after map change, not caught in beginTurn, happens randomly)
}
// skip if it's dead
if (currentActor.isDestroyed()) {
continue;
}
currentLocation = map.getLocation(currentActor);
if (currentLocation == null) {
displayDebug("doTurn(): Whoops! Actor has disappeared from the map: " + currentActor);
}
movePoints = 1; // everyone gets to do one thing
visible = null;
if (currentActor instanceof NonPlayerCharacter) {
// computer-controlled players do their stuff right away
((NonPlayerCharacter) currentActor).doTurn(this);
// remove any dead items from current location
map.clean(currentLocation);
return false;
} else if (currentActor instanceof PlayerCharacter) {
if (!currentActor.isDestroyed()) {
// For the human player, we need to wait for input, so we just return.
// Further keypresses will cause keyPressed() to be called, and once the human
// makes a move, it'll lose its movement point and doTurn() will be called again
//
// NOTE: currentActor and currentLocation are set to the IPlayer (above),
// so the game remembers who the player is whenever new keypresses occur. This
// is also how e.g., getLocalItems() work the game always keeps track of
// whose turn it is.
return true;
}
} else {
displayDebug("doTurn(): Hmm, this is a very strange actor: " + currentActor);
}
}
} while (numberOfPlayers > 0); // we can safely repeat if we have players, since we'll return (and break out of
// the loop) once we hit the player
return true;
}
/**
* Player is dead. It needs to be properly killed off.
*/
private void kill() {
map.remove(currentLocation, currentActor);
currentActor = null;
currentLocation = null;
actors = new ArrayList<>();
loadMap("gameover.txt");
Sound.GAME_OVER.play();
}
public void win() { //Trigger when the boss dies
map.remove(currentLocation, currentActor);
currentActor = null;
currentLocation = null;
actors = new ArrayList<>();
loadMap("victory.txt");
map.draw(painter, printer);
Sound.WIN.play();
won = true;
}
/**
* Loads a map with the desired name
*
* @param mapName Name of map, including extension.
*/
private void loadMap(String mapName) {
IGrid<String> inputGrid = MapReader.readFile("maps/" + mapName);
if (inputGrid == null) {
System.err.println("Map not found falling back to builtin map");
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
}
GameMap map = new AGameMap(inputGrid.getArea());
for (Location loc : inputGrid.locations()) {
Item item = createItem(inputGrid.get(loc));
if (item != null) {
map.add(loc, item);
}
}
this.map = map;
}
private void loadMap(String mapName, int lvl) {
IGrid<String> inputGrid = MapReader.readFile("maps/" + mapName);
if (inputGrid == null) {
System.err.println("Map not found falling back to builtin map");
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
}
GameMap map = new AGameMap(inputGrid.getArea());
for (Location loc : inputGrid.locations()) {
Item item = createItem(inputGrid.get(loc));
if (item instanceof Chest) {
((Chest) item).fill(lvl);
} else if (item instanceof Girl) {
((Girl) item).giveWeapon(lvl);
}
if (item != null) {
map.add(loc, item);
}
}
maps.add(map);
}
/**
* Go through the map and collect all the actors.
*/
private void beginTurn() {
numberOfPlayers = 0;
// this extra fancy iteration over each map location runs *in parallel* on
// multicore systems!
// that makes some things more tricky, hence the "synchronized" block and
// "Collections.synchronizedList()" in the initialization of "actors".
// NOTE: If you want to modify this yourself, it might be a good idea to replace
// "parallelStream()" by "stream()", because weird things can happen when many
// things happen
// at the same time! (or do INF214 or DAT103 to learn about locks and threading)
map.getArea().parallelStream().forEach((loc) -> { // will do this for each location in map
List<Item> list = map.getAllModifiable(loc); // all items at loc
Iterator<Item> li = list.iterator(); // manual iterator lets us remove() items
while (li.hasNext()) { // this is what "for(IItem item : list)" looks like on the inside
Item item = li.next();
if (item.getCurrentHealth() < 0) {
// normally, we expect these things to be removed when they are destroyed, so
// this shouldn't happen
synchronized (this) {
formatDebug("beginTurn(): found and removed leftover destroyed item %s '%s' at %s%n",
item.getName(), item.getSymbol(), loc);
}
li.remove();
map.remove(loc, item); // need to do this too, to update item map
} else if (item instanceof PlayerCharacter) {
actors.add(0, (Actor) item); // we let the human player go first
synchronized (this) {
numberOfPlayers++;
}
} else if (item instanceof Actor) {
actors.add((Actor) item); // add other actors to the end of the list
} else if (item instanceof Carrot) {
((Carrot) item).doTurn();
}
}
});
}
@Override
public boolean canGo(GridDirection dir) {
return map.canGo(currentLocation, dir);
}
private void addFactory() {
itemFactories.put("#", Wall::new);
itemFactories.put("@", Player::new);
itemFactories.put("C", Carrot::new);
itemFactories.put("R", Rabbit::new);
itemFactories.put("M", Manga::new);
itemFactories.put("G", Girl::new);
itemFactories.put(".", Dust::new);
itemFactories.put("S", BasicSword::new);
itemFactories.put("c", Chest::new);
itemFactories.put("B", Boss::new);
itemFactories.put("s", FireStaff::new);
itemFactories.put("b", BasicBow::new);
itemFactories.put("H", HealthPotion::new);
itemFactories.put("=", FakeWall::new);
itemFactories.put("<", StairsUp::new);
itemFactories.put(">", StairsDown::new);
itemFactories.put("m", Binoculars::new);
itemFactories.put("f", Shield::new);
}
@Override
public Item createItem(String symbol) {
if (" ".equals(symbol)) {
return null;
}// alternative/advanced method
Supplier<Item> factory = itemFactories.get(symbol);
if (factory != null) {
return factory.get();
} else {
System.err.println("createItem: Don't know how to create a '" + symbol + "'");
return null;
}
}
@Override
public void displayDebug(String s) {
printer.clearLine(Main.LINE_DEBUG);
printer.printAt(1, Main.LINE_DEBUG, s, Color.DARKRED);
System.err.println(s);
}
@Override
public void displayMessage(String s) {
if (lastMessages.size() >= 3) {
lastMessages.remove(2);
}
lastMessages.add(0, s);
printer.clearLine(Main.LINE_MSG1);
printer.clearLine(Main.LINE_MSG2);
printer.clearLine(Main.LINE_MSG3);
int maxLen = 80; //The maximum length of a message to not overflow.
boolean secondLineWritten = false;
boolean thirdLineWritten = false;
//Makes long messages overflow to the next line.
if (lastMessages.size() > 0) {
String message = lastMessages.get(0);
if (message.length() > 2 * maxLen) {
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen, 2 * maxLen));
printer.printAt(1, Main.LINE_MSG3, message.substring(2 * maxLen));
secondLineWritten = thirdLineWritten = true;
} else if (message.length() > maxLen) {
printer.printAt(1, Main.LINE_MSG1, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG2, message.substring(maxLen));
secondLineWritten = true;
} else {
printer.printAt(1, Main.LINE_MSG1, message);
}
}
if (lastMessages.size() > 1 && !secondLineWritten) {
String message = lastMessages.get(1);
if (message.length() > maxLen) {
printer.printAt(1, Main.LINE_MSG2, message.substring(0, maxLen));
printer.printAt(1, Main.LINE_MSG3, message.substring(maxLen));
thirdLineWritten = true;
} else {
printer.printAt(1, Main.LINE_MSG2, message);
}
}
if (lastMessages.size() > 2 && !thirdLineWritten) {
printer.printAt(1, Main.LINE_MSG3, lastMessages.get(2));
}
//System.out.println("Message: «" + s + "»");
}
@Override
public void displayStatus(String s) {
printer.clearLine(Main.LINE_STATUS);
printer.printAt(1, Main.LINE_STATUS, s);
//System.out.println("Status: «" + s + "»");
}
public void draw() {
if (numberOfPlayers == 0) {
map.draw(painter, printer);
} else {
((AGameMap) map).drawVisible(painter, printer);
}
}
@Override
public boolean drop(Item item) {
if (item != null) {
map.add(currentLocation, item);
return true;
} else {
return false;
}
}
@Override
public boolean dropAt(Location loc, Item item) {
if (item != null) {
map.add(loc, item);
return true;
} else {
return false;
}
}
@Override
public void formatDebug(String s, Object... args) {
displayDebug(String.format(s, args));
}
@Override
public void formatMessage(String s, Object... args) {
displayMessage(String.format(s, args));
}
@Override
public void formatStatus(String s, Object... args) {
displayStatus(String.format(s, args));
}
@Override
public int getHeight() {
return map.getHeight();
}
@Override
public List<Item> getLocalItems() {
return map.getItems(currentLocation);
}
@Override
public Location getLocation() {
return currentLocation;
}
@Override
public Location getLocation(GridDirection dir) {
if (currentLocation.canGo(dir)) {
return currentLocation.go(dir);
} else {
return null;
}
}
/**
* Return the game map. {@link GameMap} gives us a few more details than
* {@link MapView} (write access to item lists); the game needs this but
* individual items don't.
*/
@Override
public MapView getMap() {
return map;
}
@Override
public List<GridDirection> getPossibleMoves() {
List<GridDirection> moves = new ArrayList<>();
for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) {
if (canGo(dir)) {
moves.add(dir);
}
}
return moves;
}
@Override
public List<Location> getVisible() {
if (this.visible != null) {
return this.visible;
}
List<Location> neighbours = this.map.getNeighbourhood(this.currentLocation, this.currentActor.getVision());
List<Location> invalid = new ArrayList<>();
for (Location neighbour : neighbours) {
for (Location tile : this.currentLocation.gridLineTo(neighbour)) {
if (this.map.hasWall(tile)) {
invalid.add(neighbour);
break;
}
}
}
neighbours.removeAll(invalid);
this.visible = neighbours;
return neighbours;
}
@Override
public int getWidth() {
return this.map.getWidth();
}
public boolean keyPressed(KeyCode code) {
// only an IPlayer/human can handle keypresses, and only if it's the human's
// turn
return !(currentActor instanceof PlayerCharacter) || !((PlayerCharacter) currentActor).keyPressed(this, code);
}
@Override
public Location move(GridDirection dir) {
if (movePoints < 1) {
throw new IllegalMoveException("You're out of moves!");
}
Location newLoc = map.go(currentLocation, dir);
map.remove(currentLocation, currentActor);
map.add(newLoc, currentActor);
currentLocation = newLoc;
movePoints--;
return currentLocation;
}
@Override
public Item pickUp(Item item) {
if (item != null && map.has(currentLocation, item) && !(item instanceof Static)) {
if (item instanceof Actor) {
if (item.getCurrentHealth() / item.getMaxHealth() < 3) {
map.remove(currentLocation, item);
return item;
} else {
return null;
}
} else if (currentActor.getAttack() > item.getDefense()) {
map.remove(currentLocation, item);
return item;
} else {
return null;
}
} else {
return null;
}
}
@Override
public ITurtle getPainter() {
return painter;
}
@Override
public Printer getPrinter() {
return printer;
}
@Override
public int[] getFreeTextAreaBounds() {
int[] area = new int[4];
area[0] = getWidth() + 1;
area[1] = 1;
area[2] = printer.getLineWidth();
area[3] = printer.getPageHeight() - 5;
return area;
}
@Override
public void clearFreeTextArea() {
printer.clearRegion(getWidth() + 1, 1, printer.getLineWidth() - getWidth(), printer.getPageHeight() - 5);
}
@Override
public void clearFreeGraphicsArea() {
painter.as(GraphicsContext.class).clearRect(getWidth() * printer.getCharWidth(), 0,
painter.getWidth() - getWidth() * printer.getCharWidth(),
(printer.getPageHeight() - 5) * printer.getCharHeight());
}
@Override
public double[] getFreeGraphicsAreaBounds() {
double[] area = new double[4];
area[0] = getWidth() * printer.getCharWidth();
area[1] = 0;
area[2] = painter.getWidth();
area[3] = getHeight() * printer.getCharHeight();
return area;
}
@Override
public Actor getActor() {
return currentActor;
}
public Location setCurrent(Actor actor) {
currentLocation = map.getLocation(actor);
if (currentLocation != null) {
currentActor = actor;
movePoints = 1;
}
return currentLocation;
}
public Actor setCurrent(Location loc) {
List<Actor> list = map.getActors(loc);
if (!list.isEmpty()) {
currentActor = list.get(0);
currentLocation = loc;
movePoints = 1;
}
return currentActor;
}
public Actor setCurrent(int x, int y) {
return setCurrent(map.getLocation(x, y));
}
@Override
public Random getRandom() {
return random;
}
@Override
public List<GridDirection> locationDirection(Location start, Location target) {
int targetX = target.getX(), targetY = target.getY();
int startX = start.getX(), startY = start.getY();
List<GridDirection> dirs = new ArrayList<>();
boolean yDifferenceIsLarger = Math.abs(targetX - startX) < Math.abs(targetY - startY);
if (targetX > startX && targetY > startY) {
if (yDifferenceIsLarger) {
dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.EAST);
} else {
dirs.add(GridDirection.EAST);
dirs.add(GridDirection.SOUTH);
}
} else if (targetX > startX && targetY < startY) {
if (yDifferenceIsLarger) {
dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.EAST);
} else {
dirs.add(GridDirection.EAST);
dirs.add(GridDirection.NORTH);
}
} else if (targetX < startX && targetY > startY) {
if (yDifferenceIsLarger) {
dirs.add(GridDirection.SOUTH);
dirs.add(GridDirection.WEST);
} else {
dirs.add(GridDirection.WEST);
dirs.add(GridDirection.SOUTH);
}
} else if (targetX < startX && targetY < startY) {
if (yDifferenceIsLarger) {
dirs.add(GridDirection.NORTH);
dirs.add(GridDirection.WEST);
} else {
dirs.add(GridDirection.WEST);
dirs.add(GridDirection.NORTH);
}
} else if (targetX > startX) {
dirs.add(GridDirection.EAST);
} else if (targetX < startX) {
dirs.add(GridDirection.WEST);
} else if (targetY > startY) {
dirs.add(GridDirection.SOUTH);
} else if (targetY < startY) {
dirs.add(GridDirection.NORTH);
}
return dirs;
}
}

View File

@ -1,9 +0,0 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
public interface IConsumable extends IItem {
int hpIncrease();
int attackIncrease();
int defenceIncrease();
}

View File

@ -1,12 +0,0 @@
package inf101.v18.rogue101.items;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IMagicWeapon extends IWeapon {
@Override
default String getSound() {
return "audio/Large Fireball-SoundBible.com-301502490.wav";
}
}

View File

@ -1,12 +0,0 @@
package inf101.v18.rogue101.items;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IMeleeWeapon extends IWeapon {
@Override
default String getSound() {
return "audio/Sword Swing-SoundBible.com-639083727.wav";
}
}

View File

@ -1,12 +0,0 @@
package inf101.v18.rogue101.items;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface IRangedWeapon extends IWeapon {
@Override
default String getSound() {
return "audio/Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav";
}
}

View File

@ -1,15 +0,0 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
public interface IStatic extends IItem {
@Override
default int getDefence() {
return 0;
}
@Override
default int getSize() {
return 10000;
}
}

View File

@ -1,26 +0,0 @@
package inf101.v18.rogue101.items;
import inf101.v18.rogue101.objects.IItem;
public interface IWeapon extends IItem {
/**
* Retrieves the damage points of a weapon.
*
* @return An int
*/
int getWeaponDamage();
/**
* Returns the attack ponts of a weapon.
*
* @return an int
*/
int getWeaponAttack();
/**
* Retrieves the string path of the sound to play upon an attack.
*
* @return A string path
*/
String getSound();
}

View File

@ -1,9 +1,9 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.buff;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
public class Binoculars implements IBuffItem { public class Binoculars implements BuffItem {
@Override @Override
public int getBuffVisibility() { public int getBuffVisibility() {
@ -16,7 +16,7 @@ public class Binoculars implements IBuffItem {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -46,7 +46,7 @@ public class Binoculars implements IBuffItem {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -1,8 +1,12 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.buff;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
/**
* An item that increases one or more of a player's stats
*/
public interface BuffItem extends Item {
public interface IBuffItem extends IItem {
/** /**
* Retrieve damage increase done by the buff item. * Retrieve damage increase done by the buff item.
* *
@ -17,7 +21,7 @@ public interface IBuffItem extends IItem {
* *
* @return An int, May be 0 * @return An int, May be 0
*/ */
default int getBuffDefence() { default int getBuffDefene() {
return 0; return 0;
} }
@ -38,4 +42,5 @@ public interface IBuffItem extends IItem {
default int getBuffVisibility() { default int getBuffVisibility() {
return 0; return 0;
} }
} }

View File

@ -1,13 +1,13 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.buff;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
public class Shield implements IBuffItem { public class Shield implements BuffItem {
private final int hp = getMaxHealth(); private final int hp = getMaxHealth();
@Override @Override
public int getBuffDefence() { public int getBuffDefene() {
return 10; return 10;
} }
@ -22,7 +22,7 @@ public class Shield implements IBuffItem {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -52,7 +52,7 @@ public class Shield implements IBuffItem {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -0,0 +1,11 @@
package inf101.v18.rogue101.items.consumable;
import inf101.v18.rogue101.object.Item;
public interface Consumable extends Item {
int hpIncrease();
int attackIncrease();
int defenseIncrease();
}

View File

@ -1,9 +1,13 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.consumable;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
/**
* A consumable that restores 100 health points
*/
public class HealthPotion implements Consumable {
public class HealthPotion implements IConsumable {
@Override @Override
public int hpIncrease() { public int hpIncrease() {
return 100; return 100;
@ -15,7 +19,7 @@ public class HealthPotion implements IConsumable {
} }
@Override @Override
public int defenceIncrease() { public int defenseIncrease() {
return 0; return 0;
} }
@ -25,7 +29,7 @@ public class HealthPotion implements IConsumable {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -50,7 +54,8 @@ public class HealthPotion implements IConsumable {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -1,9 +1,13 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.consumable;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
/**
* A consumable that increases attack and defense permanently
*/
public class Manga implements Consumable {
public class Manga implements IConsumable {
private int hp = getMaxHealth(); private int hp = getMaxHealth();
@Override @Override
@ -12,7 +16,7 @@ public class Manga implements IConsumable {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -42,7 +46,7 @@ public class Manga implements IConsumable {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
return amount; return amount;
} }
@ -58,7 +62,8 @@ public class Manga implements IConsumable {
} }
@Override @Override
public int defenceIncrease() { public int defenseIncrease() {
return 5; return 5;
} }
} }

View File

@ -1,17 +1,17 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.container;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class Backpack implements IContainer { public class Backpack implements Container {
/** /**
* A list containing everything in the backpack. * A list containing everything in the backpack.
*/ */
private final List<IItem> content = new ArrayList<>(); private final List<Item> content = new ArrayList<>();
/** /**
* The maximum amount of items allowed in a single backpack. * The maximum amount of items allowed in a single backpack.
*/ */
@ -23,7 +23,7 @@ public class Backpack implements IContainer {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -48,7 +48,7 @@ public class Backpack implements IContainer {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
@ -76,7 +76,7 @@ public class Backpack implements IContainer {
* @param item The item to add * @param item The item to add
* @return True if the item was added. False if the backpack is full * @return True if the item was added. False if the backpack is full
*/ */
public boolean add(IItem item) { public boolean add(Item item) {
if (size() < MAX_SIZE) { if (size() < MAX_SIZE) {
content.add(item); content.add(item);
return true; return true;
@ -95,7 +95,7 @@ public class Backpack implements IContainer {
* *
* @param item The item to remove * @param item The item to remove
*/ */
public void remove(IItem item) { public void remove(Item item) {
content.remove(item); content.remove(item);
} }
@ -105,12 +105,12 @@ public class Backpack implements IContainer {
* @param i The index of an element * @param i The index of an element
* @return An object of type T * @return An object of type T
*/ */
public IItem get(int i) { public Item get(int i) {
return content.get(i); return content.get(i);
} }
@Override @Override
public boolean addItem(IItem item) { public boolean addItem(Item item) {
if (content.size() < MAX_SIZE) { if (content.size() < MAX_SIZE) {
content.add(item); content.add(item);
return true; return true;
@ -124,7 +124,7 @@ public class Backpack implements IContainer {
* *
* @return A list of T * @return A list of T
*/ */
public List<IItem> getContent() { public List<Item> getContent() {
return Collections.unmodifiableList(content); return Collections.unmodifiableList(content);
} }
@ -134,8 +134,8 @@ public class Backpack implements IContainer {
} }
@Override @Override
public IItem getFirst(Class<?> clazz) { public Item getFirst(Class<?> clazz) {
for (IItem item : content) { for (Item item : content) {
if (clazz.isInstance(item)) { if (clazz.isInstance(item)) {
return item; return item;
} }

View File

@ -1,15 +1,22 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.container;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.items.buff.Binoculars;
import inf101.v18.rogue101.items.buff.Shield;
import inf101.v18.rogue101.items.consumable.HealthPotion;
import inf101.v18.rogue101.items.consumable.Manga;
import inf101.v18.rogue101.items.weapon.BasicBow;
import inf101.v18.rogue101.items.weapon.BasicSword;
import inf101.v18.rogue101.items.weapon.FireStaff;
import inf101.v18.rogue101.object.Item;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
public class Chest implements IContainer, IStatic { public class Chest implements Container, Static {
private final List<IItem> container; private final List<Item> container;
private final int MAX_SIZE = 10; private final int MAX_SIZE = 10;
public Chest() { public Chest() {
@ -21,13 +28,13 @@ public class Chest implements IContainer, IStatic {
* *
* @param lvl The current dungeon level * @param lvl The current dungeon level
*/ */
public void fill (int lvl) { public void fill(int lvl) {
Random random = new Random(); Random random = new Random();
int itemChance = 3; int itemChance = 3;
List<IItem> items = new ArrayList<>(); List<Item> items = new ArrayList<>();
items.add(new Staff()); items.add(new FireStaff());
items.add(new Sword()); items.add(new BasicSword());
items.add(new Bow()); items.add(new BasicBow());
items.add(new Binoculars()); items.add(new Binoculars());
items.add(new Shield()); items.add(new Shield());
items.add(new Manga()); items.add(new Manga());
@ -49,12 +56,12 @@ public class Chest implements IContainer, IStatic {
} }
@Override @Override
public IItem get(int i) { public Item get(int i) {
return container.get(i); return container.get(i);
} }
@Override @Override
public List<IItem> getContent() { public List<Item> getContent() {
return Collections.unmodifiableList(container); return Collections.unmodifiableList(container);
} }
@ -64,8 +71,8 @@ public class Chest implements IContainer, IStatic {
} }
@Override @Override
public IItem getFirst(Class<?> clazz) { public Item getFirst(Class<?> clazz) {
for (IItem item : container) { for (Item item : container) {
if (clazz.isInstance(item)) { if (clazz.isInstance(item)) {
return item; return item;
} }
@ -103,12 +110,12 @@ public class Chest implements IContainer, IStatic {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
@Override @Override
public boolean addItem(IItem item) { public boolean addItem(Item item) {
if (container.size() < MAX_SIZE) { if (container.size() < MAX_SIZE) {
container.add(item); container.add(item);
return true; return true;

View File

@ -1,10 +1,10 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.container;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import java.util.List; import java.util.List;
public interface IContainer extends IItem { public interface Container extends Item {
/** /**
* Retrieves an item from a container in index i * Retrieves an item from a container in index i
* *
@ -12,22 +12,22 @@ public interface IContainer extends IItem {
* @return An IItem * @return An IItem
* @throws IndexOutOfBoundsException If the index is out of range. * @throws IndexOutOfBoundsException If the index is out of range.
*/ */
IItem get(int i); Item get(int i);
/** /**
* Adds an item to a container. * Adds an item to a container.
* *
* @return True if the container was not full
* @param item The item to add to the container * @param item The item to add to the container
* @return True if the container was not full
*/ */
boolean addItem(IItem item); boolean addItem(Item item);
/** /**
* Gets a list with everything inside a container. * Gets a list with everything inside a container.
* *
* @return A list of Objects extending IItem * @return A list of Objects extending IItem
*/ */
List<IItem> getContent(); List<Item> getContent();
/** /**
* Checks if we can add anything at all to the container. * Checks if we can add anything at all to the container.
@ -51,7 +51,7 @@ public interface IContainer extends IItem {
* @param clazz The class type to accept * @param clazz The class type to accept
* @return An IItem or null * @return An IItem or null
*/ */
IItem getFirst(Class<?> clazz); Item getFirst(Class<?> clazz);
/** /**
* Removes an element at index i from the container. * Removes an element at index i from the container.

View File

@ -0,0 +1,20 @@
package inf101.v18.rogue101.items.container;
import inf101.v18.rogue101.object.Item;
/**
* A static item that cannot be kept in a player's inventory
*/
public interface Static extends Item {
@Override
default int getDefense() {
return 0;
}
@Override
default int getSize() {
return 10000;
}
}

View File

@ -1,11 +1,11 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import java.util.Random; import java.util.Random;
public class Bow implements IRangedWeapon { public class BasicBow implements RangedWeapon {
private static final Random random = new Random(); private static final Random random = new Random();
private final int damage = 3 + random.nextInt(20); private final int damage = 3 + random.nextInt(20);
private final int hp = getMaxHealth(); private final int hp = getMaxHealth();
@ -26,7 +26,7 @@ public class Bow implements IRangedWeapon {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -56,7 +56,7 @@ public class Bow implements IRangedWeapon {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -1,11 +1,11 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import java.util.Random; import java.util.Random;
public class Sword implements IMeleeWeapon { public class BasicSword implements MeleeWeapon {
private static final Random random = new Random(); private static final Random random = new Random();
private final int damage = 5 + random.nextInt(25); private final int damage = 5 + random.nextInt(25);
private int hp = getMaxHealth(); private int hp = getMaxHealth();
@ -26,7 +26,7 @@ public class Sword implements IMeleeWeapon {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -56,7 +56,7 @@ public class Sword implements IMeleeWeapon {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
return amount; return amount;
} }

View File

@ -1,11 +1,11 @@
package inf101.v18.rogue101.items; package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.objects.IItem; import inf101.v18.rogue101.object.Item;
import java.util.Random; import java.util.Random;
public class Staff implements IMagicWeapon { public class FireStaff implements MagicWeapon {
private static final Random random = new Random(); private static final Random random = new Random();
private final int damage = 5 + random.nextInt(25); private final int damage = 5 + random.nextInt(25);
private int hp = getMaxHealth(); private int hp = getMaxHealth();
@ -26,7 +26,7 @@ public class Staff implements IMagicWeapon {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -56,7 +56,7 @@ public class Staff implements IMagicWeapon {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
return amount; return amount;
} }

View File

@ -0,0 +1,15 @@
package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.state.Sound;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface MagicWeapon extends Weapon {
@Override
default Sound getSound() {
return Sound.RANGED_STAFF;
}
}

View File

@ -0,0 +1,15 @@
package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.state.Sound;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface MeleeWeapon extends Weapon {
@Override
default Sound getSound() {
return Sound.MELEE_SWORD;
}
}

View File

@ -0,0 +1,14 @@
package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.state.Sound;
/**
* An interface to extinguish different weapons for specific NPC.
*/
public interface RangedWeapon extends Weapon {
@Override
default Sound getSound() {
return Sound.RANGED_BOW;
}
}

View File

@ -0,0 +1,29 @@
package inf101.v18.rogue101.items.weapon;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.state.Sound;
public interface Weapon extends Item {
/**
* Retrieves the damage points of a weapon.
*
* @return An int
*/
int getWeaponDamage();
/**
* Returns the attack points of a weapon
*
* @return <p>The attack points as an integer</p>
*/
int getWeaponAttack();
/**
* Retrieves the sound to play upon an attack
*
* @return <p>The sound to use</p>
*/
Sound getSound();
}

View File

@ -0,0 +1,446 @@
package inf101.v18.rogue101.map;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.IArea;
import inf101.v18.grid.IMultiGrid;
import inf101.v18.grid.Location;
import inf101.v18.grid.MultiGrid;
import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.items.buff.Shield;
import inf101.v18.rogue101.items.consumable.Manga;
import inf101.v18.rogue101.items.container.Backpack;
import inf101.v18.rogue101.items.container.Chest;
import inf101.v18.rogue101.items.weapon.BasicBow;
import inf101.v18.rogue101.items.weapon.BasicSword;
import inf101.v18.rogue101.items.weapon.FireStaff;
import inf101.v18.rogue101.object.Actor;
import inf101.v18.rogue101.object.FakeWall;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.object.PlayerCharacter;
import inf101.v18.rogue101.object.Wall;
import javafx.scene.canvas.GraphicsContext;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
public class AGameMap implements GameMap {
/**
* The grid that makes up our map
*/
private final IMultiGrid<Item> grid;
/**
* These locations have changed, and need to be redrawn
*/
private final Set<Location> dirtyLocs = new HashSet<>();
/**
* An index of all the items in the map and their locations.
*/
// an IdentityHashMap uses object identity as a lookup key, so items are
// considered equal if they are the same object (a == b)
private final Map<Item, Location> items = new IdentityHashMap<>();
public AGameMap(IArea area) {
grid = new MultiGrid<>(area);
}
public AGameMap(int width, int height) {
grid = new MultiGrid<>(width, height);
}
@Override
public void add(Location loc, Item item) {
// keep track of location of all items
items.put(item, loc);
// also keep track of whether we need to redraw this cell
dirtyLocs.add(loc);
// do the actual adding
List<Item> list = grid.get(loc);
for (int i = 0; i < list.size(); i++) {
if (item.compareTo(list.get(i)) >= 0) {
list.add(i, item);
return;
}
}
list.add(item);
}
public void addRandomItems(Location loc) {
if (!this.hasActors(loc) && !this.hasWall(loc)) {
Random random = new Random();
if (random.nextInt(10) < 2) {
this.add(loc, new Carrot());
}
if (random.nextInt(10) < 1) {
this.add(loc, new Manga());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Chest());
}
if (random.nextInt(2) < 1) {
this.add(loc, new BasicSword());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Shield());
}
if (random.nextInt(2) < 1) {
this.add(loc, new FireStaff());
}
if (random.nextInt(2) < 1) {
this.add(loc, new BasicBow());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Backpack());
}
}
}
@Override
public boolean canGo(Location to) {
return !grid.contains(to, (i) -> ((i instanceof Wall && !(i instanceof FakeWall)) || i instanceof Actor));
}
@Override
public boolean hasNeighbour(Location from, GridDirection dir) {
return from.canGo(dir);
}
@Override
public boolean canGo(Location from, GridDirection dir) {
if (!from.canGo(dir)) {
return false;
}
Location loc = from.go(dir);
return canGo(loc);
}
@Override
public void draw(ITurtle painter, Printer printer) {
Iterable<Location> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty()) {
return;
} else {
cells = dirtyLocs;
}
} else {
cells = grid.locations();
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
getHeight() * printer.getCharHeight());
printer.clearRegion(1, 1, getWidth(), getHeight());
}
GraphicsContext ctx = painter.as(GraphicsContext.class);
double h = printer.getCharHeight();
double w = printer.getCharWidth();
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.save();
ctx.scale(w / h, 1.0);
w = h;
}
try {
for (Location loc : cells) {
List<Item> list = grid.get(loc);
String sym = " ";
if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
}
painter.save();
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
boolean dontPrint = list.get(0).draw(painter, w, h);
painter.restore();
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
}
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
}
} finally {
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.restore();
}
}
dirtyLocs.clear();
}
/**
* Writes a black void unto the whole map.
* Properly draws only the tiles visible to the player.
*
* @param painter A painter
* @param printer A printer
*/
public void drawVisible(ITurtle painter, Printer printer) {
Iterable<Location> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty()) {
return;
} else {
cells = dirtyLocs;
}
} else {
cells = grid.locations();
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
getHeight() * printer.getCharHeight());
printer.clearRegion(1, 1, getWidth(), getHeight());
}
GraphicsContext ctx = painter.as(GraphicsContext.class);
double h = printer.getCharHeight();
double w = printer.getCharWidth();
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.save();
ctx.scale(w / h, 1.0);
w = h;
}
try {
PlayerCharacter player = null;
Location playerPos = null;
for (Location loc : cells) {
printer.printAt(loc.getX() + 1, loc.getY() + 1, " ");
//We need to get the player and its location from somewhere.
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof PlayerCharacter) {
player = (PlayerCharacter) this.getActors(loc).get(0);
playerPos = loc;
}
}
if (player == null) {
return;
}
List<Location> positions = getVisible(getNeighbourhood(playerPos, player.getVision()), playerPos);
positions.add(playerPos);
for (Location loc : positions) {
List<Item> list = grid.get(loc);
String sym = ".";
if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
}
painter.save();
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
boolean dontPrint = list.get(0).draw(painter, w, h);
painter.restore();
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
}
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
}
} finally {
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.restore();
}
}
dirtyLocs.clear();
}
private List<Location> getVisible(List<Location> neighbours, Location loc) {
List<Location> invalid = new ArrayList<>();
for (Location neighbour : neighbours) {
if (!hasWall(neighbour)) {
for (Location tile : loc.gridLineTo(neighbour)) {
if (hasWall(tile)) {
invalid.add(neighbour);
break;
}
}
}
}
neighbours.removeAll(invalid);
return neighbours;
}
@Override
public List<Actor> getActors(Location loc) {
List<Actor> items = new ArrayList<>();
for (Item item : grid.get(loc)) {
if (item instanceof Actor) {
items.add((Actor) item);
}
}
return items;
}
@Override
public List<Item> getAll(Location loc) {
return Collections.unmodifiableList(grid.get(loc));
}
@Override
public List<Item> getAllModifiable(Location loc) {
dirtyLocs.add(loc);
return grid.get(loc);
}
@Override
public void clean(Location loc) {
// remove any items that have health < 0:
if (grid.get(loc).removeIf((item) -> {
if (item.isDestroyed()) {
items.remove(item);
return true;
} else {
return false;
}
})) {
dirtyLocs.add(loc);
}
}
@Override
public IArea getArea() {
return grid.getArea();
}
@Override
public int getHeight() {
return grid.getHeight();
}
@Override
public List<Item> getItems(Location loc) {
List<Item> items = new ArrayList<>(grid.get(loc));
items.removeIf((i) -> i instanceof Actor);
return items;
}
@Override
public Location getLocation(Item item) {
return items.get(item);
}
@Override
public Location getLocation(int x, int y) {
return grid.getArea().location(x, y);
}
@Override
public Location getNeighbour(Location from, GridDirection dir) {
if (!hasNeighbour(from, dir)) {
return null;
} else {
return from.go(dir);
}
}
@Override
public int getWidth() {
return grid.getWidth();
}
@Override
public Location go(Location from, GridDirection dir) throws IllegalMoveException {
if (!from.canGo(dir)) {
throw new IllegalMoveException("Cannot move outside map!");
}
Location loc = from.go(dir);
if (!canGo(loc)) {
throw new IllegalMoveException("Occupied!");
}
return loc;
}
@Override
public boolean has(Location loc, Item target) {
return grid.contains(loc, target);
}
@Override
public boolean hasActors(Location loc) {
return grid.contains(loc, (i) -> i instanceof Actor);
}
@Override
public boolean hasItems(Location loc) {
// true if grid cell contains an item which is not an IActor
return grid.contains(loc, (i) -> !(i instanceof Actor));
}
@Override
public boolean hasWall(Location loc) {
return grid.contains(loc, (i) -> i instanceof Wall);
}
@Override
public void remove(Location loc, Item item) {
grid.remove(loc, item);
items.remove(item);
dirtyLocs.add(loc);
}
@Override
public List<Location> getNeighbourhood(Location startLocation, int distance) {
if (distance < 0 || startLocation == null) {
throw new IllegalArgumentException();
} else if (distance == 0) {
return new ArrayList<>(); // empty!
}
// Run BFS to find all neighbors within the specified distance
Queue<Location> queue = new LinkedList<>();
List<Location> explored = new ArrayList<>();
explored.add(startLocation);
queue.add(startLocation);
while (!queue.isEmpty()) {
Location location = queue.poll();
// The required distance has been reached
if (Math.abs(startLocation.getX() - location.getX()) >= distance ||
Math.abs(startLocation.getY() - location.getY()) >= distance) {
break;
}
for (GridDirection gridDirection : GridDirection.EIGHT_DIRECTIONS) {
addIfValid(queue, explored, location, gridDirection);
}
}
// While the BFS requires the start location, it's not considered its own neighbor
explored.remove(startLocation);
return explored;
}
/**
* Adds the specified location to the queue if valid
*
* @param queue <p>The queue to add the location to</p>
* @param explored <p>The already explored locations</p>
* @param startLocation <p>The location to start at</p>
* @param direction <p>The direction to go</p>
*/
private void addIfValid(Queue<Location> queue, List<Location> explored, Location startLocation,
GridDirection direction) {
Location location;
// If going in the specified direction would exit the map, just return
try {
location = startLocation.go(direction);
} catch (IndexOutOfBoundsException ignored) {
return;
}
if (!explored.contains(location) && grid.isValid(location)) {
explored.add(location);
queue.add(location);
}
}
}

View File

@ -1,393 +1,48 @@
package inf101.v18.rogue101.map; package inf101.v18.rogue101.map;
import java.util.*;
import inf101.v18.gfx.gfxmode.ITurtle; import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer; import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection; import inf101.v18.grid.Location;
import inf101.v18.grid.IArea; import inf101.v18.rogue101.object.Item;
import inf101.v18.grid.ILocation;
import inf101.v18.grid.IMultiGrid;
import inf101.v18.grid.MultiGrid;
import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.examples.Carrot;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.items.Manga;
import inf101.v18.rogue101.objects.*;
import javafx.scene.canvas.GraphicsContext;
public class GameMap implements IGameMap { import java.util.List;
/**
* The grid that makes up our map
*/
private final IMultiGrid<IItem> grid;
/**
* These locations have changed, and need to be redrawn
*/
private final Set<ILocation> dirtyLocs = new HashSet<>();
/**
* An index of all the items in the map and their locations.
*/
// an IdentityHashMap uses object identity as a lookup key, so items are
// considered equal if they are the same object (a == b)
private final Map<IItem, ILocation> items = new IdentityHashMap<>();
public GameMap(IArea area) { /**
grid = new MultiGrid<>(area); * Extra map methods that are for the game class only!
}
public GameMap(int width, int height) {
grid = new MultiGrid<>(width, height);
}
@Override
public void add(ILocation loc, IItem item) {
// keep track of location of all items
items.put(item, loc);
// also keep track of whether we need to redraw this cell
dirtyLocs.add(loc);
// do the actual adding
List<IItem> list = grid.get(loc);
for (int i = 0; i < list.size(); i++) {
if (item.compareTo(list.get(i)) >= 0) {
list.add(i, item);
return;
}
}
list.add(item);
}
public void addRandomItems(ILocation loc) {
if (!this.hasActors(loc) && !this.hasWall(loc)) {
Random random = new Random();
if (random.nextInt(10) < 2) {
this.add(loc, new Carrot());
}
if (random.nextInt(10) < 1) {
this.add(loc, new Manga());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Chest());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Sword());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Shield());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Staff());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Bow());
}
if (random.nextInt(2) < 1) {
this.add(loc, new Backpack());
}
}
}
@Override
public boolean canGo(ILocation to) {
return !grid.contains(to, (i) -> (i instanceof Wall || i instanceof IActor));
}
@Override
public boolean hasNeighbour(ILocation from, GridDirection dir) {
return from.canGo(dir);
}
@Override
public boolean canGo(ILocation from, GridDirection dir) {
if (!from.canGo(dir))
return false;
ILocation loc = from.go(dir);
return canGo(loc);
}
@Override
public void draw(ITurtle painter, Printer printer) {
Iterable<ILocation> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty())
return;
else
cells = dirtyLocs;
} else {
cells = grid.locations();
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
getHeight() * printer.getCharHeight());
printer.clearRegion(1, 1, getWidth(), getHeight());
}
GraphicsContext ctx = painter.as(GraphicsContext.class);
double h = printer.getCharHeight();
double w = printer.getCharWidth();
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.save();
ctx.scale(w / h, 1.0);
w = h;
}
try {
for (ILocation loc : cells) {
List<IItem> list = grid.get(loc);
String sym = " ";
if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
}
painter.save();
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
boolean dontPrint = list.get(0).draw(painter, w, h);
painter.restore();
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
}
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym);
}
} finally {
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.restore();
}
}
dirtyLocs.clear();
}
/**
* Writes a black void unto the whole map.
* Properly draws only the tiles visible to the player.
* *
* @param painter A painter * @author anya
* @param printer A printer
*/ */
public void drawVisible(ITurtle painter, Printer printer) { public interface GameMap extends MapView {
Iterable<ILocation> cells;
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
if (dirtyLocs.isEmpty())
return;
else
cells = dirtyLocs;
} else {
cells = grid.locations();
painter.as(GraphicsContext.class).clearRect(0, 0, getWidth() * printer.getCharWidth(),
getHeight() * printer.getCharHeight());
printer.clearRegion(1, 1, getWidth(), getHeight());
}
GraphicsContext ctx = painter.as(GraphicsContext.class);
double h = printer.getCharHeight();
double w = printer.getCharWidth();
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) {
ctx.save();
ctx.scale(w / h, 1.0);
w = h;
}
try {
IPlayer player = null;
ILocation playerPos = null;
for (ILocation loc : cells) {
printer.printAt(loc.getX() + 1, loc.getY() + 1, " ");
//We need to get the player and its location from somewhere.
if (this.hasActors(loc) && this.getActors(loc).get(0) instanceof IPlayer) {
player = (IPlayer) this.getActors(loc).get(0);
playerPos = loc;
}
}
if (player == null) {
return;
}
List<ILocation> positions = getVisible(getNeighbourhood(playerPos,player.getVision()), playerPos);
positions.add(playerPos);
for (ILocation loc : positions) {
List<IItem> list = grid.get(loc);
String sym = ".";
if (!list.isEmpty()) {
if (Main.MAP_DRAW_ONLY_DIRTY_CELLS) {
ctx.clearRect(loc.getX() * w, loc.getY() * h, w, h);
// ctx.fillRect(loc.getX() * w, loc.getY() * h, w, h);
}
painter.save();
painter.jumpTo((loc.getX() + 0.5) * w, (loc.getY() + 0.5) * h);
boolean dontPrint = list.get(0).draw(painter, w, h);
painter.restore();
if (!dontPrint) {
sym = list.get(0).getPrintSymbol();
}
} /**
printer.printAt(loc.getX() + 1, loc.getY() + 1, sym); * Draw the map
} *
} finally { * @param painter
if (Main.MAP_AUTO_SCALE_ITEM_DRAW) { * @param printer
ctx.restore(); */
} void draw(ITurtle painter, Printer printer);
}
dirtyLocs.clear();
}
private List<ILocation> getVisible(List<ILocation> neighbours, ILocation loc) { /**
List<ILocation> invalid = new ArrayList<>(); * Get a modifiable list of items
for (ILocation neighbour : neighbours) { *
if (!hasWall(neighbour)) { * @param loc
for (ILocation tile : loc.gridLineTo(neighbour)) { * @return
if (hasWall(tile)) { */
invalid.add(neighbour); List<Item> getAllModifiable(Location loc);
break;
}
}
}
}
neighbours.removeAll(invalid);
return neighbours;
}
@Override /**
public List<IActor> getActors(ILocation loc) { * Remove any destroyed items at the given location (items where {@link Item#isDestroyed()} is true)
List<IActor> items = new ArrayList<>(); *
for (IItem item : grid.get(loc)) { * @param loc
if (item instanceof IActor) */
items.add((IActor) item); void clean(Location loc);
}
return items; /**
} * Remove an item
*
* @param loc
* @param item
*/
void remove(Location loc, Item item);
@Override
public List<IItem> getAll(ILocation loc) {
return Collections.unmodifiableList(grid.get(loc));
}
@Override
public List<IItem> getAllModifiable(ILocation loc) {
dirtyLocs.add(loc);
return grid.get(loc);
}
@Override
public void clean(ILocation loc) {
// remove any items that have health < 0:
if (grid.get(loc).removeIf((item) -> {
if (item.isDestroyed()) {
items.remove(item);
return true;
} else {
return false;
}
})) {
dirtyLocs.add(loc);
}
}
@Override
public IArea getArea() {
return grid.getArea();
}
@Override
public int getHeight() {
return grid.getHeight();
}
@Override
public List<IItem> getItems(ILocation loc) {
List<IItem> items = new ArrayList<>(grid.get(loc));
items.removeIf((i) -> i instanceof IActor);
return items;
}
@Override
public ILocation getLocation(IItem item) {
return items.get(item);
}
@Override
public ILocation getLocation(int x, int y) {
return grid.getArea().location(x, y);
}
@Override
public ILocation getNeighbour(ILocation from, GridDirection dir) {
if (!hasNeighbour(from, dir))
return null;
else
return from.go(dir);
}
@Override
public int getWidth() {
return grid.getWidth();
}
@Override
public ILocation go(ILocation from, GridDirection dir) throws IllegalMoveException {
if (!from.canGo(dir))
throw new IllegalMoveException("Cannot move outside map!");
ILocation loc = from.go(dir);
if (!canGo(loc))
throw new IllegalMoveException("Occupied!");
return loc;
}
@Override
public boolean has(ILocation loc, IItem target) {
return grid.contains(loc, target);
}
@Override
public boolean hasActors(ILocation loc) {
return grid.contains(loc, (i) -> i instanceof IActor);
}
@Override
public boolean hasItems(ILocation loc) {
// true if grid cell contains an item which is not an IActor
return grid.contains(loc, (i) -> !(i instanceof IActor));
}
@Override
public boolean hasWall(ILocation loc) {
return grid.contains(loc, (i) -> i instanceof Wall || i instanceof FakeWall);
}
@Override
public void remove(ILocation loc, IItem item) {
grid.remove(loc, item);
items.remove(item);
dirtyLocs.add(loc);
}
@Override
public List<ILocation> getNeighbourhood(ILocation loc, int dist) {
if (dist < 0 || loc == null) {
throw new IllegalArgumentException();
} else if (dist == 0) {
return new ArrayList<>(); // empty!
}
List<ILocation> neighbours = new ArrayList<>();
int startX = loc.getX();
int startY = loc.getY();
for (int i = 1; i <= dist; i++) {
for (int x = startX - i + 1; x < startX + i; x++) { //Top and bottom
if (grid.isValid(x, startY - i)) {
neighbours.add(getLocation(x, startY - i));
}
if (grid.isValid(x, startY + i)) {
neighbours.add(getLocation(x, startY + i));
}
}
for (int y = startY - i; y <= startY + i; y++) { //Sides
if (grid.isValid(startX - i, y)) {
neighbours.add(getLocation(startX - i, y));
}
if (grid.isValid(startX + i, y)) {
neighbours.add(getLocation(startX + i, y));
}
}
}
return neighbours;
}
} }

View File

@ -1,48 +0,0 @@
package inf101.v18.rogue101.map;
import java.util.List;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.objects.IItem;
/**
* Extra map methods that are for the game class only!
*
* @author anya
*
*/
public interface IGameMap extends IMapView {
/**
* Draw the map
*
* @param painter
* @param printer
*/
void draw(ITurtle painter, Printer printer);
/**
* Get a modifiable list of items
*
* @param loc
* @return
*/
List<IItem> getAllModifiable(ILocation loc);
/**
* Remove any destroyed items at the given location (items where {@link IItem#isDestroyed()} is true)
*
* @param loc
*/
void clean(ILocation loc);
/**
* Remove an item
* @param loc
* @param item
*/
void remove(ILocation loc, IItem item);
}

View File

@ -1,190 +0,0 @@
package inf101.v18.rogue101.map;
import java.util.List;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.IArea;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
public interface IMapView {
/**
* Add an item to the map.
*
* @param loc
* A location
* @param item
* the item
*/
void add(ILocation loc, IItem item);
/**
* Check if it's legal for an IActor to go into the given location
*
* @param to
* A location
* @return True if the location isn't already occupied
*/
boolean canGo(ILocation to);
/**
* Check if it's legal for an IActor to go in the given direction from the given
* location
*
* @param from
* Current location
* @param dir
* Direction we want to move in
* @return True if the next location exists and isn't occupied
*/
boolean canGo(ILocation from, GridDirection dir);
/**
* Get all IActors at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of actors
*/
List<IActor> getActors(ILocation loc);
/**
* Get all items (both IActors and other IItems) at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of items
*/
List<IItem> getAll(ILocation loc);
/**
* Get all non-IActor items at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of items, non of which are instanceof IActor
*/
List<IItem> getItems(ILocation loc);
/**
* @return A 2D-area defining the legal map locations
*/
IArea getArea();
/**
* @return Height of the map, same as
* {@link #getArea()}.{@link IArea#getHeight()}
*/
int getHeight();
/**
* @return Width of the map, same as {@link #getArea()}.{@link IArea#getWidth()}
*/
int getWidth();
/**
* Find location of an item
*
* @param item
* The item
* @return It's location, or <code>null</code> if it's not on the map
*/
ILocation getLocation(IItem item);
/**
* Translate (x,y)-coordinates to ILocation
*
* @param x
* @param y
* @return an ILocation
* @throws IndexOutOfBoundsException
* if (x,y) is outside {@link #getArea()}
*/
ILocation getLocation(int x, int y);
/**
* Get the neighbouring location in the given direction
*
* @param from
* A location
* @param dir
* the Direction
* @return from's neighbour in direction dir, or null, if this would be outside
* the map
*/
ILocation getNeighbour(ILocation from, GridDirection dir);
/**
* Compute new location of an IActor moving the given direction
*
* @param from
* Original location
* @param dir
* Direction we're moving in
* @return The new location
* @throws IllegalMoveException
* if !{@link #canGo(ILocation, GridDirection)}
*/
ILocation go(ILocation from, GridDirection dir) throws IllegalMoveException;
/**
* Check if an item exists at a location
*
* @param loc
* The location
* @param target
* The item we're interested in
* @return True if target would appear in {@link #getAll(loc)}
*/
boolean has(ILocation loc, IItem target);
/**
* Check for actors.
*
* @param loc
* @return True if {@link #getActors(loc)} would be non-empty
*/
boolean hasActors(ILocation loc);
/**
* Check for non-actors
*
* @param loc
* @return True if {@link #getItem(loc)} would be non-empty
*/
boolean hasItems(ILocation loc);
/**
* Check for walls
*
* @param loc
* @return True if there is a wall at the given location ({@link #getAll(loc)}
* would contain an instance of {@link Wall})
*/
boolean hasWall(ILocation loc);
/**
* Check if a neighbour exists on the map
*
* @param from A location
* @param dir A direction
* @return True if {@link #getNeighbour(from, dir)} would return non-null
*/
boolean hasNeighbour(ILocation from, GridDirection dir);
/**
* Get all locations within i steps from the given centre
* @param centre
* @param dist
* @return A list of locations, all at most i grid cells away from centre
*/
List<ILocation> getNeighbourhood(ILocation centre, int dist);
}

View File

@ -1,17 +1,17 @@
package inf101.v18.rogue101.map; package inf101.v18.rogue101.map;
import inf101.v18.grid.IGrid;
import inf101.v18.grid.MyGrid;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.Scanner; import java.util.Scanner;
import inf101.v18.grid.IGrid;
import inf101.v18.grid.MyGrid;
/** /**
* A class to read a Boulder Dash map from a file. After the file is read, the * A class to read a Boulder Dash map from a file. After the file is read, the
* map is stored as an {@link #IGrid} whose entries are characters. This can * map is stored as an {@link #IGrid} whose entries are characters. This can
* then be used to be passed to the constructor in {@link #BDMap}. * then be used to be passed to the constructor in {@link #BDMap}.
* * <p>
* The first line of the file should could contain two numbers. First the width * The first line of the file should could contain two numbers. First the width
* and then the height of the map. After that follows a matrix of characters * and then the height of the map. After that follows a matrix of characters
* describing which object goes where in the map. '*' for wall, ' ' for empty, * describing which object goes where in the map. '*' for wall, ' ' for empty,
@ -46,8 +46,9 @@ public class MapReader {
while (in.hasNextLine()) { while (in.hasNextLine()) {
xy[0] = 0; xy[0] = 0;
in.nextLine().codePoints().forEach((codePoint) -> { in.nextLine().codePoints().forEach((codePoint) -> {
if (xy[0] < symbolMap.getWidth()) if (xy[0] < symbolMap.getWidth()) {
symbolMap.set(xy[0]++, xy[1], String.valueOf(Character.toChars(codePoint))); symbolMap.set(xy[0]++, xy[1], String.valueOf(Character.toChars(codePoint)));
}
}); });
xy[1]++; xy[1]++;
} }
@ -65,8 +66,9 @@ public class MapReader {
IGrid<String> symbolMap = null; IGrid<String> symbolMap = null;
ClassLoader classloader = Thread.currentThread().getContextClassLoader(); ClassLoader classloader = Thread.currentThread().getContextClassLoader();
InputStream stream = classloader.getResourceAsStream(path); InputStream stream = classloader.getResourceAsStream(path);
if (stream == null) if (stream == null) {
return null; return null;
}
try (Scanner in = new Scanner(stream, "UTF-8")) { try (Scanner in = new Scanner(stream, "UTF-8")) {
int width = in.nextInt(); int width = in.nextInt();
int height = in.nextInt(); int height = in.nextInt();

View File

@ -0,0 +1,177 @@
package inf101.v18.rogue101.map;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.IArea;
import inf101.v18.grid.Location;
import inf101.v18.rogue101.game.IllegalMoveException;
import inf101.v18.rogue101.object.Actor;
import inf101.v18.rogue101.object.Item;
import java.util.List;
public interface MapView {
/**
* Add an item to the map.
*
* @param loc A location
* @param item the item
*/
void add(Location loc, Item item);
/**
* Check if it's legal for an IActor to go into the given location
*
* @param to A location
* @return True if the location isn't already occupied
*/
boolean canGo(Location to);
/**
* Check if it's legal for an IActor to go in the given direction from the given
* location
*
* @param from Current location
* @param dir Direction we want to move in
* @return True if the next location exists and isn't occupied
*/
boolean canGo(Location from, GridDirection dir);
/**
* Get all IActors at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of actors
*/
List<Actor> getActors(Location loc);
/**
* Get all items (both IActors and other IItems) at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of items
*/
List<Item> getAll(Location loc);
/**
* Get all non-IActor items at the given location
* <p>
* The returned list either can't be modified, or modifying it won't affect the
* map.
*
* @param loc
* @return A list of items, non of which are instanceof IActor
*/
List<Item> getItems(Location loc);
/**
* @return A 2D-area defining the legal map locations
*/
IArea getArea();
/**
* @return Height of the map, same as
* {@link #getArea()}.{@link IArea#getHeight()}
*/
int getHeight();
/**
* @return Width of the map, same as {@link #getArea()}.{@link IArea#getWidth()}
*/
int getWidth();
/**
* Find location of an item
*
* @param item The item
* @return It's location, or <code>null</code> if it's not on the map
*/
Location getLocation(Item item);
/**
* Translate (x,y)-coordinates to ILocation
*
* @param x
* @param y
* @return an ILocation
* @throws IndexOutOfBoundsException if (x,y) is outside {@link #getArea()}
*/
Location getLocation(int x, int y);
/**
* Get the neighbouring location in the given direction
*
* @param from A location
* @param dir the Direction
* @return from's neighbour in direction dir, or null, if this would be outside
* the map
*/
Location getNeighbour(Location from, GridDirection dir);
/**
* Compute new location of an IActor moving the given direction
*
* @param from Original location
* @param dir Direction we're moving in
* @return The new location
* @throws IllegalMoveException if !{@link #canGo(Location, GridDirection)}
*/
Location go(Location from, GridDirection dir) throws IllegalMoveException;
/**
* Check if an item exists at a location
*
* @param loc The location
* @param target The item we're interested in
* @return True if target would appear in {@link #getAll(loc)}
*/
boolean has(Location loc, Item target);
/**
* Check for actors.
*
* @param loc
* @return True if {@link #getActors(loc)} would be non-empty
*/
boolean hasActors(Location loc);
/**
* Check for non-actors
*
* @param loc
* @return True if {@link #getItem(loc)} would be non-empty
*/
boolean hasItems(Location loc);
/**
* Check for walls
*
* @param loc
* @return True if there is a wall at the given location ({@link #getAll(loc)}
* would contain an instance of {@link Wall})
*/
boolean hasWall(Location loc);
/**
* Check if a neighbour exists on the map
*
* @param from A location
* @param dir A direction
* @return True if {@link #getNeighbour(from, dir)} would return non-null
*/
boolean hasNeighbour(Location from, GridDirection dir);
/**
* Get all locations within i steps from the given centre
*
* @param centre
* @param dist
* @return A list of locations, all at most i grid cells away from centre
*/
List<Location> getNeighbourhood(Location centre, int dist);
}

View File

@ -0,0 +1,43 @@
package inf101.v18.rogue101.object;
import inf101.v18.rogue101.game.Game;
/**
* An actor is an Item that can also do something, either controlled by the
* computer (NonPlayerCharacter) or the user (PlayerCharacter).
*
* @author anya
*/
public interface Actor extends Item {
/**
* @return This actor's attack score (used against an item's
* {@link #getDefense()} score to see if an attack is successful)
*/
int getAttack();
/**
* @return The damage this actor deals on a successful attack (used together
* with
* {@link #handleDamage(Game, Item, int)} on
* the target)
*/
int getDamage();
/**
* How many tiles the IActor is able to see in each direction.
*
* @return Number of tiles
*/
default int getVision() {
return 1;
}
/**
* Gets an item of the specified type, if the actor has the item.
*
* @return An IItem or null
*/
Item getItem(Class<?> type);
}

View File

@ -0,0 +1,49 @@
package inf101.v18.rogue101.object;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.rogue101.game.Game;
public class Dust implements Item {
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefense() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "thick layer of dust";
}
@Override
public int getSize() {
return 1;
}
@Override
public String getSymbol() {
return BlocksAndBoxes.BLOCK_HALF;
}
@Override
public int handleDamage(Game game, Item source, int amount) {
return 0;
}
}

View File

@ -0,0 +1,31 @@
package inf101.v18.rogue101.object;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
/**
* An objects that works like a wall in every sense, except it can be passed through
*/
public class FakeWall extends Wall {
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getMaxHealth() {
return 1;
}
@Override
public String getName() {
return "fake wall";
}
@Override
public String getSymbol() {
return BlocksAndBoxes.BLOCK_HALF;
}
}

View File

@ -0,0 +1,175 @@
package inf101.v18.rogue101.object;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.rogue101.events.IEvent;
import inf101.v18.rogue101.game.Game;
/**
* An {@link Item} is something that can be placed on the map or into a
* container of items.
* <p>
* An {@link Actor} is a special case of {@link Item} used for things that are
* alive, such as the player or AI-controlled monsters/NPCs.
* <p>
* By default, all items have a hit point / health system (they can be damaged),
* and can be picked up. So you could potentially destroy an item or pick up a
* monster.
*
* @author anya
*/
public interface Item extends Comparable<Item> {
@Override
default int compareTo(Item other) {
return Integer.compare(getSize(), other.getSize());
}
/**
* Draw this item on the screen.
* <p>
* The turtle-painter will be positioned in the centre of the cell. You should
* avoid drawing outside the cell (size is indicated by the <code>w</code> and
* <code>h</code> parameters, so you can move ±w/2 and ±h/2 from the initial
* position). If you want to start in the lower left corner of the cell, you can
* start by doing <code>painter.jump(-w/2,h/2)</code> ((0,0) is in the top-left
* corner of the screen, so negative X points left and positive Y points down).
* <p>
* If this method returns <code>true</code>, the game will <em>not</em> print
* {@link #getSymbol()} in the cell.
* <p>
* All calls to <code>painter.save()</code> must be matched by a call to
* <code>painter.restore()</code>.
*
* @param painter A turtle-painter for drawing
* @param w The width of the cell
* @param h The height of the cell
* @return False if the letter from {@link #getSymbol()} should be drawn instead
*/
default boolean draw(ITurtle painter, double w, double h) {
return false;
}
/**
* @return "a" or "an", depending on the name
*/
default String getArticle() {
return "a";
}
/**
* Get current remaining health points.
* <p>
* An object's <em>health points</em> determines how much damage it can take
* before it is destroyed / broken / killed.
*
* @return Current health points for this item
*/
int getCurrentHealth();
/**
* The defence score determines how hard an object/actor is to hit or grab.
*
* @return Defence score of this object
*/
int getDefense();
/**
* Get item health as a 0.0..1.0 proportion.
*
* <li><code>getHealth() >= 1.0</code> means perfect condition
* <li><code>getHealth() <= 0.0</code> means broken or dead
* <li><code>0.0 < getHealth() < 1.0</code> means partially damaged
*
* @return Health, in the range 0.0 to 1.0
*/
default double getHealthStatus() {
return getMaxHealth() > 0 ? getCurrentHealth() / getMaxHealth() : 0;
}
/**
* Get maximum health points.
* <p>
* An object's <em>health points</em> determines how much damage it can take
* before it is destroyed / broken / killed.
*
* @return Max health points for this item
*/
int getMaxHealth();
/**
* Get a (user-friendly) name for the item
* <p>
* Used for things like <code>"You see " + getArticle() + " " + getName()</code>
*
* @return Item's name
*/
String getName();
/**
* Get a map symbol used for printing this item on the screen.
* <p>
* This is usually the same as {@link #getSymbol()}, but could also include
* special control characters for changing the text colour, for example.
*
* @return A string to be displayed for this item on the screen (should be only
* one column wide when printed)
* @see <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI
* escape code (on Wikipedia)</a>
*/
default String getPrintSymbol() {
return getSymbol();
}
/**
* Get the size of the object.
* <p>
* The size determines how much space an item will use if put into a container.
*
* @return Size of the item
*/
int getSize();
/**
* Get the map symbol of this item.
* <p>
* The symbol can be used on a text-only map, or when loading a map from text.
* <p>
* The symbol should be a single Unicode codepoint (i.e.,
* <code>getSymbol().codePointCount(0, getSymbol().length()) == 1</code>). In
* most cases this means that the symbol should be a single character (i.e.,
* getSymbol().length() == 1); but there are a few Unicode characters (such as
* many emojis and special symbols) that require two Java <code>char</code>s.
*
* @return A single-codepoint string with the item's symbol
*/
String getSymbol();
/**
* Inform the item that it has been damaged
*
* @param game The game
* @param source The item (usually an IActor) that caused the damage
* @param amount How much damage the item should take
* @return Amount of damage actually taken (could be less than
* <code>amount</code> due to armour/protection effects)
*/
int handleDamage(Game game, Item source, int amount);
/**
* Inform the item that something has happened.
*
* @param event An object describing the event.
* @return
*/
default <T> T handleEvent(IEvent<T> event) {
return event.getData();
}
/**
* @return True if this item has been destroyed, and should be removed from the map
*/
default boolean isDestroyed() {
return getCurrentHealth() < 0;
}
}

View File

@ -0,0 +1,22 @@
package inf101.v18.rogue101.object;
import inf101.v18.rogue101.game.Game;
/**
* An actor controlled by the computer
*
* @author anya
*/
public interface NonPlayerCharacter extends Actor {
/**
* Do one turn for this non-player
* <p>
* This INonPlayer will be the game's current actor ({@link Game#getActor()})
* for the duration of this method call.
*
* @param game Game, for interacting with the world
*/
void doTurn(Game game);
}

View File

@ -1,19 +1,28 @@
package inf101.v18.rogue101.objects; package inf101.v18.rogue101.object;
import inf101.v18.gfx.textmode.Printer; import inf101.v18.gfx.textmode.Printer;
import inf101.v18.grid.GridDirection; import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation; import inf101.v18.grid.Location;
import inf101.v18.rogue101.Main; import inf101.v18.rogue101.Main;
import inf101.v18.rogue101.game.Game; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.RogueGame;
import inf101.v18.rogue101.items.*; import inf101.v18.rogue101.items.buff.BuffItem;
import inf101.v18.rogue101.shared.NPC; import inf101.v18.rogue101.items.consumable.Consumable;
import inf101.v18.rogue101.states.Attack; import inf101.v18.rogue101.items.container.Backpack;
import inf101.v18.rogue101.items.container.Container;
import inf101.v18.rogue101.items.container.Static;
import inf101.v18.rogue101.items.weapon.MagicWeapon;
import inf101.v18.rogue101.items.weapon.RangedWeapon;
import inf101.v18.rogue101.items.weapon.Weapon;
import inf101.v18.rogue101.state.AttackType;
import inf101.v18.util.NPCHelper;
import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCode;
import java.util.List; import java.util.List;
public class Player implements IPlayer { public class Player implements PlayerCharacter {
private int hp = getMaxHealth();
private int healthPoints = getMaxHealth();
private int attack = 40; private int attack = 40;
private int defence = 20; private int defence = 20;
private final Backpack equipped = new Backpack(); private final Backpack equipped = new Backpack();
@ -25,7 +34,7 @@ public class Player implements IPlayer {
private String name = "Player"; private String name = "Player";
@Override @Override
public boolean keyPressed(IGame game, KeyCode key) { public boolean keyPressed(Game game, KeyCode key) {
boolean turnConsumed = false; boolean turnConsumed = false;
if (writing) { if (writing) {
write(game, key); write(game, key);
@ -72,9 +81,9 @@ public class Player implements IPlayer {
game.displayMessage("Please enter your name: "); game.displayMessage("Please enter your name: ");
writing = true; writing = true;
} else if (key == KeyCode.R) { } else if (key == KeyCode.R) {
turnConsumed = nonMeleeAttack(game, getVision(), Attack.RANGED, "ranged"); turnConsumed = nonMeleeAttack(game, getVision(), AttackType.RANGED, "ranged");
} else if (key == KeyCode.F) { } else if (key == KeyCode.F) {
turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, Attack.MAGIC, "magic"); turnConsumed = nonMeleeAttack(game, getVision() / 2 + getVision() % 2 == 0 ? 0 : 1, AttackType.MAGIC, "magic");
} }
showStatus(game); showStatus(game);
return turnConsumed; return turnConsumed;
@ -89,15 +98,15 @@ public class Player implements IPlayer {
* @param weapon The required weapon type * @param weapon The required weapon type
* @return True if an attack was possible. False otherwise * @return True if an attack was possible. False otherwise
*/ */
private boolean nonMeleeAttack(IGame game, int range, Attack type, String weapon) { private boolean nonMeleeAttack(Game game, int range, AttackType type, String weapon) {
boolean turnConsumed = false; boolean turnConsumed = false;
IItem item = null; Item item = null;
switch (type) { switch (type) {
case RANGED: case RANGED:
item = getItem(IRangedWeapon.class); item = getItem(RangedWeapon.class);
break; break;
case MAGIC: case MAGIC:
item = getItem(IMagicWeapon.class); item = getItem(MagicWeapon.class);
} }
if (item == null) { if (item == null) {
game.displayMessage("You do not have a " + weapon + " weapon."); game.displayMessage("You do not have a " + weapon + " weapon.");
@ -116,7 +125,7 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @param key The key pressed * @param key The key pressed
*/ */
private void write(IGame game, KeyCode key) { private void write(Game game, KeyCode key) {
if (key == KeyCode.BACK_SPACE) { if (key == KeyCode.BACK_SPACE) {
if (text.length() > 0) { if (text.length() > 0) {
text = text.substring(0, text.length() - 1); text = text.substring(0, text.length() - 1);
@ -143,19 +152,19 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @return True if a turn was consumed. False otherwise * @return True if a turn was consumed. False otherwise
*/ */
private boolean interactInit(IGame game) { private boolean interactInit(Game game) {
List<IItem> items = game.getLocalItems(); List<Item> items = game.getLocalItems();
if (items.size() < 1) { if (items.size() < 1) {
game.displayMessage("There is nothing to pick up"); game.displayMessage("There is nothing to pick up");
} else { } else {
IItem item = items.get(0); Item item = items.get(0);
if (item instanceof IStatic && item instanceof IContainer) { //A Static item is always the biggest (and hopefully the only) item on a tile. if (item instanceof Static && item instanceof Container) { //A Static item is always the biggest (and hopefully the only) item on a tile.
openChest(game, items); openChest(game, items);
} else if (item instanceof IStatic && item instanceof StairsUp) { } else if (item instanceof StairsUp) {
((Game)game).goUp(); ((RogueGame) game).goUp();
return true; return true;
} else if (item instanceof IStatic && item instanceof StairsDown) { } else if (item instanceof StairsDown) {
((Game)game).goDown(); ((RogueGame) game).goDown();
return true; return true;
} else { } else {
if (items.size() == 1) { if (items.size() == 1) {
@ -176,15 +185,15 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @return True if a potion was used. False otherwise * @return True if a potion was used. False otherwise
*/ */
private boolean useConsumable(IGame game) { private boolean useConsumable(Game game) {
IConsumable consumable = (IConsumable) equipped.getFirst(IConsumable.class); Consumable consumable = (Consumable) equipped.getFirst(Consumable.class);
if (consumable != null) { if (consumable != null) {
hp += consumable.hpIncrease(); healthPoints += consumable.hpIncrease();
if (hp > getMaxHealth()) { if (healthPoints > getMaxHealth()) {
hp = getMaxHealth(); healthPoints = getMaxHealth();
} }
attack += consumable.attackIncrease(); attack += consumable.attackIncrease();
defence += consumable.defenceIncrease(); defence += consumable.defenseIncrease();
equipped.remove(consumable); equipped.remove(consumable);
game.displayMessage("Used " + consumable.getName()); game.displayMessage("Used " + consumable.getName());
return true; return true;
@ -200,8 +209,8 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @param items A list of items on the current tile * @param items A list of items on the current tile
*/ */
private void openChest(IGame game, List<IItem> items) { private void openChest(Game game, List<Item> items) {
IContainer container = (IContainer) items.get(0); Container container = (Container) items.get(0);
items = container.getContent(); items = container.getContent();
game.displayMessage(container.getInteractMessage() + niceList(items, 10)); game.displayMessage(container.getInteractMessage() + niceList(items, 10));
exploringChest = true; exploringChest = true;
@ -214,7 +223,7 @@ public class Player implements IPlayer {
* @param max The maximum number of items to print * @param max The maximum number of items to print
* @return A string * @return A string
*/ */
private String niceList(List<IItem> list, int max) { private String niceList(List<Item> list, int max) {
StringBuilder msg = new StringBuilder(); StringBuilder msg = new StringBuilder();
for (int i = 0; i < Math.min(list.size(), max); i++) { for (int i = 0; i < Math.min(list.size(), max); i++) {
msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(list.get(i).getName())); msg.append(" [").append(i + 1).append("] ").append(firstCharToUpper(list.get(i).getName()));
@ -228,7 +237,7 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @return True if a turn was consumed. False otherwise * @return True if a turn was consumed. False otherwise
*/ */
private boolean dropInit(IGame game) { private boolean dropInit(Game game) {
if (equipped.size() == 1) { if (equipped.size() == 1) {
drop(game, 0); drop(game, 0);
return true; return true;
@ -247,11 +256,11 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @param i The wanted index * @param i The wanted index
*/ */
private void pickUp(IGame game, int i) { private void pickUp(Game game, int i) {
if (equipped.hasSpace()) { if (equipped.hasSpace()) {
List<IItem> items = game.getLocalItems(); List<Item> items = game.getLocalItems();
if (items.size() >= i) { if (items.size() >= i) {
IItem pickedUp = game.pickUp(items.get(i)); Item pickedUp = game.pickUp(items.get(i));
if (pickedUp != null) { if (pickedUp != null) {
equipped.add(pickedUp); equipped.add(pickedUp);
game.displayMessage("Picked up " + pickedUp.getName()); game.displayMessage("Picked up " + pickedUp.getName());
@ -269,11 +278,11 @@ public class Player implements IPlayer {
/** /**
* Takes an item from a chest, and gives it to the player. * Takes an item from a chest, and gives it to the player.
*/ */
private void loot(IGame game, int i) { private void loot(Game game, int i) {
if (equipped.hasSpace()) { if (equipped.hasSpace()) {
IContainer container = getStaticContainer(game); Container container = getStaticContainer(game);
if (container != null && i < container.getContent().size()) { if (container != null && i < container.getContent().size()) {
IItem loot = container.getContent().get(i); Item loot = container.getContent().get(i);
equipped.add(loot); equipped.add(loot);
container.remove(i); container.remove(i);
game.displayMessage("Looted " + loot.getName()); game.displayMessage("Looted " + loot.getName());
@ -289,14 +298,14 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @return A static container * @return A static container
*/ */
private IContainer getStaticContainer(IGame game) { private Container getStaticContainer(Game game) {
List<IItem> items = game.getLocalItems(); List<Item> items = game.getLocalItems();
if (items.size() < 1) { if (items.size() < 1) {
return null; return null;
} }
IItem item = items.get(0); Item item = items.get(0);
if (item instanceof IStatic && item instanceof IContainer) { if (item instanceof Static && item instanceof Container) {
return (IContainer) item; return (Container) item;
} else { } else {
return null; return null;
} }
@ -308,9 +317,9 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @param i The wanted index * @param i The wanted index
*/ */
private void drop(IGame game, int i) { private void drop(Game game, int i) {
if (!equipped.isEmpty() && equipped.size() > i) { if (!equipped.isEmpty() && equipped.size() > i) {
IContainer container = getStaticContainer(game); Container container = getStaticContainer(game);
if (container != null) { if (container != null) {
if (container.addItem(equipped.get(i))) { if (container.addItem(equipped.get(i))) {
game.displayMessage(equipped.get(i).getName() + " stored in " + container.getName()); game.displayMessage(equipped.get(i).getName() + " stored in " + container.getName());
@ -337,16 +346,16 @@ public class Player implements IPlayer {
* *
* @param game An IGame object * @param game An IGame object
*/ */
private void showStatus(IGame game) { private void showStatus(Game game) {
game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s", game.formatStatus("HP: %s ATK: %d RATK: %d MATK: %d DEF: %s DMG: %s RDMG: %s MDMG: %s",
NPC.hpBar(this), NPCHelper.hpBar(this),
getAttack(Attack.MELEE), getAttack(AttackType.MELEE),
getAttack(Attack.RANGED), getAttack(AttackType.RANGED),
getAttack(Attack.MAGIC), getAttack(AttackType.MAGIC),
getDefence(), getDefense(),
getDamage(Attack.MELEE), getDamage(AttackType.MELEE),
getDamage(Attack.RANGED), getDamage(AttackType.RANGED),
getDamage(Attack.MAGIC) getDamage(AttackType.MAGIC)
); );
printInventory(game); printInventory(game);
} }
@ -356,13 +365,13 @@ public class Player implements IPlayer {
* *
* @param game An IGame object * @param game An IGame object
*/ */
private void printInventory(IGame game) { private void printInventory(Game game) {
Printer printer = game.getPrinter(); Printer printer = game.getPrinter();
List<IItem> items = equipped.getContent(); List<Item> items = equipped.getContent();
printer.clearRegion(Main.COLUMN_RIGHTSIDE_START, 13, 45, 5); printer.clearRegion(Main.COLUMN_RIGHT_SIDE_START, 13, 45, 5);
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 12, "Inventory:"); printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 12, "Inventory:");
for (int i = 0; i < items.size(); i++) { for (int i = 0; i < items.size(); i++) {
printer.printAt(Main.COLUMN_RIGHTSIDE_START, 13 + i, items.get(i).getName()); printer.printAt(Main.COLUMN_RIGHT_SIDE_START, 13 + i, items.get(i).getName());
} }
} }
@ -386,8 +395,8 @@ public class Player implements IPlayer {
* @param game An IGame object * @param game An IGame object
* @param dir The direction the player wants to go * @param dir The direction the player wants to go
*/ */
private void tryToMove(IGame game, GridDirection dir) { private void tryToMove(Game game, GridDirection dir) {
ILocation loc = game.getLocation(); Location loc = game.getLocation();
if (game.canGo(dir)) { if (game.canGo(dir)) {
game.move(dir); game.move(dir);
} else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) { } else if (loc.canGo(dir) && game.getMap().hasActors(loc.go(dir))) {
@ -400,7 +409,7 @@ public class Player implements IPlayer {
@Override @Override
public int getAttack() { public int getAttack() {
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class); BuffItem buff = (BuffItem) getItem(BuffItem.class);
if (buff != null) { if (buff != null) {
return attack + buff.getBuffDamage(); return attack + buff.getBuffDamage();
} else { } else {
@ -414,8 +423,8 @@ public class Player implements IPlayer {
* @param type The attack type corresponding to the weapon type. * @param type The attack type corresponding to the weapon type.
* @return Attack as int * @return Attack as int
*/ */
private int getAttack(Attack type) { private int getAttack(AttackType type) {
IWeapon weapon = NPC.getWeapon(type, this); Weapon weapon = NPCHelper.getWeapon(type, this);
if (weapon != null) { if (weapon != null) {
return getAttack() + weapon.getWeaponAttack(); return getAttack() + weapon.getWeaponAttack();
} else { } else {
@ -434,8 +443,8 @@ public class Player implements IPlayer {
* @param type The attack type corresponding to the weapon type. * @param type The attack type corresponding to the weapon type.
* @return Damage as int * @return Damage as int
*/ */
private int getDamage(Attack type) { private int getDamage(AttackType type) {
IWeapon weapon = NPC.getWeapon(type, this); Weapon weapon = NPCHelper.getWeapon(type, this);
if (weapon != null) { if (weapon != null) {
return getDamage() + weapon.getWeaponDamage(); return getDamage() + weapon.getWeaponDamage();
} else { } else {
@ -444,8 +453,8 @@ public class Player implements IPlayer {
} }
@Override @Override
public IItem getItem(Class<?> type) { public Item getItem(Class<?> type) {
for (IItem item : equipped.getContent()) { for (Item item : equipped.getContent()) {
if (type.isInstance(item)) { if (type.isInstance(item)) {
return item; return item;
} }
@ -455,12 +464,12 @@ public class Player implements IPlayer {
@Override @Override
public int getCurrentHealth() { public int getCurrentHealth() {
return hp; return healthPoints;
} }
@Override @Override
public int getDefence() { public int getDefense() {
IBuffItem buff = (IBuffItem)getItem(IBuffItem.class); BuffItem buff = (BuffItem) getItem(BuffItem.class);
if (buff != null) { if (buff != null) {
return defence + buff.getBuffDamage(); return defence + buff.getBuffDamage();
} else { } else {
@ -494,10 +503,10 @@ public class Player implements IPlayer {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; healthPoints -= amount;
showStatus(game); showStatus(game);
if (hp < 1) { if (healthPoints < 1) {
game.displayStatus("Game Over"); game.displayStatus("Game Over");
} }
return amount; return amount;
@ -505,7 +514,7 @@ public class Player implements IPlayer {
@Override @Override
public int getVision() { public int getVision() {
IBuffItem item = (IBuffItem) getItem(IBuffItem.class); BuffItem item = (BuffItem) getItem(BuffItem.class);
if (item != null) { if (item != null) {
return 3 + item.getBuffVisibility(); return 3 + item.getBuffVisibility();
} else { } else {
@ -521,14 +530,14 @@ public class Player implements IPlayer {
* @param type The attack type * @param type The attack type
* @return True if the player attacked. False otherwise * @return True if the player attacked. False otherwise
*/ */
private boolean rangedAttack(IGame game, int range, Attack type) { private boolean rangedAttack(Game game, int range, AttackType type) {
List<ILocation> neighbours = game.getVisible(); List<Location> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) { for (Location neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) { if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation(); Location current = game.getLocation();
List<GridDirection> dirs = game.locationDirection(current, neighbour); List<GridDirection> dirs = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor. Actor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
if (actor instanceof INonPlayer && current.gridDistanceTo(neighbour) <= range) { if (actor instanceof NonPlayerCharacter && current.gridDistanceTo(neighbour) <= range) {
GridDirection dir = dirs.get(0); //Only ever has one item GridDirection dir = dirs.get(0); //Only ever has one item
game.rangedAttack(dir, actor, type); game.rangedAttack(dir, actor, type);
return true; return true;

View File

@ -0,0 +1,25 @@
package inf101.v18.rogue101.object;
import inf101.v18.rogue101.game.Game;
import javafx.scene.input.KeyCode;
public interface PlayerCharacter extends Actor {
/**
* Send key presses from the human player to the player object.
* <p>
* The player object should interpret the key presses, and then perform its
* moves or whatever, according to the game's rules and the player's
* instructions.
* <p>
* This IPlayer will be the game's current actor ({@link Game#getActor()}) and
* be at {@link Game#getLocation()}, when this method is called.
* <p>
* This method may be called many times in a single turn; the turn ends
* {@link #keyPressed(Game, KeyCode)} returns and the player has used its
* movement points (e.g., by calling {@link Game#move(inf101.v18.grid.GridDirection)}).
*
* @param game Game, for interacting with the world
* @return True if the player has done anything consuming a turn. False otherwise
*/
boolean keyPressed(Game game, KeyCode key);
}

View File

@ -1,16 +1,20 @@
package inf101.v18.rogue101.objects; package inf101.v18.rogue101.object;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.items.IStatic; import inf101.v18.rogue101.items.container.Static;
/**
* Stairs going downwards to the previous floor
*/
public class StairsDown implements Item, Static {
public class StairsDown implements IItem, IStatic {
@Override @Override
public int getCurrentHealth() { public int getCurrentHealth() {
return 0; return 0;
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -35,7 +39,8 @@ public class StairsDown implements IItem, IStatic {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -1,16 +1,20 @@
package inf101.v18.rogue101.objects; package inf101.v18.rogue101.object;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.items.IStatic; import inf101.v18.rogue101.items.container.Static;
/**
* Stairs going upwards to the next floor
*/
public class StairsUp implements Item, Static {
public class StairsUp implements IItem, IStatic {
@Override @Override
public int getCurrentHealth() { public int getCurrentHealth() {
return 0; return 0;
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 0; return 0;
} }
@ -35,7 +39,8 @@ public class StairsUp implements IItem, IStatic {
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
return 0; return 0;
} }
} }

View File

@ -1,10 +1,14 @@
package inf101.v18.rogue101.objects; package inf101.v18.rogue101.object;
import inf101.v18.gfx.gfxmode.ITurtle; import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes; import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.rogue101.game.IGame; import inf101.v18.rogue101.game.Game;
/**
* A solid wall blocking passage
*/
public class Wall implements Item {
public class FakeWall implements IItem {
private int hp = getMaxHealth(); private int hp = getMaxHealth();
@Override @Override
@ -18,18 +22,18 @@ public class FakeWall implements IItem {
} }
@Override @Override
public int getDefence() { public int getDefense() {
return 10; return 10;
} }
@Override @Override
public int getMaxHealth() { public int getMaxHealth() {
return 1; return 1000;
} }
@Override @Override
public String getName() { public String getName() {
return "fake wall"; return "wall";
} }
@Override @Override
@ -39,12 +43,13 @@ public class FakeWall implements IItem {
@Override @Override
public String getSymbol() { public String getSymbol() {
return "\u001b[47m" + BlocksAndBoxes.BLOCK_FULL + "\u001b[0m"; return BlocksAndBoxes.BLOCK_FULL;
} }
@Override @Override
public int handleDamage(IGame game, IItem source, int amount) { public int handleDamage(Game game, Item source, int amount) {
hp -= amount; hp -= amount;
return amount; return amount;
} }
} }

View File

@ -1,49 +0,0 @@
package inf101.v18.rogue101.objects;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.rogue101.game.IGame;
public class Dust implements IItem {
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getCurrentHealth() {
return 0;
}
@Override
public int getDefence() {
return 0;
}
@Override
public int getMaxHealth() {
return 0;
}
@Override
public String getName() {
return "thick layer of dust";
}
@Override
public int getSize() {
return 1;
}
@Override
public String getSymbol() {
return BlocksAndBoxes.BLOCK_HALF;
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
return 0;
}
}

View File

@ -1,40 +0,0 @@
package inf101.v18.rogue101.objects;
/**
* An actor is an IItem that can also do something, either controlled by the
* computer (INonPlayer) or the user (IPlayer).
*
* @author anya
*
*/
public interface IActor extends IItem {
/**
* @return This actor's attack score (used against an item's
* {@link #getDefence()} score to see if an attack is successful)
*/
int getAttack();
/**
* @return The damage this actor deals on a successful attack (used together
* with
* {@link #handleDamage(inf101.v18.rogue101.game.IGame, IItem, int)} on
* the target)
*/
int getDamage();
/**
* How many tiles the IActor is able to see in each direction.
*
* @return Number of tiles
*/
default int getVision() {
return 1;
}
/**
* Gets an item of the specified type, if the actor has the item.
*
* @return An IItem or null
*/
IItem getItem(Class<?> type);
}

View File

@ -1,181 +0,0 @@
package inf101.v18.rogue101.objects;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.rogue101.events.IEvent;
import inf101.v18.rogue101.game.IGame;
/**
* An {@link IItem} is something that can be placed on the map or into a
* container of items.
* <p>
* An {@link IActor} is a special case of {@link IItem} used for things that are
* alive, such as the player or AI-controlled monsters/NPCs.
* <p>
* By default, all items have a hit point / health system (they can be damaged),
* and can be picked up. So you could potentially destroy an item or pick up a
* monster.
*
* @author anya
*/
public interface IItem extends Comparable<IItem> {
@Override
default int compareTo(IItem other) {
return Integer.compare(getSize(), other.getSize());
}
/**
* Draw this item on the screen.
* <p>
* The turtle-painter will be positioned in the centre of the cell. You should
* avoid drawing outside the cell (size is indicated by the <code>w</code> and
* <code>h</code> parameters, so you can move ±w/2 and ±h/2 from the initial
* position). If you want to start in the lower left corner of the cell, you can
* start by doing <code>painter.jump(-w/2,h/2)</code> ((0,0) is in the top-left
* corner of the screen, so negative X points left and positive Y points down).
* <p>
* If this method returns <code>true</code>, the game will <em>not</em> print
* {@link #getSymbol()} in the cell.
* <p>
* All calls to <code>painter.save()</code> must be matched by a call to
* <code>painter.restore()</code>.
*
* @param painter
* A turtle-painter for drawing
* @param w
* The width of the cell
* @param h
* The height of the cell
* @return False if the letter from {@link #getSymbol()} should be drawn instead
*/
default boolean draw(ITurtle painter, double w, double h) {
return false;
}
/**
* @return "a" or "an", depending on the name
*/
default String getArticle() {
return "a";
}
/**
* Get current remaining health points.
* <p>
* An object's <em>health points</em> determines how much damage it can take
* before it is destroyed / broken / killed.
*
* @return Current health points for this item
*/
int getCurrentHealth();
/**
* The defence score determines how hard an object/actor is to hit or grab.
*
* @return Defence score of this object
*/
int getDefence();
/**
* Get item health as a 0.0..1.0 proportion.
*
* <li><code>getHealth() >= 1.0</code> means perfect condition
* <li><code>getHealth() <= 0.0</code> means broken or dead
* <li><code>0.0 < getHealth() < 1.0</code> means partially damaged
*
* @return Health, in the range 0.0 to 1.0
*/
default double getHealthStatus() {
return getMaxHealth() > 0 ? getCurrentHealth() / getMaxHealth() : 0;
}
/**
* Get maximum health points.
*
* An object's <em>health points</em> determines how much damage it can take
* before it is destroyed / broken / killed.
*
* @return Max health points for this item
*/
int getMaxHealth();
/**
* Get a (user-friendly) name for the item
* <p>
* Used for things like <code>"You see " + getArticle() + " " + getName()</code>
*
* @return Item's name
*/
String getName();
/**
* Get a map symbol used for printing this item on the screen.
* <p>
* This is usually the same as {@link #getSymbol()}, but could also include
* special control characters for changing the text colour, for example.
*
*
* @return A string to be displayed for this item on the screen (should be only
* one column wide when printed)
* @see <a href="https://en.wikipedia.org/wiki/ANSI_escape_code#Colors">ANSI
* escape code (on Wikipedia)</a>
*/
default String getPrintSymbol() {
return getSymbol();
}
/**
* Get the size of the object.
* <p>
* The size determines how much space an item will use if put into a container.
*
* @return Size of the item
*/
int getSize();
/**
* Get the map symbol of this item.
* <p>
* The symbol can be used on a text-only map, or when loading a map from text.
* <p>
* The symbol should be a single Unicode codepoint (i.e.,
* <code>getSymbol().codePointCount(0, getSymbol().length()) == 1</code>). In
* most cases this means that the symbol should be a single character (i.e.,
* getSymbol().length() == 1); but there are a few Unicode characters (such as
* many emojis and special symbols) that require two Java <code>char</code>s.
*
* @return A single-codepoint string with the item's symbol
*/
String getSymbol();
/**
* Inform the item that it has been damaged
*
* @param game
* The game
* @param source
* The item (usually an IActor) that caused the damage
* @param amount
* How much damage the item should take
* @return Amount of damage actually taken (could be less than
* <code>amount</code> due to armour/protection effects)
*/
int handleDamage(IGame game, IItem source, int amount);
/**
* Inform the item that something has happened.
*
* @param event
* An object describing the event.
* @return
*/
default <T> T handleEvent(IEvent<T> event) {
return event.getData();
}
/**
* @return True if this item has been destroyed, and should be removed from the map
*/
default boolean isDestroyed() {
return getCurrentHealth() < 0;
}
}

View File

@ -1,22 +0,0 @@
package inf101.v18.rogue101.objects;
import inf101.v18.rogue101.game.IGame;
/**
* An actor controlled by the computer
*
* @author anya
*
*/
public interface INonPlayer extends IActor {
/**
* Do one turn for this non-player
* <p>
* This INonPlayer will be the game's current actor ({@link IGame#getActor()})
* for the duration of this method call.
*
* @param game
* Game, for interacting with the world
*/
void doTurn(IGame game);
}

View File

@ -1,26 +0,0 @@
package inf101.v18.rogue101.objects;
import inf101.v18.rogue101.game.IGame;
import javafx.scene.input.KeyCode;
public interface IPlayer extends IActor {
/**
* Send key presses from the human player to the player object.
* <p>
* The player object should interpret the key presses, and then perform its
* moves or whatever, according to the game's rules and the player's
* instructions.
* <p>
* This IPlayer will be the game's current actor ({@link IGame#getActor()}) and
* be at {@link IGame#getLocation()}, when this method is called.
* <p>
* This method may be called many times in a single turn; the turn ends
* {@link #keyPressed(IGame, KeyCode)} returns and the player has used its
* movement points (e.g., by calling {@link IGame#move(inf101.v18.grid.GridDirection)}).
*
* @param game
* Game, for interacting with the world
* @return True if the player has done anything consuming a turn. False otherwise
*/
boolean keyPressed(IGame game, KeyCode key);
}

View File

@ -1,50 +0,0 @@
package inf101.v18.rogue101.objects;
import inf101.v18.gfx.gfxmode.ITurtle;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.rogue101.game.IGame;
public class Wall implements IItem {
private int hp = getMaxHealth();
@Override
public boolean draw(ITurtle painter, double w, double h) {
return false;
}
@Override
public int getCurrentHealth() {
return hp;
}
@Override
public int getDefence() {
return 10;
}
@Override
public int getMaxHealth() {
return 1000;
}
@Override
public String getName() {
return "wall";
}
@Override
public int getSize() {
return Integer.MAX_VALUE;
}
@Override
public String getSymbol() {
return BlocksAndBoxes.BLOCK_FULL;
}
@Override
public int handleDamage(IGame game, IItem source, int amount) {
hp -= amount;
return amount;
}
}

View File

@ -1,226 +0,0 @@
package inf101.v18.rogue101.shared;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.ILocation;
import inf101.v18.rogue101.game.IGame;
import inf101.v18.rogue101.items.*;
import inf101.v18.rogue101.map.IMapView;
import inf101.v18.rogue101.objects.IActor;
import inf101.v18.rogue101.objects.IItem;
import inf101.v18.rogue101.objects.IPlayer;
import inf101.v18.rogue101.states.Attack;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import java.io.File;
import java.io.FileNotFoundException;
import java.net.URL;
import java.util.List;
/**
* A holder class for methods used by IActors.
*/
public class NPC {
private NPC() {}
/**
* Performs a ranged attack on the closest actor (if any) observable by the current actor if range is > 1.
* Performs a melee attack on an actor (if any) on a neighbouring tile if range == 1.
* Moves closer to an enemy if an attack is not possible.
*
* @param game An IGame object
* @param range The range of the equipped weapon
* @return True if an attack or a move towards an enemy was successful. False otherwise
*/
public static boolean tryAttack(IGame game, int range, Attack type) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
IMapView map = game.getMap();
if (map.hasActors(neighbour) && map.getActors(neighbour).get(0) instanceof IPlayer) {
ILocation current = game.getLocation();
List<GridDirection> dirs = game.locationDirection(current, neighbour);
IActor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
for (GridDirection dir : dirs) {
if (type == Attack.MELEE
&& current.gridDistanceTo(neighbour) <= 1
&& current.canGo(dir)
&& game.getMap().has(current.go(dir), actor)
) {
game.attack(dir, actor);
return true;
}
if (range > 1 && current.gridDistanceTo(neighbour) <= range) {
game.rangedAttack(dir, actor, type);
return true;
}
}
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
return false;
}
/**
* Tries to find and reach an item of a specified class.
*
* @param game An IGame object
* @param validItems A list of classes the NPC should look for
* @return True if an appropriate item was found in the range of the current actor. False otherwise.
*/
public static boolean trackItem(IGame game, List<Class<?>> validItems) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
for (IItem item : game.getMap().getAll(neighbour)) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
ILocation current = game.getLocation();
List<GridDirection> dirs = game.locationDirection(current, neighbour);
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
}
}
return false;
}
/**
* Picks up an item of one of the specified class types.
*
* @param game An IGame object
* @param validItems A list of classes the NPC should accept
* @return An item if it was successfully picked up. Null otherwise
*/
public static IItem pickUp(IGame game, List<Class<?>> validItems) {
for (IItem item : game.getLocalItems()) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
return game.pickUp(item);
}
}
}
return null;
}
/**
* Makes an IActor go to the opposite direction of the closest enemy.
* Will return false if there are no visible enemies.
*
* @param game An IGame object
* @return True if the IActor fled. False otherwise
*/
public static boolean flee(IGame game) {
List<ILocation> neighbours = game.getVisible();
for (ILocation neighbour : neighbours) {
if (game.getMap().hasActors(neighbour)) {
ILocation current = game.getLocation();
List<GridDirection> dirs = reverseDir(game.locationDirection(current, neighbour));
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
return false;
}
/**
* Reverses a list of directions.
*
* @param dirs A list directions
* @return A list of the opposite directions
*/
private static List<GridDirection> reverseDir(List<GridDirection> dirs) {
for (int i = 0; i < dirs.size(); i++) {
GridDirection dir = dirs.get(i);
switch (dir) {
case SOUTH:
dirs.set(i, GridDirection.NORTH);
break;
case NORTH:
dirs.set(i, GridDirection.SOUTH);
break;
case WEST:
dirs.set(i, GridDirection.EAST);
break;
case EAST:
dirs.set(i, GridDirection.WEST);
}
}
return dirs;
}
/**
* Plays a sound.
*
* @param filename The String path of the audio file to play
*/
public static void playSound(String filename) {
URL url = NPC.class.getClassLoader().getResource(filename);
if (url != null) {
AudioClip clip = new AudioClip(url.toString());
clip.play();
} else {
System.out.println("Could not play audio file " + filename);
}
}
/**
* Generates a hp bar based on item's current hp
*
* @return HP bar as string
*/
public static String hpBar(IItem item) {
StringBuilder str = new StringBuilder(BlocksAndBoxes.BLOCK_HALF);
int hp = item.getCurrentHealth();
int hpLen = (int)((float)hp/item.getMaxHealth() * 10.0);
if (hp > 0 && hp < 100) {
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_BOTTOM + "\u001b[0m");
hpLen++;
} else {
for (int i = 0; i < hpLen; i++) {
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_HALF + "\u001b[0m");
}
}
for (int i = 0; i < 10 - hpLen; i++) {
str.append(" ");
}
str.append(BlocksAndBoxes.BLOCK_HALF);
return str.toString();
}
/**
* Gets the weapon appropriate for the used attack.
*
* @param type The attack type
* @return An IWeapon or null
*/
public static IWeapon getWeapon(Attack type, IActor actor) {
IWeapon weapon = null;
switch (type) {
case MELEE:
weapon = (IWeapon)actor.getItem(IMeleeWeapon.class);
break;
case RANGED:
weapon = (IWeapon)actor.getItem(IRangedWeapon.class);
break;
case MAGIC:
weapon = (IWeapon)actor.getItem(IMagicWeapon.class);
}
return weapon;
}
}

View File

@ -0,0 +1,12 @@
package inf101.v18.rogue101.state;
/**
* The type of a given attack
*/
public enum AttackType {
MELEE,
MAGIC,
RANGED
}

View File

@ -1,16 +1,18 @@
package inf101.v18.rogue101.states; package inf101.v18.rogue101.state;
import java.util.Random; import java.util.Random;
/** /**
* An enum to distinguish different enemies of the same type. * The age of an enemy, which alters some stats
*/ */
public enum Age { public enum LifeStage {
TODDLER, CHILD, TEEN, ADULT, ELDER; TODDLER, CHILD, TEEN, ADULT, ELDER;
private static final Random random = new Random(); private static final Random random = new Random();
public static Age getRandom() { public static LifeStage getRandom() {
return values()[random.nextInt(values().length)]; return values()[random.nextInt(values().length)];
} }
} }

View File

@ -0,0 +1,31 @@
package inf101.v18.rogue101.state;
import java.util.Random;
/**
* The occupation of an enemy, which decides the type of attack it uses
*/
public enum Occupation {
/**
* A knight that always uses melee attacks
*/
KNIGHT,
/**
* A mage that always uses magic attacks
*/
MAGE,
/**
* An archer that always uses ranged attacks
*/
ARCHER;
private static final Random random = new Random();
public static Occupation getRandom() {
return values()[random.nextInt(values().length)];
}
}

View File

@ -0,0 +1,31 @@
package inf101.v18.rogue101.state;
import java.util.Random;
/**
* The personality of an enemy, which defines its combat behavior
*/
public enum Personality {
/**
* An enemy that attacks on sight
*/
AGGRESSIVE,
/**
* An enemy that attacks if attacked
*/
CALM,
/**
* An enemy that flees from battle
*/
AFRAID;
private static final Random random = new Random();
public static Personality getRandom() {
return values()[random.nextInt(values().length)];
}
}

View File

@ -0,0 +1,63 @@
package inf101.v18.rogue101.state;
import inf101.v18.util.NPCHelper;
import javafx.scene.media.AudioClip;
import java.net.URL;
/**
* A sound that can be played
*/
public enum Sound {
MELEE_NO_WEAPON("Realistic_Punch-Mark_DiAngelo-1609462330.wav"),
MELEE_SWORD("Sword Swing-SoundBible.com-639083727.wav"),
RANGED_NO_WEAPON("Snow Ball Throw And Splat-SoundBible.com-992042947.wav"),
RANGED_BOW("Bow_Fire_Arrow-Stephan_Schutze-2133929391.wav"),
RANGED_STAFF("Large Fireball-SoundBible.com-301502490.wav"),
GAME_OVER("Dying-SoundBible.com-1255481835.wav"),
WIN("1_person_cheering-Jett_Rifkin-1851518140.wav"),
NPC_DEATH("oh-no-113125.wav"),
;
private final String soundFile;
private AudioClip audioClip = null;
/**
* Instantiates a new sound file
*
* @param soundFile <p>The file containing this sound</p>
*/
Sound(String soundFile) {
this.soundFile = soundFile;
}
/**
* Gets the sound file for this sound
*
* @return <p>The sound file for this sound</p>
*/
public String getSoundFile() {
return "audio/" + soundFile;
}
/**
* Plays this sound
*/
public void play() {
if (audioClip == null) {
URL url = NPCHelper.class.getClassLoader().getResource(getSoundFile());
if (url != null) {
audioClip = new AudioClip(url.toString());
} else {
System.out.println("Could not play audio file " + getSoundFile());
}
}
// Play the sound in a new thread if it could be loaded
if (audioClip != null) {
new Thread(() -> audioClip.play()).start();
}
}
}

View File

@ -1,5 +0,0 @@
package inf101.v18.rogue101.states;
public enum Attack {
MELEE, MAGIC, RANGED
}

View File

@ -1,16 +0,0 @@
package inf101.v18.rogue101.states;
import java.util.Random;
/**
* An enum to distinguish different enemies of the same type.
*/
public enum Occupation {
KNIGHT, MAGE, BOWSMAN;
private static final Random random = new Random();
public static Occupation getRandom() {
return values()[random.nextInt(values().length)];
}
}

View File

@ -1,16 +0,0 @@
package inf101.v18.rogue101.states;
import java.util.Random;
/**
* An enum to distinguish different enemies of the same type.
*/
public enum Personality {
AGRESSIVE, CALM, AFRAID;
private static final Random random = new Random();
public static Personality getRandom() {
return values()[random.nextInt(values().length)];
}
}

View File

@ -0,0 +1,27 @@
package inf101.v18.util;
import java.util.Random;
/**
* An interface for generators of random data values (to be used in testing).
*
* @param <T> The type of data generated.
*/
public interface Generator<T> {
/**
* Generate a random object.
*
* @return An object of type T
*/
T generate();
/**
* Generate a random object.
*
* @param r A random generator
* @return An object of type T
*/
T generate(Random r);
}

View File

@ -1,53 +0,0 @@
package inf101.v18.util;
import java.util.List;
import java.util.Random;
/**
* An interface for generators of random data values (to be used in testing).
*
* @param <T>
* The type of data generated.
*/
public interface IGenerator<T> {
/**
* Generate a random object.
*
* @return An object of type T
*/
T generate();
/**
* Generate a random object.
*
* @param r
* A random generator
* @return An object of type T
*/
T generate(Random r);
/**
* Generate a number of equal objects.
*
* @param n
* The number of objects to generate.
* @return A list of objects, with the property that for any two objects a,b in
* the collection a.equals(b).
*
*/
List<T> generateEquals(int n);
/**
* Generate a number of equal objects.
*
* @param r
* A random generator
* @param n
* The number of objects to generate.
* @return A list of objects, with the property that for any two objects a,b in
* the collection a.equals(b).
*
*/
List<T> generateEquals(Random r, int n);
}

View File

@ -0,0 +1,245 @@
package inf101.v18.util;
import inf101.v18.gfx.textmode.BlocksAndBoxes;
import inf101.v18.grid.GridDirection;
import inf101.v18.grid.Location;
import inf101.v18.rogue101.game.Game;
import inf101.v18.rogue101.items.weapon.MagicWeapon;
import inf101.v18.rogue101.items.weapon.MeleeWeapon;
import inf101.v18.rogue101.items.weapon.RangedWeapon;
import inf101.v18.rogue101.items.weapon.Weapon;
import inf101.v18.rogue101.map.MapView;
import inf101.v18.rogue101.object.Actor;
import inf101.v18.rogue101.object.Item;
import inf101.v18.rogue101.object.PlayerCharacter;
import inf101.v18.rogue101.state.AttackType;
import java.util.List;
/**
* A holder class for methods used by IActors.
*/
public class NPCHelper {
private NPCHelper() {
}
/**
* Performs a ranged attack on the closest actor (if any) observable by the current actor if range is > 1.
* Performs a melee attack on an actor (if any) on a neighbouring tile if range == 1.
* Moves closer to an enemy if an attack is not possible.
*
* @param game An IGame object
* @param range The range of the equipped weapon
* @return True if an attack or a move towards an enemy was successful. False otherwise
*/
public static boolean tryAttack(Game game, int range, AttackType type) {
List<Location> neighbours = game.getVisible();
for (Location neighbour : neighbours) {
MapView map = game.getMap();
if (map.hasActors(neighbour) && map.getActors(neighbour).get(0) instanceof PlayerCharacter) {
Location current = game.getLocation();
List<GridDirection> dirs = game.locationDirection(current, neighbour);
Actor actor = game.getMap().getActors(neighbour).get(0); //We assume there is only one actor.
for (GridDirection dir : dirs) {
if (type == AttackType.MELEE
&& current.gridDistanceTo(neighbour) <= 1
&& current.canGo(dir)
&& game.getMap().has(current.go(dir), actor)
) {
game.attack(dir, actor);
return true;
}
if (range > 1 && current.gridDistanceTo(neighbour) <= range) {
game.rangedAttack(dir, actor, type);
return true;
}
}
for (GridDirection dir : dirs) {
if (game.canGo(dir)) {
game.move(dir);
return true;
}
}
}
}
return false;
}
/**
* Tries to find and reach an item of a specified class.
*
* @param game An IGame object
* @param validItems A list of classes the NPC should look for
* @return True if an appropriate item was found in the range of the current actor. False otherwise.
*/
public static boolean trackItem(Game game, List<Class<?>> validItems) {
List<Location> neighbours = game.getVisible();
for (Location neighbour : neighbours) {
for (Item item : game.getMap().getAll(neighbour)) {
if (!isValid(item, validItems)) {
continue;
}
// If an item is found, go towards the item
Location current = game.getLocation();
List<GridDirection> directions = game.locationDirection(current, neighbour);
for (GridDirection direction : directions) {
if (game.canGo(direction)) {
game.move(direction);
return true;
}
}
}
}
return false;
}
/**
* Checks whether an item's class is in the list of valid item classes
*
* @param item <p>The item to check</p>
* @param validItems <p>Valid item classes to look for</p>
* @return <p>True if the item is valid</p>
*/
private static boolean isValid(Item item, List<Class<?>> validItems) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
return true;
}
}
return false;
}
/**
* Picks up an item of one of the specified class types.
*
* @param game An IGame object
* @param validItems A list of classes the NPC should accept
* @return An item if it was successfully picked up. Null otherwise
*/
public static Item pickUp(Game game, List<Class<?>> validItems) {
for (Item item : game.getLocalItems()) {
for (Class<?> validItem : validItems) {
if (validItem.isInstance(item)) {
return game.pickUp(item);
}
}
}
return null;
}
/**
* Makes an Actor go to the opposite direction of the closest enemy.
*
* <p>Will return false if there are no visible enemies.</p>
*
* @param game <p>A Game object</p>
* @return <p>True if the Actor fled. False otherwise</p>
*/
public static boolean flee(Game game) {
List<Location> neighbours = game.getVisible();
for (Location neighbour : neighbours) {
if (!game.getMap().hasActors(neighbour)) {
continue;
}
Location current = game.getLocation();
List<GridDirection> directions = reverseDirections(game.locationDirection(current, neighbour));
for (GridDirection direction : directions) {
if (game.canGo(direction)) {
game.move(direction);
return true;
}
}
}
return false;
}
/**
* Gets the reverse of a given direction
*
* @param direction <p>The direction to get the reverse of</p>
* @return <p>The reverse direction</p>
*/
public static GridDirection getReverseDirection(GridDirection direction) {
switch (direction) {
case SOUTH:
return GridDirection.NORTH;
case NORTH:
return GridDirection.SOUTH;
case WEST:
return GridDirection.EAST;
case EAST:
return GridDirection.WEST;
case NORTHEAST:
return GridDirection.SOUTHWEST;
case NORTHWEST:
return GridDirection.SOUTHEAST;
case SOUTHEAST:
return GridDirection.NORTHWEST;
case SOUTHWEST:
return GridDirection.NORTHEAST;
case CENTER:
return GridDirection.CENTER;
default:
throw new IllegalArgumentException("Unknown direction encountered");
}
}
/**
* Reverses a list of directions.
*
* @param directions A list directions
* @return A list of the opposite directions
*/
private static List<GridDirection> reverseDirections(List<GridDirection> directions) {
directions.replaceAll(NPCHelper::getReverseDirection);
return directions;
}
/**
* Generates a hp bar based on item's current hp
*
* @return HP bar as string
*/
public static String hpBar(Item item) {
StringBuilder str = new StringBuilder(BlocksAndBoxes.BLOCK_HALF);
int hp = item.getCurrentHealth();
int hpLen = (int) ((float) hp / item.getMaxHealth() * 10.0);
if (hp > 0 && hp < 100) {
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_BOTTOM + "\u001b[0m");
hpLen++;
} else {
for (int i = 0; i < hpLen; i++) {
str.append("\u001b[91m" + BlocksAndBoxes.BLOCK_HALF + "\u001b[0m");
}
}
for (int i = 0; i < 10 - hpLen; i++) {
str.append(" ");
}
str.append(BlocksAndBoxes.BLOCK_HALF);
return str.toString();
}
/**
* Gets the weapon appropriate for the used attack.
*
* @param type The attack type
* @return An IWeapon or null
*/
public static Weapon getWeapon(AttackType type, Actor actor) {
Weapon weapon = null;
switch (type) {
case MELEE:
weapon = (Weapon) actor.getItem(MeleeWeapon.class);
break;
case RANGED:
weapon = (Weapon) actor.getItem(RangedWeapon.class);
break;
case MAGIC:
weapon = (Weapon) actor.getItem(MagicWeapon.class);
}
return weapon;
}
}

View File

@ -1,12 +1,10 @@
package inf101.v18.util.generators; package inf101.v18.util.generators;
import java.util.ArrayList; import inf101.v18.util.Generator;
import java.util.List;
import java.util.Random; import java.util.Random;
import inf101.v18.util.IGenerator; public abstract class AbstractGenerator<T> implements Generator<T> {
public abstract class AbstractGenerator<T> implements IGenerator<T> {
private static final Random commonRandom = new Random(); private static final Random commonRandom = new Random();
@Override @Override
@ -14,22 +12,4 @@ public abstract class AbstractGenerator<T> implements IGenerator<T> {
return generate(commonRandom); return generate(commonRandom);
} }
@Override
public List<T> generateEquals(int n) {
return generateEquals(commonRandom, n);
}
@Override
public List<T> generateEquals(Random r, int n) {
long seed = r.nextLong();
List<T> list = new ArrayList<T>();
for (int i = 0; i < n; i++) {
list.add(generate(new Random(seed)));
}
return list;
}
} }

View File

@ -2,7 +2,7 @@ package inf101.v18.util.generators;
import inf101.v18.grid.IArea; import inf101.v18.grid.IArea;
import inf101.v18.grid.RectArea; import inf101.v18.grid.RectArea;
import inf101.v18.util.IGenerator; import inf101.v18.util.Generator;
import java.util.Random; import java.util.Random;
@ -10,11 +10,11 @@ public class AreaGenerator extends AbstractGenerator<IArea> {
/** /**
* Generator for the x-coordinate * Generator for the x-coordinate
*/ */
private final IGenerator<Integer> xGenerator; private final Generator<Integer> xGenerator;
/** /**
* Generator for the y-coordinate * Generator for the y-coordinate
*/ */
private final IGenerator<Integer> yGenerator; private final Generator<Integer> yGenerator;
/** /**
* Generate random Areas between (1,1) and (100,100) * Generate random Areas between (1,1) and (100,100)

View File

@ -4,11 +4,10 @@ import java.util.Random;
/** /**
* Generator for doubles, with uniform distribution. * Generator for doubles, with uniform distribution.
* * <p>
* Will never generate positive or negative infinity or NaN. * Will never generate positive or negative infinity or NaN.
* *
* @author anya * @author anya
*
*/ */
public class DoubleGenerator extends AbstractGenerator<Double> { public class DoubleGenerator extends AbstractGenerator<Double> {
private final double minValue; private final double minValue;
@ -27,8 +26,7 @@ public class DoubleGenerator extends AbstractGenerator<Double> {
* Make a generator for positive doubles from 0 (inclusive) to maxValue * Make a generator for positive doubles from 0 (inclusive) to maxValue
* (exclusive). * (exclusive).
* *
* @param maxValue * @param maxValue The max value, or 0 for the full range of positive doubles
* The max value, or 0 for the full range of positive doubles
*/ */
public DoubleGenerator(double maxValue) { public DoubleGenerator(double maxValue) {
if (maxValue < 0.0) { if (maxValue < 0.0) {
@ -46,17 +44,15 @@ public class DoubleGenerator extends AbstractGenerator<Double> {
/** /**
* Make a generator for numbers from minValue (inclusive) to maxValue * Make a generator for numbers from minValue (inclusive) to maxValue
* (exclusive). * (exclusive).
* * <p>
* Due to the way the numbers are constructed, the range from minValue to * Due to the way the numbers are constructed, the range from minValue to
* maxValue can only span half the total range of available double values. If * maxValue can only span half the total range of available double values. If
* the range is too large the bounds will be divided by two (you can check * the range is too large the bounds will be divided by two (you can check
* whether they range is too large by checking * whether they range is too large by checking
* <code>Double.isInfinite(maxValue-minValue)</code>). * <code>Double.isInfinite(maxValue-minValue)</code>).
* *
* @param minValue * @param minValue The minimum value
* The minimum value * @param maxValue The maximum value, minValue < maxValue
* @param maxValue
* The maximum value, minValue < maxValue
*/ */
public DoubleGenerator(double minValue, double maxValue) { public DoubleGenerator(double minValue, double maxValue) {
if (minValue >= maxValue) { if (minValue >= maxValue) {

Some files were not shown because too many files have changed in this diff Show More