ready!
This commit is contained in:
@ -9,11 +9,6 @@ public interface IPaintLayer {
|
||||
*/
|
||||
void clear();
|
||||
|
||||
/**
|
||||
* Send this layer to the front, so it will be drawn on top of any other layers.
|
||||
*/
|
||||
void layerToFront();
|
||||
|
||||
/**
|
||||
* Send this layer to the back, so it will be drawn behind any other layers.
|
||||
*
|
||||
@ -24,4 +19,10 @@ public interface IPaintLayer {
|
||||
* {@link Screen#getBackgroundContext()}.
|
||||
*/
|
||||
void layerToBack();
|
||||
|
||||
/**
|
||||
* Send this layer to the front, so it will be drawn on top of any other layers.
|
||||
*/
|
||||
void layerToFront();
|
||||
|
||||
}
|
||||
|
@ -108,330 +108,15 @@ public class Screen {
|
||||
public static final int CONFIG_FLAG_NO_AUTOHIDE_MOUSE = 2 << CONFIG_FLAG_SHIFT;
|
||||
public static final int CONFIG_FLAG_DEBUG = 4 << CONFIG_FLAG_SHIFT;
|
||||
private static final int CONFIG_FLAG_MASK = 7;
|
||||
private final double rawCanvasWidth;
|
||||
private final double rawCanvasHeight;
|
||||
private boolean logKeyEvents = false;
|
||||
private final SubScene subScene;
|
||||
private final List<Canvas> canvases = new ArrayList<>();
|
||||
private final Map<IPaintLayer, Canvas> layerCanvases = new IdentityHashMap<>();
|
||||
private final Canvas background;
|
||||
private final Group root;
|
||||
private Paint bgColor = Color.CORNFLOWERBLUE;
|
||||
private int aspect = 0;
|
||||
private double scaling = 0;
|
||||
private double currentScale = 1.0;
|
||||
private double currentFit = 1.0;
|
||||
private double resolutionScale = 1.0;
|
||||
private int maxScale = 1;
|
||||
private Predicate<KeyEvent> keyOverride = null;
|
||||
private Predicate<KeyEvent> keyPressedHandler = null;
|
||||
private Predicate<KeyEvent> keyTypedHandler = null;
|
||||
private Predicate<KeyEvent> keyReleasedHandler = null;
|
||||
private boolean debug = true;
|
||||
private List<Double> aspects;
|
||||
private boolean hideFullScreenMouseCursor = true;
|
||||
private Cursor oldCursor;
|
||||
|
||||
/** @return the keyTypedHandler */
|
||||
public Predicate<KeyEvent> getKeyTypedHandler() {
|
||||
return keyTypedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyTypedHandler
|
||||
* the keyTypedHandler to set
|
||||
*/
|
||||
public void setKeyTypedHandler(Predicate<KeyEvent> keyTypedHandler) {
|
||||
this.keyTypedHandler = keyTypedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyReleasedHandler */
|
||||
public Predicate<KeyEvent> getKeyReleasedHandler() {
|
||||
return keyReleasedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyReleasedHandler
|
||||
* the keyReleasedHandler to set
|
||||
*/
|
||||
public void setKeyReleasedHandler(Predicate<KeyEvent> keyReleasedHandler) {
|
||||
this.keyReleasedHandler = keyReleasedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyOverride */
|
||||
public Predicate<KeyEvent> getKeyOverride() {
|
||||
return keyOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyOverride
|
||||
* the keyOverride to set
|
||||
*/
|
||||
public void setKeyOverride(Predicate<KeyEvent> keyOverride) {
|
||||
this.keyOverride = keyOverride;
|
||||
}
|
||||
|
||||
/** @return the keyHandler */
|
||||
public Predicate<KeyEvent> getKeyPressedHandler() {
|
||||
return keyPressedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyHandler
|
||||
* the keyHandler to set
|
||||
*/
|
||||
public void setKeyPressedHandler(Predicate<KeyEvent> keyHandler) {
|
||||
this.keyPressedHandler = keyHandler;
|
||||
}
|
||||
|
||||
public Screen(double width, double height, double pixWidth, double pixHeight, double canvasWidth,
|
||||
double canvasHeight) {
|
||||
root = new Group();
|
||||
subScene = new SubScene(root, Math.floor(width), Math.floor(height));
|
||||
resolutionScale = pixWidth / canvasWidth;
|
||||
this.rawCanvasWidth = Math.floor(pixWidth);
|
||||
this.rawCanvasHeight = Math.floor(pixHeight);
|
||||
double aspectRatio = width / height;
|
||||
aspect = 0;
|
||||
for (double a : STD_ASPECTS)
|
||||
if (Math.abs(aspectRatio - a) < 0.01) {
|
||||
break;
|
||||
} else {
|
||||
aspect++;
|
||||
}
|
||||
aspects = new ArrayList<>(STD_ASPECTS);
|
||||
if (aspect >= STD_ASPECTS.size()) {
|
||||
aspects.add(aspectRatio);
|
||||
}
|
||||
background = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
background.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
setBackground(bgColor);
|
||||
clearBackground();
|
||||
root.getChildren().add(background);
|
||||
subScene.layoutBoundsProperty()
|
||||
.addListener((ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) -> {
|
||||
recomputeLayout(false);
|
||||
});
|
||||
}
|
||||
|
||||
public void clearBackground() {
|
||||
getBackgroundContext().setFill(bgColor);
|
||||
getBackgroundContext().fillRect(0.0, 0.0, background.getWidth(), background.getHeight());
|
||||
}
|
||||
|
||||
public void cycleAspect() {
|
||||
aspect = (aspect + 1) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomCycle() {
|
||||
scaling++;
|
||||
if (scaling > maxScale)
|
||||
scaling = ((int) scaling) % maxScale;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public void zoomIn() {
|
||||
scaling = Math.min(10, currentScale + 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOut() {
|
||||
scaling = Math.max(0.1, currentScale - 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomFit() {
|
||||
scaling = 0;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOne() {
|
||||
scaling = 1;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void fitScaling() {
|
||||
scaling = 0;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public int getAspect() {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
public GraphicsContext getBackgroundContext() {
|
||||
return background.getGraphicsContext2D();
|
||||
}
|
||||
|
||||
public TurtlePainter createPainter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new TurtlePainter(this, canvas);
|
||||
}
|
||||
|
||||
public Printer createPrinter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new Printer(this, canvas);
|
||||
}
|
||||
|
||||
private void recomputeLayout(boolean resizeWindow) {
|
||||
double xScale = subScene.getWidth() / getRawWidth();
|
||||
double yScale = subScene.getHeight() / getRawHeight();
|
||||
double xMaxScale = getDisplayWidth() / getRawWidth();
|
||||
double yMaxScale = getDisplayHeight() / getRawHeight();
|
||||
currentFit = Math.min(xScale, yScale);
|
||||
maxScale = (int) Math.max(1, Math.ceil(Math.min(xMaxScale, yMaxScale)));
|
||||
currentScale = scaling == 0 ? currentFit : scaling;
|
||||
|
||||
if (resizeWindow) {
|
||||
Scene scene = subScene.getScene();
|
||||
Window window = scene.getWindow();
|
||||
double hBorder = window.getWidth() - scene.getWidth();
|
||||
double vBorder = window.getHeight() - scene.getHeight();
|
||||
double myWidth = getRawWidth() * currentScale;
|
||||
double myHeight = getRawHeight() * currentScale;
|
||||
if (debug)
|
||||
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", //
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
// this.setWidth(myWidth);
|
||||
// this.setHeight(myHeight);
|
||||
window.setWidth(myWidth + hBorder);
|
||||
window.setHeight(myHeight + vBorder);
|
||||
if (debug)
|
||||
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",
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
}
|
||||
|
||||
if (debug)
|
||||
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(),
|
||||
getRawHeight());
|
||||
for (Node n : root.getChildren()) {
|
||||
n.relocate(Math.floor(subScene.getWidth() / 2),
|
||||
Math.floor(subScene.getHeight() / 2 + (rawCanvasHeight - getRawHeight()) * currentScale / 2));
|
||||
n.setTranslateX(-Math.floor(rawCanvasWidth / 2));
|
||||
n.setTranslateY(-Math.floor(rawCanvasHeight / 2));
|
||||
if (debug)
|
||||
System.out.printf(" * layout %1.2fx%1.2f, translate %1.2fx%1.2f%n", n.getLayoutX(), n.getLayoutY(),
|
||||
n.getTranslateX(), n.getTranslateY());
|
||||
n.setScaleX(currentScale);
|
||||
n.setScaleY(currentScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void setAspect(int aspect) {
|
||||
this.aspect = (aspect) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void setBackground(Paint bgColor) {
|
||||
this.bgColor = bgColor;
|
||||
subScene.setFill(bgColor instanceof Color ? ((Color) bgColor).darker() : bgColor);
|
||||
}
|
||||
|
||||
public boolean minimalKeyHandler(KeyEvent event) {
|
||||
KeyCode code = event.getCode();
|
||||
if (event.isShortcutDown()) {
|
||||
if (code == KeyCode.Q) {
|
||||
System.exit(0);
|
||||
} else if (code == KeyCode.PLUS) {
|
||||
zoomIn();
|
||||
return true;
|
||||
} else if (code == KeyCode.MINUS) {
|
||||
zoomOut();
|
||||
return true;
|
||||
}
|
||||
} else if (!(event.isAltDown() || event.isControlDown() || event.isMetaDown() || event.isShiftDown())) {
|
||||
if (code == KeyCode.F11) {
|
||||
setFullScreen(!isFullScreen());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean isFullScreen() {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage)
|
||||
return ((Stage) window).isFullScreen();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public void setFullScreen(boolean fullScreen) {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage) {
|
||||
((Stage) window).setFullScreenExitHint("");
|
||||
((Stage) window).setFullScreen(fullScreen);
|
||||
if (hideFullScreenMouseCursor) {
|
||||
if (fullScreen) {
|
||||
oldCursor = subScene.getScene().getCursor();
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
} else if (oldCursor != null) {
|
||||
subScene.getScene().setCursor(oldCursor);
|
||||
oldCursor = null;
|
||||
} else {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical width of the screen, in pixels.
|
||||
* Get the resolution of this screen, in DPI (pixels per inch).
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
* @return The primary display's DPI
|
||||
* @see javafx.stage.Screen#getDpi()
|
||||
*/
|
||||
public static double getRawDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical height of the screen, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
*/
|
||||
public static double getRawDisplayHeight() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the width of the display, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This takes into account such things as toolbars, menus and such (on a
|
||||
* desktop), and pixel density (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Width of the display
|
||||
* @see javafx.stage.Screen#getVisualBounds()
|
||||
*/
|
||||
public static double getDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getVisualBounds().getWidth();
|
||||
public static double getDisplayDpi() {
|
||||
return javafx.stage.Screen.getPrimary().getDpi();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -449,13 +134,45 @@ public class Screen {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the resolution of this screen, in DPI (pixels per inch).
|
||||
* Get the width of the display, in pixels.
|
||||
*
|
||||
* @return The primary display's DPI
|
||||
* @see javafx.stage.Screen#getDpi()
|
||||
* <p>
|
||||
* This takes into account such things as toolbars, menus and such (on a
|
||||
* desktop), and pixel density (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Width of the display
|
||||
* @see javafx.stage.Screen#getVisualBounds()
|
||||
*/
|
||||
public static double getDisplayDpi() {
|
||||
return javafx.stage.Screen.getPrimary().getDpi();
|
||||
public static double getDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getVisualBounds().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical height of the screen, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
*/
|
||||
public static double getRawDisplayHeight() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the native physical width of the screen, in pixels.
|
||||
*
|
||||
* <p>
|
||||
* This will not include such things as toolbars, menus and such (on a desktop),
|
||||
* or take pixel density into account (e.g., on high resolution mobile devices).
|
||||
*
|
||||
* @return Raw width of the display
|
||||
* @see javafx.stage.Screen#getBounds()
|
||||
*/
|
||||
public static double getRawDisplayWidth() {
|
||||
return javafx.stage.Screen.getPrimary().getBounds().getWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -607,27 +324,174 @@ public class Screen {
|
||||
return pScene;
|
||||
}
|
||||
|
||||
public double getRawWidth() {
|
||||
return rawCanvasWidth;
|
||||
private final double rawCanvasWidth;
|
||||
private final double rawCanvasHeight;
|
||||
private boolean logKeyEvents = false;
|
||||
private final SubScene subScene;
|
||||
private final List<Canvas> canvases = new ArrayList<>();
|
||||
private final Map<IPaintLayer, Canvas> layerCanvases = new IdentityHashMap<>();
|
||||
private final Canvas background;
|
||||
private final Group root;
|
||||
private Paint bgColor = Color.CORNFLOWERBLUE;
|
||||
private int aspect = 0;
|
||||
private double scaling = 0;
|
||||
private double currentScale = 1.0;
|
||||
private double currentFit = 1.0;
|
||||
private double resolutionScale = 1.0;
|
||||
private int maxScale = 1;
|
||||
private Predicate<KeyEvent> keyOverride = null;
|
||||
|
||||
private Predicate<KeyEvent> keyPressedHandler = null;
|
||||
|
||||
private Predicate<KeyEvent> keyTypedHandler = null;
|
||||
|
||||
private Predicate<KeyEvent> keyReleasedHandler = null;
|
||||
|
||||
private boolean debug = true;
|
||||
|
||||
private List<Double> aspects;
|
||||
|
||||
private boolean hideFullScreenMouseCursor = true;
|
||||
|
||||
private Cursor oldCursor;
|
||||
|
||||
public Screen(double width, double height, double pixWidth, double pixHeight, double canvasWidth,
|
||||
double canvasHeight) {
|
||||
root = new Group();
|
||||
subScene = new SubScene(root, Math.floor(width), Math.floor(height));
|
||||
resolutionScale = pixWidth / canvasWidth;
|
||||
this.rawCanvasWidth = Math.floor(pixWidth);
|
||||
this.rawCanvasHeight = Math.floor(pixHeight);
|
||||
double aspectRatio = width / height;
|
||||
aspect = 0;
|
||||
for (double a : STD_ASPECTS)
|
||||
if (Math.abs(aspectRatio - a) < 0.01) {
|
||||
break;
|
||||
} else {
|
||||
aspect++;
|
||||
}
|
||||
aspects = new ArrayList<>(STD_ASPECTS);
|
||||
if (aspect >= STD_ASPECTS.size()) {
|
||||
aspects.add(aspectRatio);
|
||||
}
|
||||
background = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
background.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
setBackground(bgColor);
|
||||
clearBackground();
|
||||
root.getChildren().add(background);
|
||||
subScene.layoutBoundsProperty()
|
||||
.addListener((ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds bounds) -> {
|
||||
recomputeLayout(false);
|
||||
});
|
||||
}
|
||||
|
||||
public double getRawHeight() {
|
||||
return Math.floor(rawCanvasWidth / aspects.get(aspect));
|
||||
public void clearBackground() {
|
||||
getBackgroundContext().setFill(bgColor);
|
||||
getBackgroundContext().fillRect(0.0, 0.0, background.getWidth(), background.getHeight());
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return Math.floor(getRawWidth() / resolutionScale);
|
||||
public TurtlePainter createPainter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new TurtlePainter(this, canvas);
|
||||
}
|
||||
|
||||
public Printer createPrinter() {
|
||||
Canvas canvas = new Canvas(rawCanvasWidth, rawCanvasHeight);
|
||||
canvas.getGraphicsContext2D().scale(resolutionScale, resolutionScale);
|
||||
canvases.add(canvas);
|
||||
root.getChildren().add(canvas);
|
||||
return new Printer(this, canvas);
|
||||
}
|
||||
|
||||
public void cycleAspect() {
|
||||
aspect = (aspect + 1) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void fitScaling() {
|
||||
scaling = 0;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public int getAspect() {
|
||||
return aspect;
|
||||
}
|
||||
|
||||
public GraphicsContext getBackgroundContext() {
|
||||
return background.getGraphicsContext2D();
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return Math.floor(getRawHeight() / resolutionScale);
|
||||
}
|
||||
|
||||
public void moveToFront(IPaintLayer layer) {
|
||||
Canvas canvas = layerCanvases.get(layer);
|
||||
if (canvas != null) {
|
||||
canvas.toFront();
|
||||
/** @return the keyOverride */
|
||||
public Predicate<KeyEvent> getKeyOverride() {
|
||||
return keyOverride;
|
||||
}
|
||||
|
||||
/** @return the keyHandler */
|
||||
public Predicate<KeyEvent> getKeyPressedHandler() {
|
||||
return keyPressedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyReleasedHandler */
|
||||
public Predicate<KeyEvent> getKeyReleasedHandler() {
|
||||
return keyReleasedHandler;
|
||||
}
|
||||
|
||||
/** @return the keyTypedHandler */
|
||||
public Predicate<KeyEvent> getKeyTypedHandler() {
|
||||
return keyTypedHandler;
|
||||
}
|
||||
|
||||
public double getRawHeight() {
|
||||
return Math.floor(rawCanvasWidth / aspects.get(aspect));
|
||||
}
|
||||
|
||||
public double getRawWidth() {
|
||||
return rawCanvasWidth;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return Math.floor(getRawWidth() / resolutionScale);
|
||||
}
|
||||
|
||||
public void hideMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
}
|
||||
|
||||
public boolean isFullScreen() {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage)
|
||||
return ((Stage) window).isFullScreen();
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean minimalKeyHandler(KeyEvent event) {
|
||||
KeyCode code = event.getCode();
|
||||
if (event.isShortcutDown()) {
|
||||
if (code == KeyCode.Q) {
|
||||
System.exit(0);
|
||||
} else if (code == KeyCode.PLUS) {
|
||||
zoomIn();
|
||||
return true;
|
||||
} else if (code == KeyCode.MINUS) {
|
||||
zoomOut();
|
||||
return true;
|
||||
}
|
||||
} else if (!(event.isAltDown() || event.isControlDown() || event.isMetaDown() || event.isShiftDown())) {
|
||||
if (code == KeyCode.F11) {
|
||||
setFullScreen(!isFullScreen());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void moveToBack(IPaintLayer layer) {
|
||||
@ -638,16 +502,93 @@ public class Screen {
|
||||
}
|
||||
}
|
||||
|
||||
public void hideMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
public void moveToFront(IPaintLayer layer) {
|
||||
Canvas canvas = layerCanvases.get(layer);
|
||||
if (canvas != null) {
|
||||
canvas.toFront();
|
||||
}
|
||||
}
|
||||
|
||||
public void showMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
private void recomputeLayout(boolean resizeWindow) {
|
||||
double xScale = subScene.getWidth() / getRawWidth();
|
||||
double yScale = subScene.getHeight() / getRawHeight();
|
||||
double xMaxScale = getDisplayWidth() / getRawWidth();
|
||||
double yMaxScale = getDisplayHeight() / getRawHeight();
|
||||
currentFit = Math.min(xScale, yScale);
|
||||
maxScale = (int) Math.max(1, Math.ceil(Math.min(xMaxScale, yMaxScale)));
|
||||
currentScale = scaling == 0 ? currentFit : scaling;
|
||||
|
||||
if (resizeWindow) {
|
||||
Scene scene = subScene.getScene();
|
||||
Window window = scene.getWindow();
|
||||
double hBorder = window.getWidth() - scene.getWidth();
|
||||
double vBorder = window.getHeight() - scene.getHeight();
|
||||
double myWidth = getRawWidth() * currentScale;
|
||||
double myHeight = getRawHeight() * currentScale;
|
||||
if (debug)
|
||||
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", //
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
// this.setWidth(myWidth);
|
||||
// this.setHeight(myHeight);
|
||||
window.setWidth(myWidth + hBorder);
|
||||
window.setHeight(myHeight + vBorder);
|
||||
if (debug)
|
||||
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",
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getWidth(),
|
||||
javafx.stage.Screen.getPrimary().getVisualBounds().getHeight(), subScene.getWidth(),
|
||||
subScene.getHeight(), scene.getWidth(), scene.getHeight(), window.getWidth(),
|
||||
window.getHeight(), hBorder, vBorder, myWidth, myHeight, getRawWidth(), getRawHeight());
|
||||
}
|
||||
|
||||
if (debug)
|
||||
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(),
|
||||
getRawHeight());
|
||||
for (Node n : root.getChildren()) {
|
||||
n.relocate(Math.floor(subScene.getWidth() / 2),
|
||||
Math.floor(subScene.getHeight() / 2 + (rawCanvasHeight - getRawHeight()) * currentScale / 2));
|
||||
n.setTranslateX(-Math.floor(rawCanvasWidth / 2));
|
||||
n.setTranslateY(-Math.floor(rawCanvasHeight / 2));
|
||||
if (debug)
|
||||
System.out.printf(" * layout %1.2fx%1.2f, translate %1.2fx%1.2f%n", n.getLayoutX(), n.getLayoutY(),
|
||||
n.getTranslateX(), n.getTranslateY());
|
||||
n.setScaleX(currentScale);
|
||||
n.setScaleY(currentScale);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMouseCursor(Cursor cursor) {
|
||||
subScene.getScene().setCursor(cursor);
|
||||
public void setAspect(int aspect) {
|
||||
this.aspect = (aspect) % aspects.size();
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void setBackground(Paint bgColor) {
|
||||
this.bgColor = bgColor;
|
||||
subScene.setFill(bgColor instanceof Color ? ((Color) bgColor).darker() : bgColor);
|
||||
}
|
||||
|
||||
public void setFullScreen(boolean fullScreen) {
|
||||
Window window = subScene.getScene().getWindow();
|
||||
if (window instanceof Stage) {
|
||||
((Stage) window).setFullScreenExitHint("");
|
||||
((Stage) window).setFullScreen(fullScreen);
|
||||
if (hideFullScreenMouseCursor) {
|
||||
if (fullScreen) {
|
||||
oldCursor = subScene.getScene().getCursor();
|
||||
subScene.getScene().setCursor(Cursor.NONE);
|
||||
} else if (oldCursor != null) {
|
||||
subScene.getScene().setCursor(oldCursor);
|
||||
oldCursor = null;
|
||||
} else {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setHideFullScreenMouseCursor(boolean hideIt) {
|
||||
@ -664,4 +605,71 @@ public class Screen {
|
||||
}
|
||||
hideFullScreenMouseCursor = hideIt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyOverride
|
||||
* the keyOverride to set
|
||||
*/
|
||||
public void setKeyOverride(Predicate<KeyEvent> keyOverride) {
|
||||
this.keyOverride = keyOverride;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyHandler
|
||||
* the keyHandler to set
|
||||
*/
|
||||
public void setKeyPressedHandler(Predicate<KeyEvent> keyHandler) {
|
||||
this.keyPressedHandler = keyHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyReleasedHandler
|
||||
* the keyReleasedHandler to set
|
||||
*/
|
||||
public void setKeyReleasedHandler(Predicate<KeyEvent> keyReleasedHandler) {
|
||||
this.keyReleasedHandler = keyReleasedHandler;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param keyTypedHandler
|
||||
* the keyTypedHandler to set
|
||||
*/
|
||||
public void setKeyTypedHandler(Predicate<KeyEvent> keyTypedHandler) {
|
||||
this.keyTypedHandler = keyTypedHandler;
|
||||
}
|
||||
|
||||
public void setMouseCursor(Cursor cursor) {
|
||||
subScene.getScene().setCursor(cursor);
|
||||
}
|
||||
|
||||
public void showMouseCursor() {
|
||||
subScene.getScene().setCursor(Cursor.DEFAULT);
|
||||
}
|
||||
|
||||
public void zoomCycle() {
|
||||
scaling++;
|
||||
if (scaling > maxScale)
|
||||
scaling = ((int) scaling) % maxScale;
|
||||
recomputeLayout(true);
|
||||
}
|
||||
|
||||
public void zoomFit() {
|
||||
scaling = 0;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomIn() {
|
||||
scaling = Math.min(10, currentScale + 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOne() {
|
||||
scaling = 1;
|
||||
recomputeLayout(false);
|
||||
}
|
||||
|
||||
public void zoomOut() {
|
||||
scaling = Math.max(0.1, currentScale - 0.2);
|
||||
recomputeLayout(false);
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Binary file not shown.
@ -132,6 +132,7 @@ public class Direction {
|
||||
return Math.atan2(yDir, xDir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%.2f", toDegrees());
|
||||
}
|
||||
|
@ -5,13 +5,13 @@ import javafx.scene.paint.Paint;
|
||||
|
||||
public interface IPainter extends IPaintLayer {
|
||||
|
||||
IShape shape();
|
||||
|
||||
ITurtle turtle();
|
||||
|
||||
IPainter restore();
|
||||
|
||||
IPainter save();
|
||||
|
||||
IPainter setInk(Paint ink);
|
||||
|
||||
IShape shape();
|
||||
|
||||
ITurtle turtle();
|
||||
}
|
@ -6,13 +6,51 @@ import javafx.scene.shape.Shape;
|
||||
|
||||
public interface IShape {
|
||||
|
||||
void draw();
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(double x, double y);
|
||||
|
||||
void draw(GraphicsContext context);
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(Point xy);
|
||||
|
||||
Shape toFXShape();
|
||||
/**
|
||||
* Set the arc angle for the subsequent draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #arc()}
|
||||
*
|
||||
* @param a
|
||||
* The angle, in degrees
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape angle(double a);
|
||||
|
||||
String toSvg();
|
||||
/**
|
||||
* Draw an arc with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape arc();
|
||||
|
||||
/**
|
||||
* Set the (x,y)-coordinates of the next draw command
|
||||
@ -24,6 +62,160 @@ public interface IShape {
|
||||
*/
|
||||
IShape at(Point p);
|
||||
|
||||
/**
|
||||
* Close the line path, turning it into a polygon.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
IShape close();
|
||||
|
||||
void draw();
|
||||
|
||||
void draw(GraphicsContext context);
|
||||
|
||||
/**
|
||||
* Draw an ellipse with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape ellipse();
|
||||
|
||||
/**
|
||||
* Fill the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fill();
|
||||
|
||||
/**
|
||||
* Set fill colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fillPaint(Paint p);
|
||||
|
||||
/**
|
||||
* Set gravity for the subsequent draw commands
|
||||
*
|
||||
* Gravity determines the point on the shape that will be used for positioning
|
||||
* and rotation.
|
||||
*
|
||||
* @param g
|
||||
* The gravity
|
||||
* @return
|
||||
*/
|
||||
IShape gravity(Gravity g);
|
||||
|
||||
/**
|
||||
* Set the height of the next draw command
|
||||
*
|
||||
* @param h
|
||||
* The height
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape height(double h);
|
||||
|
||||
/**
|
||||
* Set the length of the following draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #line()} and {@link #arc()}
|
||||
*
|
||||
* @param l
|
||||
* The length
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape length(double l);
|
||||
|
||||
/**
|
||||
* Draw a line with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)} (flattened to the horizontal axis, so, e.g.,
|
||||
* {@link Gravity#NORTH} = {@link Gravity#SOUTH} = {@link Gravity#CENTER})
|
||||
* <li>{@link #stroke(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape line();
|
||||
|
||||
/**
|
||||
* Draw a rectangle with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape rectangle();
|
||||
|
||||
/**
|
||||
* Sets rotation for subsequent draw commands.
|
||||
*
|
||||
* <p>
|
||||
* Shapes will be rotate around the {@link #gravity(Gravity)} point.
|
||||
*
|
||||
* @param angle
|
||||
* Rotation in degrees
|
||||
* @return
|
||||
*/
|
||||
IShape rotation(double angle);
|
||||
|
||||
/**
|
||||
* Stroke the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape stroke();
|
||||
|
||||
/**
|
||||
* Set stroke colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape strokePaint(Paint p);
|
||||
|
||||
Shape toFXShape();
|
||||
|
||||
String toSvg();
|
||||
|
||||
/**
|
||||
* Set the width of the next draw command
|
||||
*
|
||||
* @param w
|
||||
* The width
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape width(double w);
|
||||
|
||||
/**
|
||||
* Set the x-coordinate of the next draw command
|
||||
*
|
||||
@ -44,196 +236,4 @@ public interface IShape {
|
||||
*/
|
||||
IShape y(double y);
|
||||
|
||||
/**
|
||||
* Set gravity for the subsequent draw commands
|
||||
*
|
||||
* Gravity determines the point on the shape that will be used for positioning
|
||||
* and rotation.
|
||||
*
|
||||
* @param g
|
||||
* The gravity
|
||||
* @return
|
||||
*/
|
||||
IShape gravity(Gravity g);
|
||||
|
||||
/**
|
||||
* Sets rotation for subsequent draw commands.
|
||||
*
|
||||
* <p>
|
||||
* Shapes will be rotate around the {@link #gravity(Gravity)} point.
|
||||
*
|
||||
* @param angle
|
||||
* Rotation in degrees
|
||||
* @return
|
||||
*/
|
||||
IShape rotation(double angle);
|
||||
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(Point xy);
|
||||
|
||||
/**
|
||||
* Add another point to the line path
|
||||
*
|
||||
* @param xy
|
||||
* @return
|
||||
*/
|
||||
IShape addPoint(double x, double y);
|
||||
|
||||
/**
|
||||
* Close the line path, turning it into a polygon.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
IShape close();
|
||||
|
||||
/**
|
||||
* Draw an ellipse with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape ellipse();
|
||||
|
||||
/**
|
||||
* Draw a rectangle with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #width(double)}, {@link #height(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape rectangle();
|
||||
|
||||
/**
|
||||
* Draw an arc with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)}
|
||||
* <li>{@link #stroke(Paint)}, {@link #fill(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape arc();
|
||||
|
||||
/**
|
||||
* Draw a line with the current drawing parameters
|
||||
*
|
||||
* <p>
|
||||
* Relevant parameters:
|
||||
* <li>{@link #at(Point)}, {@link #x(double)}, {@link #gravity(double)}
|
||||
* <li>{@link #length(double)}
|
||||
* <li>{@link #angle(double)}
|
||||
* <li>{@link #gravity(Gravity)} (flattened to the horizontal axis, so, e.g.,
|
||||
* {@link Gravity#NORTH} = {@link Gravity#SOUTH} = {@link Gravity#CENTER})
|
||||
* <li>{@link #stroke(Paint)}
|
||||
* <li>{@link #rotation(double)}
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape line();
|
||||
|
||||
/**
|
||||
* Set the arc angle for the subsequent draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #arc()}
|
||||
*
|
||||
* @param a
|
||||
* The angle, in degrees
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape angle(double a);
|
||||
|
||||
/**
|
||||
* Set fill colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fillPaint(Paint p);
|
||||
|
||||
/**
|
||||
* Fill the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape fill();
|
||||
|
||||
/**
|
||||
* Set the length of the following draw commands
|
||||
*
|
||||
* <p>
|
||||
* For use with {@link #line()} and {@link #arc()}
|
||||
*
|
||||
* @param l
|
||||
* The length
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape length(double l);
|
||||
|
||||
/**
|
||||
* Stroke the current shape
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape stroke();
|
||||
|
||||
/**
|
||||
* Set stroke colour for the subsequent draw commands
|
||||
*
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape strokePaint(Paint p);
|
||||
|
||||
/**
|
||||
* Set the width of the next draw command
|
||||
*
|
||||
* @param w
|
||||
* The width
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape width(double w);
|
||||
|
||||
/**
|
||||
* Set the height of the next draw command
|
||||
*
|
||||
* @param h
|
||||
* The height
|
||||
* @return <code>this</code>, for adding more drawing parameters or issuing the
|
||||
* draw command
|
||||
*/
|
||||
IShape height(double h);
|
||||
|
||||
}
|
||||
|
@ -1,36 +1,63 @@
|
||||
package inf101.v18.gfx.gfxmode;
|
||||
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public interface ITurtle extends IPainter {
|
||||
|
||||
<T> T as(Class<T> class1);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a curve
|
||||
*
|
||||
* <p>
|
||||
* The resulting curve is a cubic Bézier curve with the control points located
|
||||
* at <code>getPos().move(getDirection, startControl)</code> and
|
||||
* <code>to.move(Direction.fromDegrees(endAngle+180), endControl)</code>.
|
||||
* <p>
|
||||
* The turtle is left at point <code>to</code>, facing <code>endAngle</code>.
|
||||
* <p>
|
||||
* The turtle will start out moving in its current direction, aiming for a point
|
||||
* <code>startControl</code> pixels away, then smoothly turning towards its
|
||||
* goal. It will approach the <code>to</code> point moving in the direction
|
||||
* <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90°
|
||||
* pointing up).
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* @param startControl
|
||||
* Distance to the starting control point.
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
|
||||
|
||||
void debugTurtle();
|
||||
|
||||
/**
|
||||
* Start drawing a shape at the current turtle position.
|
||||
* Move forward the given distance while drawing a line
|
||||
*
|
||||
* <p>
|
||||
* The shape's default origin and rotation will be set to the turtle's current
|
||||
* position and direction, but can be modified with {@link IShape#at(Point)} and
|
||||
* {@link IShape#rotation(double)}.
|
||||
* <p>
|
||||
* The turtle's position and attributes are unaffected by drawing the shape.
|
||||
*
|
||||
* @return An IDrawParams object for setting up and drawing the shape
|
||||
*/
|
||||
IShape shape();
|
||||
|
||||
/**
|
||||
* Draw a line from the current position to the given position.
|
||||
*
|
||||
* <p>
|
||||
* This method does not change the turtle position.
|
||||
*
|
||||
* @param to
|
||||
* Other end-point of the line
|
||||
* @param dist
|
||||
* Distance to move
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle line(Point to);
|
||||
ITurtle draw(double dist);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
*
|
||||
* @param x
|
||||
* X-position to move to
|
||||
* @param y
|
||||
* Y-position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(double x, double y);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(Point to);
|
||||
|
||||
/**
|
||||
* @return The current angle of the turtle, with 0° pointing to the right and
|
||||
@ -79,57 +106,16 @@ public interface ITurtle extends IPainter {
|
||||
ITurtle jumpTo(Point to);
|
||||
|
||||
/**
|
||||
* Move forward the given distance while drawing a line
|
||||
* Draw a line from the current position to the given position.
|
||||
*
|
||||
* @param dist
|
||||
* Distance to move
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle draw(double dist);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
*
|
||||
* @param x
|
||||
* X-position to move to
|
||||
* @param y
|
||||
* Y-position to move to
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(double x, double y);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a line
|
||||
* <p>
|
||||
* This method does not change the turtle position.
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* Other end-point of the line
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle drawTo(Point to);
|
||||
|
||||
/**
|
||||
* Move to the given position while drawing a curve
|
||||
*
|
||||
* <p>
|
||||
* The resulting curve is a cubic Bézier curve with the control points located
|
||||
* at <code>getPos().move(getDirection, startControl)</code> and
|
||||
* <code>to.move(Direction.fromDegrees(endAngle+180), endControl)</code>.
|
||||
* <p>
|
||||
* The turtle is left at point <code>to</code>, facing <code>endAngle</code>.
|
||||
* <p>
|
||||
* The turtle will start out moving in its current direction, aiming for a point
|
||||
* <code>startControl</code> pixels away, then smoothly turning towards its
|
||||
* goal. It will approach the <code>to</code> point moving in the direction
|
||||
* <code>endAngle</code> (an absolute bearing, with 0° pointing right and 90°
|
||||
* pointing up).
|
||||
*
|
||||
* @param to
|
||||
* Position to move to
|
||||
* @param startControl
|
||||
* Distance to the starting control point.
|
||||
* @return {@code this}, for sending more draw commands
|
||||
*/
|
||||
ITurtle curveTo(Point to, double startControl, double endAngle, double endControl);
|
||||
ITurtle line(Point to);
|
||||
|
||||
/**
|
||||
* Set the size of the turtle's pen
|
||||
@ -141,6 +127,21 @@ public interface ITurtle extends IPainter {
|
||||
*/
|
||||
ITurtle setPenSize(double pixels);
|
||||
|
||||
/**
|
||||
* Start drawing a shape at the current turtle position.
|
||||
*
|
||||
* <p>
|
||||
* The shape's default origin and rotation will be set to the turtle's current
|
||||
* position and direction, but can be modified with {@link IShape#at(Point)} and
|
||||
* {@link IShape#rotation(double)}.
|
||||
* <p>
|
||||
* The turtle's position and attributes are unaffected by drawing the shape.
|
||||
*
|
||||
* @return An IDrawParams object for setting up and drawing the shape
|
||||
*/
|
||||
@Override
|
||||
IShape shape();
|
||||
|
||||
/**
|
||||
* Change direction the given number of degrees (relative to the current
|
||||
* direction).
|
||||
@ -235,6 +236,7 @@ public interface ITurtle extends IPainter {
|
||||
*/
|
||||
ITurtle turnTowards(double degrees, double percent);
|
||||
|
||||
<T> T as(Class<T> class1);
|
||||
double getWidth();
|
||||
|
||||
double getHeight();
|
||||
}
|
@ -88,6 +88,7 @@ public class Point {
|
||||
return new Point(newX, newY);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("(%.2f,%.2f)", x, y);
|
||||
}
|
||||
|
@ -7,158 +7,7 @@ import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.Shape;
|
||||
|
||||
public class ShapePainter implements IShape {
|
||||
private double x = 0, y = 0, w = 0, h = 0, rot = 0, strokeWidth = 0;
|
||||
private List<Double> lineSegments = null;
|
||||
private Paint fill = null;
|
||||
private Paint stroke = null;
|
||||
private Gravity gravity = Gravity.CENTER;
|
||||
private DrawCommand cmd = null;
|
||||
private boolean closed = false;
|
||||
private final GraphicsContext context;
|
||||
|
||||
public ShapePainter(GraphicsContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public ShapePainter at(Point p) {
|
||||
if (p != null) {
|
||||
this.x = p.getX();
|
||||
this.y = p.getY();
|
||||
} else {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter x(double x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter y(double y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter width(double w) {
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter height(double h) {
|
||||
this.h = h;
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape ellipse() {
|
||||
cmd = new DrawEllipse();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape rectangle() {
|
||||
cmd = new DrawRectangle();
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape arc() {
|
||||
// TODO Auto-generated method stub
|
||||
return this;
|
||||
}
|
||||
|
||||
public IShape line() {
|
||||
cmd = new DrawLine();
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter length(double l) {
|
||||
w = l;
|
||||
h = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter angle(double a) {
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter fill() {
|
||||
if (cmd != null)
|
||||
cmd.fill(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter stroke() {
|
||||
if (cmd != null)
|
||||
cmd.stroke(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter fillPaint(Paint p) {
|
||||
fill = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter strokePaint(Paint p) {
|
||||
stroke = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter gravity(Gravity g) {
|
||||
gravity = g;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ShapePainter rotation(double angle) {
|
||||
rot = angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void draw() {
|
||||
draw(context);
|
||||
}
|
||||
|
||||
public Shape toFXShape() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
public String toSvg() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
private abstract static class DrawCommand {
|
||||
public void stroke(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setStroke(p.stroke);
|
||||
if (p.strokeWidth != 0)
|
||||
ctx.setLineWidth(p.strokeWidth);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
strokeIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
public void fill(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setFill(p.fill);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
fillIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
protected abstract void strokeIt(GraphicsContext ctx, ShapePainter p);
|
||||
|
||||
protected abstract void fillIt(GraphicsContext ctx, ShapePainter p);
|
||||
|
||||
// public abstract Shape toFXShape(DrawParams p);
|
||||
//
|
||||
// public abstract String toSvg(DrawParams p);
|
||||
|
||||
protected double calcX(Gravity g, double w) {
|
||||
switch (g) {
|
||||
default:
|
||||
@ -206,32 +55,70 @@ public class ShapePainter implements IShape {
|
||||
return h / 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawRectangle extends DrawCommand {
|
||||
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
public void fill(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setFill(p.fill);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
fillIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.fillRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
protected abstract void fillIt(GraphicsContext ctx, ShapePainter p);
|
||||
|
||||
// public abstract Shape toFXShape(DrawParams p);
|
||||
//
|
||||
// public abstract String toSvg(DrawParams p);
|
||||
|
||||
public void stroke(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.save();
|
||||
ctx.setStroke(p.stroke);
|
||||
if (p.strokeWidth != 0)
|
||||
ctx.setLineWidth(p.strokeWidth);
|
||||
ctx.translate(p.x, p.y);
|
||||
if (p.rot != 0)
|
||||
ctx.rotate(-p.rot);
|
||||
strokeIt(ctx, p);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
protected abstract void strokeIt(GraphicsContext ctx, ShapePainter p);
|
||||
}
|
||||
|
||||
private static class DrawEllipse extends DrawCommand {
|
||||
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.fillOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeOval(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawLine extends DrawCommand {
|
||||
|
||||
@Override
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
if (p.lineSegments != null) {
|
||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||
double xs[] = new double[nPoints];
|
||||
double ys[] = new double[nPoints];
|
||||
xs[0] = -calcX(p.gravity, p.w);
|
||||
ys[0] = -calcY(p.gravity, p.h);
|
||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||
}
|
||||
ctx.fillPolygon(xs, ys, nPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
if (p.lineSegments == null) {
|
||||
double x = -calcX(p.gravity, p.w);
|
||||
@ -253,37 +140,37 @@ public class ShapePainter implements IShape {
|
||||
ctx.strokePolyline(xs, ys, nPoints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class DrawRectangle extends DrawCommand {
|
||||
|
||||
@Override
|
||||
public void fillIt(GraphicsContext ctx, ShapePainter p) {
|
||||
if (p.lineSegments != null) {
|
||||
int nPoints = (p.lineSegments.size() / 2) + 1;
|
||||
double xs[] = new double[nPoints];
|
||||
double ys[] = new double[nPoints];
|
||||
xs[0] = -calcX(p.gravity, p.w);
|
||||
ys[0] = -calcY(p.gravity, p.h);
|
||||
for (int i = 0; i < p.lineSegments.size(); i++) {
|
||||
xs[i] = p.lineSegments.get(i * 2) - p.x;
|
||||
ys[i] = p.lineSegments.get(i * 2 + 1) - p.y;
|
||||
}
|
||||
ctx.fillPolygon(xs, ys, nPoints);
|
||||
}
|
||||
ctx.fillRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void strokeIt(GraphicsContext ctx, ShapePainter p) {
|
||||
ctx.strokeRect(-calcX(p.gravity, p.w), -calcY(p.gravity, p.h), p.w, p.h);
|
||||
}
|
||||
}
|
||||
|
||||
public void draw(GraphicsContext context) {
|
||||
if (cmd != null) {
|
||||
if (fill != null)
|
||||
cmd.fill(context, this);
|
||||
if (stroke != null)
|
||||
cmd.stroke(context, this);
|
||||
}
|
||||
}
|
||||
private double x = 0, y = 0, w = 0, h = 0, rot = 0, strokeWidth = 0;
|
||||
private List<Double> lineSegments = null;
|
||||
private Paint fill = null;
|
||||
private Paint stroke = null;
|
||||
|
||||
@Override
|
||||
public IShape addPoint(Point xy) {
|
||||
lineSegments.add(xy.getX());
|
||||
lineSegments.add(xy.getY());
|
||||
return this;
|
||||
private Gravity gravity = Gravity.CENTER;
|
||||
|
||||
private DrawCommand cmd = null;
|
||||
|
||||
private boolean closed = false;
|
||||
|
||||
private final GraphicsContext context;
|
||||
|
||||
public ShapePainter(GraphicsContext context) {
|
||||
super();
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -293,9 +180,150 @@ public class ShapePainter implements IShape {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape addPoint(Point xy) {
|
||||
lineSegments.add(xy.getX());
|
||||
lineSegments.add(xy.getY());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter angle(double a) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape arc() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter at(Point p) {
|
||||
if (p != null) {
|
||||
this.x = p.getX();
|
||||
this.y = p.getY();
|
||||
} else {
|
||||
this.x = 0;
|
||||
this.y = 0;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape close() {
|
||||
closed = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw() {
|
||||
draw(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(GraphicsContext context) {
|
||||
if (cmd != null) {
|
||||
if (fill != null)
|
||||
cmd.fill(context, this);
|
||||
if (stroke != null)
|
||||
cmd.stroke(context, this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape ellipse() {
|
||||
cmd = new DrawEllipse();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter fill() {
|
||||
if (cmd != null)
|
||||
cmd.fill(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter fillPaint(Paint p) {
|
||||
fill = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter gravity(Gravity g) {
|
||||
gravity = g;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter height(double h) {
|
||||
this.h = h;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter length(double l) {
|
||||
w = l;
|
||||
h = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape line() {
|
||||
cmd = new DrawLine();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape rectangle() {
|
||||
cmd = new DrawRectangle();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter rotation(double angle) {
|
||||
rot = angle;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter stroke() {
|
||||
if (cmd != null)
|
||||
cmd.stroke(context, this);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter strokePaint(Paint p) {
|
||||
stroke = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Shape toFXShape() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSvg() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter width(double w) {
|
||||
this.w = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter x(double x) {
|
||||
this.x = x;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ShapePainter y(double y) {
|
||||
this.y = y;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
}
|
||||
|
||||
private final Screen screen;
|
||||
private final double width;
|
||||
private final double height;
|
||||
private final GraphicsContext context;
|
||||
private final List<TurtleState> stateStack = new ArrayList<>();
|
||||
|
||||
@ -39,68 +41,45 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
private final Canvas canvas;
|
||||
private boolean path = false;
|
||||
|
||||
public TurtlePainter(double width, double height, Canvas canvas) {
|
||||
screen = null;
|
||||
if (canvas == null)
|
||||
canvas = new Canvas(width, height);
|
||||
this.canvas = canvas;
|
||||
this.context = canvas.getGraphicsContext2D();
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
stateStack.add(new TurtleState());
|
||||
state.dir = new Direction(1.0, 0.0);
|
||||
state.pos = new Point(width / 2, height / 2);
|
||||
}
|
||||
|
||||
public TurtlePainter(Screen screen, Canvas canvas) {
|
||||
this.screen = screen;
|
||||
this.canvas = canvas;
|
||||
this.context = canvas.getGraphicsContext2D();
|
||||
this.width = screen.getWidth();
|
||||
this.height = screen.getHeight();
|
||||
stateStack.add(new TurtleState());
|
||||
state.dir = new Direction(1.0, 0.0);
|
||||
state.pos = new Point(screen.getWidth() / 2, screen.getHeight() / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T as(Class<T> clazz) {
|
||||
if (clazz == GraphicsContext.class)
|
||||
return (T) context;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
context.clearRect(0, 0, getWidth(), getHeight());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugTurtle() {
|
||||
System.err.println("[" + state.pos + " " + state.dir + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape shape() {
|
||||
ShapePainter s = new ShapePainter(context);
|
||||
return s.at(getPos()).rotation(getAngle()).strokePaint(state.ink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle line(Point to) {
|
||||
// context.save();
|
||||
context.setStroke(state.ink);
|
||||
context.setLineWidth(state.penSize);
|
||||
context.strokeLine(state.pos.getX(), state.pos.getY(), to.getX(), to.getY());
|
||||
// context.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAngle() {
|
||||
return state.dir.toDegrees();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getDirection() {
|
||||
return state.dir;
|
||||
}
|
||||
|
||||
public double getHeight() {
|
||||
return screen.getHeight();
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPos() {
|
||||
return state.pos;
|
||||
}
|
||||
|
||||
public double getWidth() {
|
||||
return screen.getWidth();
|
||||
}
|
||||
|
||||
public ITurtle curveTo(Point to, double startControl, double endAngle, double endControl) {
|
||||
Point c1 = state.pos.move(state.dir, startControl);
|
||||
Point c2 = to.move(Direction.fromDegrees(endAngle + 180), endControl);
|
||||
@ -123,6 +102,64 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void debugTurtle() {
|
||||
System.err.println("[" + state.pos + " " + state.dir + "]");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle draw(double dist) {
|
||||
Point to = state.pos.move(state.dir, dist);
|
||||
return drawTo(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(double x, double y) {
|
||||
Point to = new Point(x, y);
|
||||
return drawTo(to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(Point to) {
|
||||
if (path) {
|
||||
context.lineTo(to.getX(), to.getY());
|
||||
} else {
|
||||
line(to);
|
||||
}
|
||||
state.inDir = state.dir;
|
||||
state.pos = to;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getAngle() {
|
||||
return state.dir.toDegrees();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Direction getDirection() {
|
||||
return state.dir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point getPos() {
|
||||
return state.pos;
|
||||
}
|
||||
|
||||
public Screen getScreen() {
|
||||
return screen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getWidth() {
|
||||
return width;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle jump(double dist) {
|
||||
state.inDir = state.dir;
|
||||
@ -145,26 +182,24 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle draw(double dist) {
|
||||
Point to = state.pos.move(state.dir, dist);
|
||||
return drawTo(to);
|
||||
public void layerToBack() {
|
||||
if (screen != null)
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(double x, double y) {
|
||||
Point to = new Point(x, y);
|
||||
return drawTo(to);
|
||||
public void layerToFront() {
|
||||
if (screen != null)
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle drawTo(Point to) {
|
||||
if (path) {
|
||||
context.lineTo(to.getX(), to.getY());
|
||||
} else {
|
||||
line(to);
|
||||
}
|
||||
state.inDir = state.dir;
|
||||
state.pos = to;
|
||||
public ITurtle line(Point to) {
|
||||
// context.save();
|
||||
context.setStroke(state.ink);
|
||||
context.setLineWidth(state.penSize);
|
||||
context.strokeLine(state.pos.getX(), state.pos.getY(), to.getX(), to.getY());
|
||||
// context.restore();
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -196,6 +231,12 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IShape shape() {
|
||||
ShapePainter s = new ShapePainter(context);
|
||||
return s.at(getPos()).rotation(getAngle()).strokePaint(state.ink);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turn(double degrees) {
|
||||
state.dir = state.dir.turn(degrees);
|
||||
@ -245,29 +286,12 @@ public class TurtlePainter implements IPaintLayer, ITurtle {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToFront() {
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToBack() {
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ITurtle turtle() {
|
||||
TurtlePainter painter = new TurtlePainter(screen, canvas);
|
||||
TurtlePainter painter = screen != null ? new TurtlePainter(screen, canvas)
|
||||
: new TurtlePainter(width, height, canvas);
|
||||
painter.stateStack.set(0, new TurtleState(state));
|
||||
return painter;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T as(Class<T> clazz) {
|
||||
if (clazz == GraphicsContext.class)
|
||||
return (T) context;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,10 +6,26 @@ import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class BlocksAndBoxes {
|
||||
public enum PixelOrder implements Iterable<Integer> {
|
||||
LEFT_TO_RIGHT(8, 4, 2, 1), RIGHT_TO_LEFT(4, 8, 1, 2), LEFT_TO_RIGHT_UPWARDS(2, 1, 8,
|
||||
4), RIGHT_TO_LEFT_UPWARDS(1, 2, 4, 8);
|
||||
|
||||
private List<Integer> order;
|
||||
|
||||
private PixelOrder(int a, int b, int c, int d) {
|
||||
order = Arrays.asList(a, b, c, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return order.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
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 BLOCK_EMPTY = " ";
|
||||
public static final String BLOCK_BOTTOM_RIGHT = "▗";
|
||||
@ -27,7 +43,19 @@ public class BlocksAndBoxes {
|
||||
public static final String BLOCK_REVERSE_BOTTOM_LEFT = "▜";
|
||||
public static final String BLOCK_REVERSE_BOTTOM_RIGHT = "▛";
|
||||
public static final String BLOCK_FULL = "█";
|
||||
public static final String BLOCK_HALF = "▒";
|
||||
|
||||
public static final String BLOCK_HALF = "▒";;
|
||||
|
||||
public static String blockAddOne(String s, PixelOrder order) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i >= 0) {
|
||||
for (int bit : order) {
|
||||
if ((i & bit) == 0)
|
||||
return unicodeBlocks[i | bit];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string into a Unicode block graphics character.
|
||||
@ -122,7 +150,20 @@ public class BlocksAndBoxes {
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"Expected length 4 string of \" \" and \"*\", or \"++++\", got \"" + s + "\"");
|
||||
};
|
||||
}
|
||||
|
||||
public static String blockCompact(String s) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i > 0) {
|
||||
int lower = i & 3;
|
||||
int upper = (i >> 2) & 3;
|
||||
i = (lower | upper) | ((lower & upper) << 2);
|
||||
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
|
||||
// + "\n");
|
||||
return BlocksAndBoxes.unicodeBlocks[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String blockCompose(String b1, String b2, BiFunction<Integer, Integer, Integer> op) {
|
||||
int i1 = unicodeBlocksString.indexOf(b1);
|
||||
@ -146,33 +187,6 @@ public class BlocksAndBoxes {
|
||||
return unicodeBlocks[op.apply(i1, i2)];
|
||||
}
|
||||
|
||||
public enum PixelOrder implements Iterable<Integer> {
|
||||
LEFT_TO_RIGHT(8, 4, 2, 1), RIGHT_TO_LEFT(4, 8, 1, 2), LEFT_TO_RIGHT_UPWARDS(2, 1, 8,
|
||||
4), RIGHT_TO_LEFT_UPWARDS(1, 2, 4, 8);
|
||||
|
||||
private List<Integer> order;
|
||||
|
||||
private PixelOrder(int a, int b, int c, int d) {
|
||||
order = Arrays.asList(a, b, c, d);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Integer> iterator() {
|
||||
return order.iterator();
|
||||
}
|
||||
}
|
||||
|
||||
public static String blockAddOne(String s, PixelOrder order) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i >= 0) {
|
||||
for (int bit : order) {
|
||||
if ((i & bit) == 0)
|
||||
return unicodeBlocks[i | bit];
|
||||
}
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String blockRemoveOne(String s, PixelOrder order) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i >= 0) {
|
||||
@ -184,17 +198,4 @@ public class BlocksAndBoxes {
|
||||
return s;
|
||||
}
|
||||
|
||||
public static String blockCompact(String s) {
|
||||
int i = BlocksAndBoxes.unicodeBlocksString.indexOf(s);
|
||||
if (i > 0) {
|
||||
int lower = i & 3;
|
||||
int upper = (i >> 2) & 3;
|
||||
i = (lower | upper) | ((lower & upper) << 2);
|
||||
// System.out.println("Compact: " + s + " -> " + BlocksAndBoxes.unicodeBlocks[i]
|
||||
// + "\n");
|
||||
return BlocksAndBoxes.unicodeBlocks[i];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ public class DemoPages {
|
||||
printer.println(s);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,16 @@ public class Printer implements IPaintLayer {
|
||||
4.0000, -8.5000, 1.5000, 1.0000, true);
|
||||
public static final TextFont FONT_ZXSPECTRUM7 = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE,
|
||||
3.1000, -3.8000, 1.0000, 1.0000, true);
|
||||
|
||||
/**
|
||||
* TTF file can be found here: http://users.teilar.gr/~g1951d/ in this ZIP file:
|
||||
* http://users.teilar.gr/~g1951d/Symbola.zip
|
||||
* <p>
|
||||
* (Put the extracted Symbola.ttf in src/inf101/v18/gfx/fonts/)
|
||||
*/
|
||||
public static final TextFont FONT_SYMBOLA = new TextFont("Symbola.ttf", 26.70, TextMode.CHAR_BOX_SIZE, -0.4000,
|
||||
-7.6000, 1.35000, 1.0000, true);
|
||||
|
||||
/**
|
||||
* TTF file can be found here:
|
||||
* http://www.kreativekorp.com/software/fonts/c64.shtml
|
||||
@ -49,8 +59,6 @@ public class Printer implements IPaintLayer {
|
||||
*/
|
||||
public static final TextFont FONT_C64 = new TextFont("PetMe64.ttf", 31.50, TextMode.CHAR_BOX_SIZE, 0.0000, -4.000,
|
||||
1.0000, 1.0000, true);
|
||||
Color DEFAULT_FILL = Color.BLACK;
|
||||
Color DEFAULT_STROKE = Color.TRANSPARENT;
|
||||
private static final Paint DEFAULT_BACKGROUND = Color.TRANSPARENT;
|
||||
private static final TextMode DEFAULT_MODE = TextMode.MODE_40X22;
|
||||
|
||||
@ -67,6 +75,10 @@ public class Printer implements IPaintLayer {
|
||||
return r;
|
||||
}
|
||||
|
||||
Color DEFAULT_FILL = Color.BLACK;
|
||||
|
||||
Color DEFAULT_STROKE = Color.TRANSPARENT;
|
||||
|
||||
private TextMode textMode;
|
||||
private Color fill;
|
||||
private Color stroke;
|
||||
@ -88,6 +100,10 @@ public class Printer implements IPaintLayer {
|
||||
|
||||
private int csiMode = 0;
|
||||
|
||||
public Printer(double width, double height) {
|
||||
this(null, new Canvas(width, height));
|
||||
}
|
||||
|
||||
public Printer(Screen screen, Canvas page) {
|
||||
this.screen = screen;
|
||||
this.textPage = page;
|
||||
@ -142,17 +158,6 @@ public class Printer implements IPaintLayer {
|
||||
});
|
||||
}
|
||||
|
||||
private void drawChar(int x, int y, Char c) {
|
||||
if (c != null) {
|
||||
GraphicsContext context = textPage.getGraphicsContext2D();
|
||||
|
||||
context.setFill(c.fill);
|
||||
context.setStroke(c.stroke);
|
||||
font.drawTextAt(context, (x - 1) * getCharWidth(), y * getCharHeight(), c.s,
|
||||
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode, c.bg);
|
||||
}
|
||||
}
|
||||
|
||||
private String addToCsiBuffer(String s) {
|
||||
if (csiMode == 1) {
|
||||
switch (s) {
|
||||
@ -207,6 +212,7 @@ public class Printer implements IPaintLayer {
|
||||
y = topMargin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
print("\f");
|
||||
}
|
||||
@ -215,6 +221,31 @@ public class Printer implements IPaintLayer {
|
||||
printAt(x, y, " ");
|
||||
}
|
||||
|
||||
public void clearLine(int y) {
|
||||
y = constrainY(y);
|
||||
if (y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
Arrays.fill(lineBuffer.get(y - 1), null);
|
||||
redrawTextPage();
|
||||
}
|
||||
}
|
||||
|
||||
public void clearRegion(int x, int y, int width, int height) {
|
||||
if (x > getLineWidth() || y > getPageHeight())
|
||||
return;
|
||||
int x2 = Math.min(x + width - 1, getLineWidth());
|
||||
int y2 = Math.min(y + height - 1, getPageHeight());
|
||||
if (x2 < 1 || y2 < 1)
|
||||
return;
|
||||
int x1 = Math.max(1, x);
|
||||
int y1 = Math.max(1, y);
|
||||
// Char fillWith = new Char("*", Color.BLACK, Color.GREEN, Color.TRANSPARENT,
|
||||
// 0);
|
||||
for (int i = y1; i <= y2; i++) {
|
||||
Arrays.fill(lineBuffer.get(i - 1), x1 - 1, x2, null);
|
||||
}
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
private int constrainX(int x) {
|
||||
return x; // Math.min(LINE_WIDTH_HIRES, Math.max(1, x));
|
||||
}
|
||||
@ -249,26 +280,52 @@ public class Printer implements IPaintLayer {
|
||||
|
||||
public void cycleMode(boolean adjustDisplayAspect) {
|
||||
textMode = textMode.nextMode();
|
||||
if (adjustDisplayAspect)
|
||||
if (adjustDisplayAspect && screen != null)
|
||||
screen.setAspect(textMode.getAspect());
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public void drawCharCells() {
|
||||
GraphicsContext context = screen.getBackgroundContext();
|
||||
screen.clearBackground();
|
||||
double w = getCharWidth();
|
||||
double h = getCharHeight();
|
||||
context.save();
|
||||
context.setGlobalBlendMode(BlendMode.EXCLUSION);
|
||||
context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.3));
|
||||
for (int x = 0; x < getLineWidth(); x++) {
|
||||
for (int y = 0; y < getPageHeight(); y++) {
|
||||
if ((x + y) % 2 == 0)
|
||||
context.fillRect(x * w, y * h, w, h);
|
||||
}
|
||||
private void drawChar(int x, int y, Char c) {
|
||||
if (c != null) {
|
||||
GraphicsContext context = textPage.getGraphicsContext2D();
|
||||
|
||||
context.setFill(c.fill);
|
||||
context.setStroke(c.stroke);
|
||||
font.drawTextAt(context, (x - 1) * getCharWidth(), y * getCharHeight(), c.s,
|
||||
textMode.getCharWidth() / textMode.getCharBoxSize(), c.mode, c.bg);
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
|
||||
public void drawCharCells() {
|
||||
if (screen != null) {
|
||||
GraphicsContext context = screen.getBackgroundContext();
|
||||
screen.clearBackground();
|
||||
double w = getCharWidth();
|
||||
double h = getCharHeight();
|
||||
context.save();
|
||||
context.setGlobalBlendMode(BlendMode.EXCLUSION);
|
||||
context.setFill(Color.WHITE.deriveColor(0.0, 1.0, 1.0, 0.1));
|
||||
for (int x = 0; x < getLineWidth(); x++) {
|
||||
for (int y = 0; y < getPageHeight(); y++) {
|
||||
if ((x + y) % 2 == 0)
|
||||
context.fillRect(x * w, y * h, w, h);
|
||||
}
|
||||
}
|
||||
context.restore();
|
||||
}
|
||||
}
|
||||
|
||||
public Color getBackground(int x, int y) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
Color bg = Color.TRANSPARENT;
|
||||
if (c != null && c.bg instanceof Color)
|
||||
bg = (Color) c.bg;
|
||||
else if (background instanceof Color)
|
||||
bg = (Color) background;
|
||||
return bg;
|
||||
}
|
||||
|
||||
public boolean getBold() {
|
||||
@ -305,41 +362,6 @@ public class Printer implements IPaintLayer {
|
||||
return fill;
|
||||
}
|
||||
|
||||
public Color getBackground(int x, int y) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
Color bg = Color.TRANSPARENT;
|
||||
if (c != null && c.bg instanceof Color)
|
||||
bg = (Color) c.bg;
|
||||
else if (background instanceof Color)
|
||||
bg = (Color) background;
|
||||
return bg;
|
||||
}
|
||||
|
||||
public void setBackground(int x, int y, Paint bg) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.bg = bg;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(int x, int y, Color fill) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.fill = fill;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public TextFont getFont() {
|
||||
return font;
|
||||
}
|
||||
@ -367,6 +389,10 @@ public class Printer implements IPaintLayer {
|
||||
return (videoAttrs & TextFont.ATTR_INVERSE) != 0;
|
||||
}
|
||||
|
||||
public TextMode getTextMode() {
|
||||
return textMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the topMargin
|
||||
*/
|
||||
@ -390,6 +416,20 @@ public class Printer implements IPaintLayer {
|
||||
return !getChar(x, y).equals(" ");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToBack() {
|
||||
if (screen != null) {
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToFront() {
|
||||
if (screen != null) {
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(int deltaX, int deltaY) {
|
||||
x = constrainX(x + deltaX);
|
||||
y = constrainYOrScroll(y + deltaY);
|
||||
@ -550,6 +590,17 @@ public class Printer implements IPaintLayer {
|
||||
return old;
|
||||
}
|
||||
|
||||
public void setBackground(int x, int y, Paint bg) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.bg = bg;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void setBackground(Paint bgColor) {
|
||||
this.background = bgColor != null ? bgColor : DEFAULT_BACKGROUND;
|
||||
}
|
||||
@ -570,6 +621,17 @@ public class Printer implements IPaintLayer {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void setColor(int x, int y, Color fill) {
|
||||
Char c = null;
|
||||
if (x > 0 && x <= TextMode.LINE_WIDTH_MAX && y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
c = lineBuffer.get(y - 1)[x - 1];
|
||||
}
|
||||
if (c != null) {
|
||||
c.fill = fill;
|
||||
drawChar(x, y, c);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFill(Color fill) {
|
||||
this.fill = fill != null ? fill : DEFAULT_FILL;
|
||||
}
|
||||
@ -615,6 +677,19 @@ public class Printer implements IPaintLayer {
|
||||
this.stroke = stroke != null ? stroke : DEFAULT_STROKE;
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode) {
|
||||
setTextMode(mode, false);
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode, boolean adjustDisplayAspect) {
|
||||
if (mode == null)
|
||||
throw new IllegalArgumentException();
|
||||
textMode = mode;
|
||||
if (adjustDisplayAspect && screen != null)
|
||||
screen.setAspect(textMode.getAspect());
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public void setTopMargin() {
|
||||
this.topMargin = y;
|
||||
}
|
||||
@ -627,10 +702,6 @@ public class Printer implements IPaintLayer {
|
||||
this.topMargin = constrainY(topMargin);
|
||||
}
|
||||
|
||||
public void setVideoAttrs(int attr) {
|
||||
videoAttrs = attr;
|
||||
}
|
||||
|
||||
public void setVideoAttrDisabled(int attr) {
|
||||
videoAttrs &= ~attr;
|
||||
}
|
||||
@ -639,42 +710,12 @@ public class Printer implements IPaintLayer {
|
||||
videoAttrs |= attr;
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode) {
|
||||
setTextMode(mode, false);
|
||||
}
|
||||
|
||||
public void setTextMode(TextMode mode, boolean adjustDisplayAspect) {
|
||||
if (mode == null)
|
||||
throw new IllegalArgumentException();
|
||||
textMode = mode;
|
||||
if (adjustDisplayAspect)
|
||||
screen.setAspect(textMode.getAspect());
|
||||
redrawTextPage();
|
||||
}
|
||||
|
||||
public TextMode getTextMode() {
|
||||
return textMode;
|
||||
public void setVideoAttrs(int attr) {
|
||||
videoAttrs = attr;
|
||||
}
|
||||
|
||||
public void unplot(int x, int y) {
|
||||
plot(x, y, (a, b) -> a & ~b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToFront() {
|
||||
screen.moveToFront(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void layerToBack() {
|
||||
screen.moveToBack(this);
|
||||
}
|
||||
|
||||
public void clearLine(int y) {
|
||||
y = constrainY(y);
|
||||
if (y > 0 && y <= TextMode.PAGE_HEIGHT_MAX) {
|
||||
Arrays.fill(lineBuffer.get(y - 1), null);
|
||||
redrawTextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import javafx.scene.paint.Paint;
|
||||
import javafx.scene.shape.StrokeLineCap;
|
||||
import javafx.scene.text.Font;
|
||||
import javafx.scene.transform.Affine;
|
||||
import javafx.scene.transform.Transform;
|
||||
|
||||
/**
|
||||
* TextFont – for grid-based text / character graphics
|
||||
@ -935,7 +936,7 @@ public class TextFont {
|
||||
target.save(); // save 3
|
||||
if ((mode & ATTR_ITALIC) != 0) {
|
||||
target.translate(-0.2, 0);
|
||||
target.transform(new Affine(Affine.shear(-0.2, 0)));
|
||||
target.transform(new Affine(Transform.shear(-0.2, 0)));
|
||||
}
|
||||
setGraphicsContext(target, xScaleFactor);
|
||||
if (fill != null)
|
||||
|
@ -9,7 +9,7 @@ import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
|
||||
public class TextFontAdjuster extends Application {
|
||||
private static final String FONT_NAME = "PetMe64.ttf";
|
||||
// private static final String FONT_NAME = "PetMe64.ttf";
|
||||
// new TextFont(FONT_NAME, 22.2, TextModes.CHAR_BOX_SIZE, 0.0, 0.0, 1.0, 1.0);
|
||||
private static TextFontAdjuster demo;
|
||||
|
||||
@ -21,8 +21,9 @@ public class TextFontAdjuster extends Application {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
private TextFont textFont = new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000, -3.8000, 1.0000,
|
||||
1.0000, true);
|
||||
private TextFont textFont = Printer.FONT_SYMBOLA;//
|
||||
// new TextFont("ZXSpectrum-7.otf", 22.00, TextMode.CHAR_BOX_SIZE, 3.1000,
|
||||
// -3.8000, 1.0000, 1.0000, true);
|
||||
private Screen screen;
|
||||
|
||||
private boolean paused;
|
||||
@ -107,11 +108,9 @@ public class TextFontAdjuster extends Application {
|
||||
textFont.getFont().getSize()));
|
||||
printer.println(String.format(" xTr=%-1.1f yTr=%-1.1f xSc=%-1.1f ySc=%-1.1f ", textFont.getxTranslate(),
|
||||
textFont.getyTranslate(), textFont.getxScale(), textFont.getyScale()));
|
||||
// System.out.printf("new TextFont(\"%s\", %1.2f, Printer.CHAR_HEIGHT, %1.4f,
|
||||
// %1.4f, %1.4f, %1.4f)%n", FONT_NAME,
|
||||
// textFont.getSize(), textFont.getxTranslate(), textFont.getyTranslate(),
|
||||
// textFont.getxScale(),
|
||||
// textFont.getyScale());
|
||||
System.out.printf("new TextFont(\"%s\", %1.2f, Printer.CHAR_HEIGHT, %1.4f, %1.4f, %1.4f, %1.4f)%n",
|
||||
textFont.getFont().getName(), textFont.getSize(), textFont.getxTranslate(), textFont.getyTranslate(),
|
||||
textFont.getxScale(), textFont.getyScale());
|
||||
|
||||
printer.moveTo(1, 15);
|
||||
}
|
||||
|
@ -43,17 +43,15 @@ public interface IArea extends Iterable<ILocation> {
|
||||
boolean equals(Object other);
|
||||
|
||||
/**
|
||||
* Get a location object corresponding to (x,y)
|
||||
* Convert a 1D coordinate to a location
|
||||
* <p>
|
||||
* Returns a location <code>l = fromIndex(i)</code> such that
|
||||
* <code>toIndex(l.getX(), l.getY()) == i</code>.
|
||||
*
|
||||
* @param x
|
||||
* X-coordinate
|
||||
* @param y
|
||||
* Y-coordinate
|
||||
* @return The location object associated with (x,y)
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@link #contains(int, int)} returns false for (x,y)
|
||||
* @param i
|
||||
* @return A location
|
||||
*/
|
||||
ILocation location(int x, int y);
|
||||
ILocation fromIndex(int i);
|
||||
|
||||
/** @return Height of the area */
|
||||
int getHeight();
|
||||
@ -71,6 +69,32 @@ public interface IArea extends Iterable<ILocation> {
|
||||
@Override
|
||||
int hashCode();
|
||||
|
||||
/**
|
||||
* Get a location object corresponding to (x,y)
|
||||
*
|
||||
* @param x
|
||||
* X-coordinate
|
||||
* @param y
|
||||
* Y-coordinate
|
||||
* @return The location object associated with (x,y)
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if {@link #contains(int, int)} returns false for (x,y)
|
||||
*/
|
||||
ILocation location(int x, int y);
|
||||
|
||||
/**
|
||||
* Get all locations in area
|
||||
* <p>
|
||||
* Since IArea is @{@link Iterable}, you can also use directly in a for-loop to
|
||||
* iterate over the locations.
|
||||
* <p>
|
||||
* All locations in the list are guaranteed to be valid according to
|
||||
* {@link #isValid(ILocation)}. The returned list cannot be modified.
|
||||
*
|
||||
* @return An unmodifiable list with all the locations in the area
|
||||
*/
|
||||
List<ILocation> locations();
|
||||
|
||||
/**
|
||||
* Return an object for iterating over all the neighbours of the given position,
|
||||
* suitable for use in a new-style for-loop.
|
||||
@ -91,6 +115,23 @@ public interface IArea extends Iterable<ILocation> {
|
||||
*/
|
||||
Iterable<ILocation> neighboursOf(ILocation pos);
|
||||
|
||||
/** @return A (possibly) parallel {@link Stream} of all locations in the area */
|
||||
Stream<ILocation> parallelStream();
|
||||
|
||||
/** @return A {@link Stream} of all locations in the area */
|
||||
Stream<ILocation> stream();
|
||||
|
||||
/**
|
||||
* Convert a 2D coordinate to 1D
|
||||
*
|
||||
* @param x
|
||||
* X-coordinate
|
||||
* @param y
|
||||
* Y-coordinate
|
||||
* @return x + y*getWidth()
|
||||
*/
|
||||
int toIndex(int x, int y);
|
||||
|
||||
@Override
|
||||
String toString();
|
||||
|
||||
@ -117,45 +158,4 @@ public interface IArea extends Iterable<ILocation> {
|
||||
* @return True if the area wraps around vertically
|
||||
*/
|
||||
boolean wrapsVertically();
|
||||
|
||||
/** @return A {@link Stream} of all locations in the area */
|
||||
Stream<ILocation> stream();
|
||||
|
||||
/** @return A (possibly) parallel {@link Stream} of all locations in the area */
|
||||
Stream<ILocation> parallelStream();
|
||||
|
||||
/**
|
||||
* Convert a 2D coordinate to 1D
|
||||
*
|
||||
* @param x
|
||||
* X-coordinate
|
||||
* @param y
|
||||
* Y-coordinate
|
||||
* @return x + y*getWidth()
|
||||
*/
|
||||
int toIndex(int x, int y);
|
||||
|
||||
/**
|
||||
* Convert a 1D coordinate to a location
|
||||
* <p>
|
||||
* Returns a location <code>l = fromIndex(i)</code> such that
|
||||
* <code>toIndex(l.getX(), l.getY()) == i</code>.
|
||||
*
|
||||
* @param i
|
||||
* @return A location
|
||||
*/
|
||||
ILocation fromIndex(int i);
|
||||
|
||||
/**
|
||||
* Get all locations in area
|
||||
* <p>
|
||||
* Since IArea is @{@link Iterable}, you can also use directly in a for-loop to
|
||||
* iterate over the locations.
|
||||
* <p>
|
||||
* All locations in the list are guaranteed to be valid
|
||||
* according to {@link #isValid(ILocation)}. The returned list cannot be modified.
|
||||
*
|
||||
* @return An unmodifiable list with all the locations in the area
|
||||
*/
|
||||
List<ILocation> locations();
|
||||
}
|
||||
|
@ -13,123 +13,19 @@ public interface IGrid<T> extends Iterable<T> {
|
||||
IGrid<T> copy();
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param x
|
||||
* The column of the cell to get the contents of.
|
||||
* @param y
|
||||
* The row of the cell to get contents of.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
T get(int x, int y);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(pos)
|
||||
*/
|
||||
T get(ILocation pos);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param x
|
||||
* The column of the cell to get the contents of.
|
||||
* @param y
|
||||
* 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.
|
||||
*/
|
||||
T getOrDefault(int x, int y, T defaultResult);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* 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
|
||||
* contents == null.
|
||||
*/
|
||||
T getOrDefault(ILocation pos, T defaultResult);
|
||||
|
||||
/** @return The height of the grid. */
|
||||
int getHeight();
|
||||
|
||||
/** @return The width of the grid. */
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid.
|
||||
* Create a parallel {@link Stream} with all the elements in this grid.
|
||||
*
|
||||
* Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() <
|
||||
* getHeight().
|
||||
*
|
||||
* @param pos
|
||||
* A position
|
||||
* @return true if the position is within the grid
|
||||
* @return A stream
|
||||
* @see {@link java.util.Collection#parallelStream()}
|
||||
*/
|
||||
boolean isValid(ILocation pos);
|
||||
Stream<T> elementParallelStream();
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid.
|
||||
* Create a {@link Stream} with all the elements in this grid.
|
||||
*
|
||||
* Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight().
|
||||
*
|
||||
* @param x
|
||||
* an x coordinate
|
||||
* @param y
|
||||
* an y coordinate
|
||||
* @return true if the (x,y) position is within the grid
|
||||
* @return A stream
|
||||
*/
|
||||
boolean isValid(int x, int y);
|
||||
|
||||
/**
|
||||
* Set the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* The contents the cell is to have.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
void set(int x, int y, T element);
|
||||
|
||||
/**
|
||||
* Set the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* The contents the cell is to have.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(pos)
|
||||
*/
|
||||
void set(ILocation pos, T element);
|
||||
Stream<T> elementStream();
|
||||
|
||||
/**
|
||||
* Initialise the contents of all cells using an initialiser function.
|
||||
@ -161,6 +57,108 @@ public interface IGrid<T> extends Iterable<T> {
|
||||
*/
|
||||
void fill(T element);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(pos)
|
||||
*/
|
||||
T get(ILocation pos);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param x
|
||||
* The column of the cell to get the contents of.
|
||||
* @param y
|
||||
* The row of the cell to get contents of.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
T get(int x, int y);
|
||||
|
||||
IArea getArea();
|
||||
|
||||
/** @return The height of the grid. */
|
||||
int getHeight();
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* 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
|
||||
* contents == null.
|
||||
*/
|
||||
T getOrDefault(ILocation pos, T defaultResult);
|
||||
|
||||
/**
|
||||
* Get the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param x
|
||||
* The column of the cell to get the contents of.
|
||||
* @param y
|
||||
* 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.
|
||||
*/
|
||||
T getOrDefault(int x, int y, T defaultResult);
|
||||
|
||||
/** @return The width of the grid. */
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid.
|
||||
*
|
||||
* Valid coordinates are 0 <= pos.getX() < getWidth(), 0 <= pos.getY() <
|
||||
* getHeight().
|
||||
*
|
||||
* @param pos
|
||||
* A position
|
||||
* @return true if the position is within the grid
|
||||
*/
|
||||
boolean isValid(ILocation pos);
|
||||
|
||||
/**
|
||||
* Check if coordinates are valid.
|
||||
*
|
||||
* Valid coordinates are 0 <= x < getWidth(), 0 <= y < getHeight().
|
||||
*
|
||||
* @param x
|
||||
* an x coordinate
|
||||
* @param y
|
||||
* an y coordinate
|
||||
* @return true if the (x,y) position is within the grid
|
||||
*/
|
||||
boolean isValid(int x, int y);
|
||||
|
||||
/**
|
||||
* Create a parallel {@link Stream} with all the locations in this grid.
|
||||
* <p>
|
||||
* All locations obtained through the stream are guaranteed to be valid
|
||||
* according to {@link #isValid(ILocation)}.
|
||||
*
|
||||
* @return A stream
|
||||
* @see {@link java.util.Collection#parallelStream()}
|
||||
*/
|
||||
Stream<ILocation> locationParallelStream();
|
||||
|
||||
/**
|
||||
* Iterate over all grid locations
|
||||
* <p>
|
||||
@ -185,31 +183,33 @@ public interface IGrid<T> extends Iterable<T> {
|
||||
Stream<ILocation> locationStream();
|
||||
|
||||
/**
|
||||
* Create a parallel {@link Stream} with all the locations in this grid.
|
||||
* <p>
|
||||
* All locations obtained through the stream are guaranteed to be valid
|
||||
* according to {@link #isValid(ILocation)}.
|
||||
*
|
||||
* @return A stream
|
||||
* @see {@link java.util.Collection#parallelStream()}
|
||||
* Set the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* The contents the cell is to have.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(pos)
|
||||
*/
|
||||
Stream<ILocation> locationParallelStream();
|
||||
void set(ILocation pos, T element);
|
||||
|
||||
/**
|
||||
* Create a {@link Stream} with all the elements in this grid.
|
||||
*
|
||||
* @return A stream
|
||||
* Set the contents of the cell in the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* The contents the cell is to have.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
Stream<T> elementStream();
|
||||
|
||||
/**
|
||||
* Create a parallel {@link Stream} with all the elements in this grid.
|
||||
*
|
||||
* @return A stream
|
||||
* @see {@link java.util.Collection#parallelStream()}
|
||||
*/
|
||||
Stream<T> elementParallelStream();
|
||||
|
||||
IArea getArea();
|
||||
void set(int x, int y, T element);
|
||||
|
||||
}
|
@ -32,6 +32,21 @@ import java.util.List;
|
||||
*/
|
||||
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
|
||||
@ -41,6 +56,22 @@ public interface ILocation extends IPosition {
|
||||
*/
|
||||
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>
|
||||
@ -56,37 +87,6 @@ public interface ILocation extends IPosition {
|
||||
*/
|
||||
ILocation go(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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
IArea getArea();
|
||||
|
||||
int getIndex();
|
||||
|
||||
/**
|
||||
* Find the grid cells between this location (exclusive) and another location
|
||||
* (inclusive).
|
||||
|
@ -5,6 +5,20 @@ import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||
/**
|
||||
* Add to the cell at the given location.
|
||||
*
|
||||
* @param loc
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* An element to be added to the cell.
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default void add(ILocation loc, T element) {
|
||||
get(loc).add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the cell at the given x,y location.
|
||||
*
|
||||
@ -23,35 +37,23 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to the cell at the given location.
|
||||
* Check if a cell contains an element.
|
||||
*
|
||||
*
|
||||
* @param loc
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* An element to be added to the cell.
|
||||
* The (x,y) position of the grid cell
|
||||
* @param predicate
|
||||
* Search predicate.
|
||||
* @return true if an element matching the predicate was found
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default void add(ILocation loc, T element) {
|
||||
get(loc).add(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cell contains an element.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* An element to search for.
|
||||
* @return true if element is at the given location
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
default boolean contains(int x, int y, T element) {
|
||||
return get(x, y).contains(element);
|
||||
default boolean contains(ILocation loc, Predicate<T> predicate) {
|
||||
for (T t : get(loc)) {
|
||||
if (predicate.test(t))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -91,6 +93,24 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||
/**
|
||||
* Check if a cell contains an element.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param element
|
||||
* An element to search for.
|
||||
* @return true if element is at the given location
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
default boolean contains(int x, int y, T element) {
|
||||
return get(x, y).contains(element);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in a cell that match the predicate
|
||||
*
|
||||
*
|
||||
* @param loc
|
||||
* The (x,y) position of the grid cell
|
||||
@ -100,30 +120,44 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default boolean contains(ILocation loc, Predicate<T> predicate) {
|
||||
for (T t : get(loc)) {
|
||||
if (predicate.test(t))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default List<T> get(ILocation loc, Predicate<T> predicate) {
|
||||
return get(loc).stream().filter(predicate).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the cell at the given x,y location.
|
||||
* Check if a cell contains an element.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell
|
||||
* @param element
|
||||
* An element to be removed from the cell
|
||||
* @return Number of elements removed
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param predicate
|
||||
* Search predicate.
|
||||
* @return true if an element matching the predicate was found
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
default int remove(int x, int y, T element) {
|
||||
return get(x, y).remove(element) ? 1 : 0;
|
||||
default List<T> get(int x, int y, Predicate<T> predicate) {
|
||||
return get(this.getArea().location(x, y), predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the cell at the given location.
|
||||
*
|
||||
* @param loc
|
||||
* The location of the grid cell
|
||||
* @param predicate
|
||||
* Predicate which should be true for elements to be removed
|
||||
* @return Number of elements removed
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default int remove(ILocation loc, Predicate<T> predicate) {
|
||||
List<T> list = get(loc);
|
||||
int s = list.size();
|
||||
get(loc).removeIf(predicate);
|
||||
return s - list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,55 +194,21 @@ public interface IMultiGrid<T> extends IGrid<List<T>> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an element from the cell at the given location.
|
||||
*
|
||||
* @param loc
|
||||
* The location of the grid cell
|
||||
* @param predicate
|
||||
* Predicate which should be true for elements to be removed
|
||||
* @return Number of elements removed
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default int remove(ILocation loc, Predicate<T> predicate) {
|
||||
List<T> list = get(loc);
|
||||
int s = list.size();
|
||||
get(loc).removeIf(predicate);
|
||||
return s - list.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a cell contains an element.
|
||||
* Remove an element from the cell at the given x,y location.
|
||||
*
|
||||
* 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().
|
||||
*
|
||||
* @param pos
|
||||
* The (x,y) position of the grid cell to get the contents of.
|
||||
* @param predicate
|
||||
* Search predicate.
|
||||
* @return true if an element matching the predicate was found
|
||||
* The (x,y) position of the grid cell
|
||||
* @param element
|
||||
* An element to be removed from the cell
|
||||
* @return Number of elements removed
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(x,y)
|
||||
*/
|
||||
default List<T> get(int x, int y, Predicate<T> predicate) {
|
||||
return get(this.getArea().location(x, y), predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all elements in a cell that match the predicate
|
||||
*
|
||||
*
|
||||
* @param loc
|
||||
* The (x,y) position of the grid cell
|
||||
* @param predicate
|
||||
* Search predicate.
|
||||
* @return true if an element matching the predicate was found
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if !isValid(loc)
|
||||
*/
|
||||
default List<T> get(ILocation loc, Predicate<T> predicate) {
|
||||
return get(loc).stream().filter(predicate).collect(Collectors.toList());
|
||||
default int remove(int x, int y, T element) {
|
||||
return get(x, y).remove(element) ? 1 : 0;
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,42 @@ package inf101.v18.grid;
|
||||
|
||||
public interface IPosition {
|
||||
|
||||
/**
|
||||
* @param obj
|
||||
* Another object
|
||||
* @return true if obj is also an IPosition, and the x and y coordinates are
|
||||
* equal
|
||||
*/
|
||||
@Override
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* Find the Euclidian distance between the midpoint of this position and another
|
||||
* position.
|
||||
*
|
||||
* 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> =
|
||||
* 1. For example, the distance from (0,0) to (3,5) is √(3²+5²) = 5.83.
|
||||
*
|
||||
* @param other
|
||||
* @return Euclidian distance between this and other's midpoints
|
||||
*/
|
||||
double geometricDistanceTo(IPosition other);
|
||||
|
||||
/**
|
||||
* Gets the x-coordinate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int getX();
|
||||
|
||||
/**
|
||||
* Gets the y-coordinate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int getY();
|
||||
|
||||
/**
|
||||
* Find the distance in grid cells to another location.
|
||||
*
|
||||
@ -19,6 +55,9 @@ public interface IPosition {
|
||||
*/
|
||||
int gridDistanceTo(IPosition other);
|
||||
|
||||
@Override
|
||||
int hashCode();
|
||||
|
||||
/**
|
||||
* Find the number of non-diagonal steps needed to go from this location the
|
||||
* other location.
|
||||
@ -37,44 +76,8 @@ public interface IPosition {
|
||||
*/
|
||||
int stepDistanceTo(IPosition other);
|
||||
|
||||
/**
|
||||
* Find the Euclidian distance between the midpoint of this position and another
|
||||
* position.
|
||||
*
|
||||
* 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> =
|
||||
* 1. For example, the distance from (0,0) to (3,5) is √(3²+5²) = 5.83.
|
||||
*
|
||||
* @param other
|
||||
* @return Euclidian distance between this and other's midpoints
|
||||
*/
|
||||
double geometricDistanceTo(IPosition other);
|
||||
|
||||
/**
|
||||
* @param obj
|
||||
* Another object
|
||||
* @return true if obj is also an IPosition, and the x and y coordinates are
|
||||
* equal
|
||||
*/
|
||||
boolean equals(Object obj);
|
||||
|
||||
/**
|
||||
* Gets the x-coordinate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int getX();
|
||||
|
||||
/**
|
||||
* Gets the y-coordinate
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
int getY();
|
||||
|
||||
int hashCode();
|
||||
|
||||
/** @return Position as a string, "(x,y)" */
|
||||
@Override
|
||||
String toString();
|
||||
|
||||
}
|
@ -11,18 +11,6 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
private final IArea area;
|
||||
private final List<T> cells;
|
||||
|
||||
/**
|
||||
* Construct a grid with the given dimensions.
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
* @param initElement
|
||||
* What the cells should initially hold (possibly null)
|
||||
*/
|
||||
public MyGrid(int width, int height, T initElement) {
|
||||
this(new RectArea(width, height), initElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a grid with the given dimensions.
|
||||
*
|
||||
@ -40,8 +28,16 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
* @param initialiser
|
||||
* The initialiser function
|
||||
*/
|
||||
public MyGrid(int width, int height, Function<ILocation, T> initialiser) {
|
||||
this(new RectArea(width, height), initialiser);
|
||||
public MyGrid(IArea area, Function<ILocation, T> initialiser) {
|
||||
if (area == null || initialiser == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
this.area = area;
|
||||
this.cells = new ArrayList<T>(area.getSize());
|
||||
for (ILocation loc : area) {
|
||||
cells.add(initialiser.apply(loc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -81,16 +77,20 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
* @param initialiser
|
||||
* The initialiser function
|
||||
*/
|
||||
public MyGrid(IArea area, Function<ILocation, T> initialiser) {
|
||||
if (area == null || initialiser == null) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
public MyGrid(int width, int height, Function<ILocation, T> initialiser) {
|
||||
this(new RectArea(width, height), initialiser);
|
||||
}
|
||||
|
||||
this.area = area;
|
||||
this.cells = new ArrayList<T>(area.getSize());
|
||||
for (ILocation loc : area) {
|
||||
cells.add(initialiser.apply(loc));
|
||||
}
|
||||
/**
|
||||
* Construct a grid with the given dimensions.
|
||||
*
|
||||
* @param width
|
||||
* @param height
|
||||
* @param initElement
|
||||
* What the cells should initially hold (possibly null)
|
||||
*/
|
||||
public MyGrid(int width, int height, T initElement) {
|
||||
this(new RectArea(width, height), initElement);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,79 +101,13 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int x, int y) {
|
||||
return cells.get(area.toIndex(x, y));
|
||||
public Stream<T> elementParallelStream() {
|
||||
return cells.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return area.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return area.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(int x, int y, T elem) {
|
||||
cells.set(area.toIndex(x, y), elem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return cells.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(ILocation loc) {
|
||||
if (loc.getArea() == area)
|
||||
return cells.get(loc.getIndex());
|
||||
else
|
||||
return cells.get(area.toIndex(loc.getX(), loc.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getOrDefault(int x, int y, T defaultResult) {
|
||||
T r = null;
|
||||
if (isValid(x, y))
|
||||
r = get(x, y);
|
||||
if (r != null)
|
||||
return r;
|
||||
else
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getOrDefault(ILocation loc, T defaultResult) {
|
||||
if (loc.getArea() == area) {
|
||||
T r = cells.get(loc.getIndex());
|
||||
if (r != null)
|
||||
return r;
|
||||
else
|
||||
return defaultResult;
|
||||
} else {
|
||||
return getOrDefault(loc.getX(), loc.getY(), defaultResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(ILocation loc) {
|
||||
return loc.getArea() == area || area.contains(loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(int x, int y) {
|
||||
return area.contains(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(ILocation loc, T element) {
|
||||
if (loc.getArea() == area) {
|
||||
cells.set(loc.getIndex(), element);
|
||||
} else {
|
||||
set(loc.getX(), loc.getY(), element);
|
||||
}
|
||||
public Stream<T> elementStream() {
|
||||
return cells.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -193,6 +127,78 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(ILocation loc) {
|
||||
if (loc.getArea() == area)
|
||||
return cells.get(loc.getIndex());
|
||||
else
|
||||
return cells.get(area.toIndex(loc.getX(), loc.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int x, int y) {
|
||||
return cells.get(area.toIndex(x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArea getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return area.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getOrDefault(ILocation loc, T defaultResult) {
|
||||
if (loc.getArea() == area) {
|
||||
T r = cells.get(loc.getIndex());
|
||||
if (r != null)
|
||||
return r;
|
||||
else
|
||||
return defaultResult;
|
||||
} else {
|
||||
return getOrDefault(loc.getX(), loc.getY(), defaultResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getOrDefault(int x, int y, T defaultResult) {
|
||||
T r = null;
|
||||
if (isValid(x, y))
|
||||
r = get(x, y);
|
||||
if (r != null)
|
||||
return r;
|
||||
else
|
||||
return defaultResult;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return area.getWidth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(ILocation loc) {
|
||||
return loc.getArea() == area || area.contains(loc.getX(), loc.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid(int x, int y) {
|
||||
return area.contains(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return cells.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> locationParallelStream() {
|
||||
return area.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ILocation> locations() {
|
||||
return area;
|
||||
@ -204,22 +210,16 @@ public class MyGrid<T> implements IGrid<T> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> elementStream() {
|
||||
return cells.stream();
|
||||
public void set(ILocation loc, T element) {
|
||||
if (loc.getArea() == area) {
|
||||
cells.set(loc.getIndex(), element);
|
||||
} else {
|
||||
set(loc.getX(), loc.getY(), element);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArea getArea() {
|
||||
return area;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> locationParallelStream() {
|
||||
return area.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> elementParallelStream() {
|
||||
return cells.parallelStream();
|
||||
public void set(int x, int y, T elem) {
|
||||
cells.set(area.toIndex(x, y), elem);
|
||||
}
|
||||
}
|
||||
|
@ -8,10 +8,164 @@ import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public class RectArea implements IArea {
|
||||
/** A class to represent an (x, y)-location on a grid. */
|
||||
class Location implements ILocation {
|
||||
|
||||
/** value of the x-coordinate */
|
||||
protected final int x;
|
||||
/** value of the y-coordinate */
|
||||
protected final int y;
|
||||
protected final int idx;
|
||||
protected final int edgeMask;
|
||||
|
||||
/**
|
||||
* Main constructor. Initializes a new {@link #Location} objects with the
|
||||
* corresponding values of x and y.
|
||||
*
|
||||
* @param x
|
||||
* X coordinate
|
||||
* @param y
|
||||
* Y coordinate
|
||||
* @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
|
||||
* corresponding edge of the area
|
||||
*/
|
||||
Location(int x, int y, int idx, int edgeMask) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.idx = idx;
|
||||
this.edgeMask = edgeMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ILocation> allNeighbours() {
|
||||
Collection<ILocation> ns = new ArrayList<>(8);
|
||||
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) {
|
||||
if (canGo(d))
|
||||
ns.add(go(d));
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGo(GridDirection dir) {
|
||||
return (edgeMask & dir.getMask()) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ILocation> cardinalNeighbours() {
|
||||
Collection<ILocation> ns = new ArrayList<>(4);
|
||||
for (GridDirection d : GridDirection.FOUR_DIRECTIONS) {
|
||||
if (canGo(d))
|
||||
ns.add(go(d));
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof IPosition)) {
|
||||
return false;
|
||||
}
|
||||
IPosition other = (IPosition) obj;
|
||||
if (x != other.getX()) {
|
||||
return false;
|
||||
}
|
||||
if (y != other.getY()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double geometricDistanceTo(IPosition other) {
|
||||
return Math.sqrt(Math.pow(this.x - other.getX(), 2) + Math.pow(this.y - other.getY(), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArea getArea() {
|
||||
return RectArea.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return idx;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation go(GridDirection dir) {
|
||||
return location(x + dir.getDx(), y + dir.getDy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int gridDistanceTo(IPosition other) {
|
||||
return Math.max(Math.abs(this.x - other.getX()), Math.abs(this.y - other.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ILocation> gridLineTo(ILocation other) {
|
||||
if (!contains(other))
|
||||
throw new IllegalArgumentException();
|
||||
int distX = other.getX() - x;
|
||||
int distY = other.getY() - y;
|
||||
int length = Math.max(Math.abs(distX), Math.abs(distY));
|
||||
List<ILocation> line = new ArrayList<>(length);
|
||||
if (length == 0)
|
||||
return line;
|
||||
double dx = (double) distX / (double) length;
|
||||
double dy = (double) distY / (double) length;
|
||||
// System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length);
|
||||
for (int i = 1; i <= length; i++) {
|
||||
line.add(location(x + (int) Math.round(dx * i), y + (int) Math.round(dy * i)));
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + x;
|
||||
result = prime * result + y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stepDistanceTo(IPosition other) {
|
||||
return Math.abs(this.x - other.getX()) + Math.abs(this.y - other.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(x=" + x + ",y=" + y + ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected final int width;
|
||||
protected final int height;
|
||||
protected final int size;
|
||||
protected final List<ILocation> locs;
|
||||
|
||||
protected final boolean hWrap, vWrap;
|
||||
|
||||
public RectArea(int width, int height) {
|
||||
@ -86,15 +240,16 @@ public class RectArea implements IArea {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return height;
|
||||
public ILocation fromIndex(int i) {
|
||||
if (i >= 0 && i < size)
|
||||
return locs.get(i);
|
||||
else
|
||||
throw new IndexOutOfBoundsException("" + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toIndex(int x, int y) {
|
||||
x = checkX(x);
|
||||
y = checkY(y);
|
||||
return y * width + x;
|
||||
public int getHeight() {
|
||||
return height;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -112,6 +267,41 @@ public class RectArea implements IArea {
|
||||
return locs.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation location(int x, int y) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||
throw new IndexOutOfBoundsException("(" + x + "," + y + ")");
|
||||
int i = x + y * width;
|
||||
return locs.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ILocation> locations() {
|
||||
return locs; // (OK since locs has been through Collections.unmodifiableList())
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ILocation> neighboursOf(ILocation pos) {
|
||||
return pos.allNeighbours();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> parallelStream() {
|
||||
return locs.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> stream() {
|
||||
return locs.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int toIndex(int x, int y) {
|
||||
x = checkX(x);
|
||||
y = checkY(y);
|
||||
return y * width + x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
@ -154,193 +344,4 @@ public class RectArea implements IArea {
|
||||
}
|
||||
}
|
||||
|
||||
/** A class to represent an (x, y)-location on a grid. */
|
||||
class Location implements ILocation {
|
||||
|
||||
/** value of the x-coordinate */
|
||||
protected final int x;
|
||||
/** value of the y-coordinate */
|
||||
protected final int y;
|
||||
protected final int idx;
|
||||
protected final int edgeMask;
|
||||
|
||||
/**
|
||||
* Main constructor. Initializes a new {@link #Location} objects with the
|
||||
* corresponding values of x and y.
|
||||
*
|
||||
* @param x
|
||||
* X coordinate
|
||||
* @param y
|
||||
* Y coordinate
|
||||
* @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
|
||||
* corresponding edge of the area
|
||||
*/
|
||||
Location(int x, int y, int idx, int edgeMask) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.idx = idx;
|
||||
this.edgeMask = edgeMask;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int gridDistanceTo(IPosition other) {
|
||||
return Math.max(Math.abs(this.x - other.getX()), Math.abs(this.y - other.getY()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int stepDistanceTo(IPosition other) {
|
||||
return Math.abs(this.x - other.getX()) + Math.abs(this.y - other.getY());
|
||||
}
|
||||
|
||||
@Override
|
||||
public double geometricDistanceTo(IPosition other) {
|
||||
return Math.sqrt(Math.pow(this.x - other.getX(), 2) + Math.pow(this.y - other.getY(), 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ILocation> gridLineTo(ILocation other) {
|
||||
if (!contains(other))
|
||||
throw new IllegalArgumentException();
|
||||
int distX = other.getX() - x;
|
||||
int distY = other.getY() - y;
|
||||
int length = Math.max(Math.abs(distX), Math.abs(distY));
|
||||
List<ILocation> line = new ArrayList<>(length);
|
||||
if (length == 0)
|
||||
return line;
|
||||
double dx = (double) distX / (double) length;
|
||||
double dy = (double) distY / (double) length;
|
||||
// System.out.printf("dx=%g, dy=%g, length=%d%n", dx, dy, length);
|
||||
for (int i = 1; i <= length; i++) {
|
||||
line.add(location(x + (int) Math.round(dx * i), y + (int) Math.round(dy * i)));
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(obj instanceof IPosition)) {
|
||||
return false;
|
||||
}
|
||||
IPosition other = (IPosition) obj;
|
||||
if (x != other.getX()) {
|
||||
return false;
|
||||
}
|
||||
if (y != other.getY()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + x;
|
||||
result = prime * result + y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGo(GridDirection dir) {
|
||||
return (edgeMask & dir.getMask()) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation go(GridDirection dir) {
|
||||
return location(x + dir.getDx(), y + dir.getDy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "(x=" + x + ",y=" + y + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ILocation> cardinalNeighbours() {
|
||||
Collection<ILocation> ns = new ArrayList<>(4);
|
||||
for (GridDirection d : GridDirection.FOUR_DIRECTIONS) {
|
||||
if (canGo(d))
|
||||
ns.add(go(d));
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ILocation> allNeighbours() {
|
||||
Collection<ILocation> ns = new ArrayList<>(8);
|
||||
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS) {
|
||||
if (canGo(d))
|
||||
ns.add(go(d));
|
||||
}
|
||||
return ns;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IArea getArea() {
|
||||
return RectArea.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndex() {
|
||||
return idx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation location(int x, int y) {
|
||||
if (x < 0 || x >= width || y < 0 || y >= height)
|
||||
throw new IndexOutOfBoundsException("(" + x + "," + y + ")");
|
||||
int i = x + y * width;
|
||||
return locs.get(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> stream() {
|
||||
return locs.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<ILocation> parallelStream() {
|
||||
return locs.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterable<ILocation> neighboursOf(ILocation pos) {
|
||||
return pos.allNeighbours();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation fromIndex(int i) {
|
||||
if (i >= 0 && i < size)
|
||||
return locs.get(i);
|
||||
else
|
||||
throw new IndexOutOfBoundsException("" + i);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ILocation> locations() {
|
||||
return locs; // (OK since locs has been through Collections.unmodifiableList())
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,32 +23,6 @@ public class AreaRetting {
|
||||
|
||||
}
|
||||
|
||||
public void uniqueLocationsProperty(IArea area) {
|
||||
Set<ILocation> set = new HashSet<>();
|
||||
for (ILocation l : area) {
|
||||
assertTrue("Location should be unique: " + l, set.add(l));
|
||||
}
|
||||
}
|
||||
|
||||
public void validLocationsProperty(IArea area) {
|
||||
for (ILocation l : area) {
|
||||
assertTrue("Location should be in area: " + l, area.contains(l));
|
||||
assertTrue("Location should be in area: " + l, area.contains(l.getX(), l.getY()));
|
||||
}
|
||||
}
|
||||
|
||||
public void neighboursDistProperty(ILocation loc) {
|
||||
for (ILocation l : loc.allNeighbours()) {
|
||||
assertEquals(1, loc.gridDistanceTo(l));
|
||||
}
|
||||
}
|
||||
|
||||
public void neighboursSymmetryProperty(ILocation loc) {
|
||||
for (ILocation l : loc.allNeighbours()) {
|
||||
assertTrue("My neighbour should have me as a neighbour", l.allNeighbours().contains(loc));
|
||||
}
|
||||
}
|
||||
|
||||
public void canGoProperty(ILocation l, GridDirection dir) {
|
||||
int x = l.getX() + dir.getDx();
|
||||
int y = l.getY() + dir.getDy();
|
||||
@ -61,33 +35,23 @@ public class AreaRetting {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uniqueLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
uniqueLocationsProperty(area);
|
||||
}
|
||||
public void distanceProperty(ILocation l1, ILocation l2) {
|
||||
assertEquals(l1.gridDistanceTo(l2), l2.gridDistanceTo(l1));
|
||||
assertEquals(l1.stepDistanceTo(l2), l2.stepDistanceTo(l1));
|
||||
assertTrue(l1.gridDistanceTo(l2) <= l1.stepDistanceTo(l2));
|
||||
assertTrue(l1.gridDistanceTo(l2) <= l1.geometricDistanceTo(l2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
validLocationsProperty(area);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void locationsTest() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
for (ILocation l : area) {
|
||||
neighboursDistProperty(l);
|
||||
neighboursSymmetryProperty(l);
|
||||
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS)
|
||||
canGoProperty(l, d);
|
||||
}
|
||||
public void gridLineProperty(ILocation l1, ILocation l2) {
|
||||
// System.out.println(l1.toString() + " .. " + l2.toString());
|
||||
List<ILocation> line = l1.gridLineTo(l2);
|
||||
assertEquals(l1.gridDistanceTo(l2), line.size());
|
||||
ILocation last = l1;
|
||||
for (ILocation l : line) {
|
||||
assertEquals(1, last.gridDistanceTo(l));
|
||||
last = l;
|
||||
}
|
||||
assertEquals(l2, last);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -104,22 +68,58 @@ public class AreaRetting {
|
||||
}
|
||||
}
|
||||
|
||||
public void gridLineProperty(ILocation l1, ILocation l2) {
|
||||
// System.out.println(l1.toString() + " .. " + l2.toString());
|
||||
List<ILocation> line = l1.gridLineTo(l2);
|
||||
assertEquals(l1.gridDistanceTo(l2), line.size());
|
||||
ILocation last = l1;
|
||||
for (ILocation l : line) {
|
||||
assertEquals(1, last.gridDistanceTo(l));
|
||||
last = l;
|
||||
@Test
|
||||
public void locationsTest() {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
for (ILocation l : area) {
|
||||
neighboursDistProperty(l);
|
||||
neighboursSymmetryProperty(l);
|
||||
for (GridDirection d : GridDirection.EIGHT_DIRECTIONS)
|
||||
canGoProperty(l, d);
|
||||
}
|
||||
}
|
||||
assertEquals(l2, last);
|
||||
}
|
||||
|
||||
public void distanceProperty(ILocation l1, ILocation l2) {
|
||||
assertEquals(l1.gridDistanceTo(l2), l2.gridDistanceTo(l1));
|
||||
assertEquals(l1.stepDistanceTo(l2), l2.stepDistanceTo(l1));
|
||||
assertTrue(l1.gridDistanceTo(l2) <= l1.stepDistanceTo(l2));
|
||||
assertTrue(l1.gridDistanceTo(l2) <= l1.geometricDistanceTo(l2));
|
||||
public void neighboursDistProperty(ILocation loc) {
|
||||
for (ILocation l : loc.allNeighbours()) {
|
||||
assertEquals(1, loc.gridDistanceTo(l));
|
||||
}
|
||||
}
|
||||
|
||||
public void neighboursSymmetryProperty(ILocation loc) {
|
||||
for (ILocation l : loc.allNeighbours()) {
|
||||
assertTrue("My neighbour should have me as a neighbour", l.allNeighbours().contains(loc));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uniqueLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
uniqueLocationsProperty(area);
|
||||
}
|
||||
}
|
||||
|
||||
public void uniqueLocationsProperty(IArea area) {
|
||||
Set<ILocation> set = new HashSet<>();
|
||||
for (ILocation l : area) {
|
||||
assertTrue("Location should be unique: " + l, set.add(l));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
IArea area = areaGen.generate();
|
||||
validLocationsProperty(area);
|
||||
}
|
||||
}
|
||||
|
||||
public void validLocationsProperty(IArea area) {
|
||||
for (ILocation l : area) {
|
||||
assertTrue("Location should be in area: " + l, area.contains(l));
|
||||
assertTrue("Location should be in area: " + l, area.contains(l.getX(), l.getY()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,37 +19,6 @@ public class GridRetting {
|
||||
private IGenerator<String> strGen = new StringGenerator();
|
||||
private IGenerator<IGrid<String>> gridGen = new GridGenerator<String>(strGen);
|
||||
|
||||
/** A set on (x1,y1) doesn't affect a get on a different (x2,y2) */
|
||||
public <T> void setGetIndependentProperty(IGrid<T> grid, ILocation l1, ILocation l2, T val) {
|
||||
if (!l1.equals(l2)) {
|
||||
T s = grid.get(l2);
|
||||
grid.set(l1, val);
|
||||
assertEquals(s, grid.get(l2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setGetIndependentTest() {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
IGrid<String> grid = gridGen.generate();
|
||||
IGenerator<ILocation> lGen = new LocationGenerator(grid.getArea());
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ILocation l1 = lGen.generate();
|
||||
ILocation l2 = lGen.generate();
|
||||
String s = strGen.generate();
|
||||
|
||||
setGetIndependentProperty(grid, l1, l2, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** get(x,y) is val after set(x, y, val) */
|
||||
public <T> void setGetProperty(IGrid<T> grid, ILocation l, T val) {
|
||||
grid.set(l, val);
|
||||
assertEquals(val, grid.get(l));
|
||||
}
|
||||
|
||||
public <T> void fillProperty1(IGrid<T> grid, T val) {
|
||||
grid.fill(val);
|
||||
for (ILocation l : grid.locations()) {
|
||||
@ -83,12 +52,37 @@ public class GridRetting {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uniqueLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
/** A set on (x1,y1) doesn't affect a get on a different (x2,y2) */
|
||||
public <T> void setGetIndependentProperty(IGrid<T> grid, ILocation l1, ILocation l2, T val) {
|
||||
if (!l1.equals(l2)) {
|
||||
T s = grid.get(l2);
|
||||
grid.set(l1, val);
|
||||
assertEquals(s, grid.get(l2));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setGetIndependentTest() {
|
||||
for (int j = 0; j < 10; j++) {
|
||||
IGrid<String> grid = gridGen.generate();
|
||||
IGenerator<ILocation> lGen = new LocationGenerator(grid.getArea());
|
||||
|
||||
for (int i = 0; i < N; i++) {
|
||||
ILocation l1 = lGen.generate();
|
||||
ILocation l2 = lGen.generate();
|
||||
String s = strGen.generate();
|
||||
|
||||
setGetIndependentProperty(grid, l1, l2, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** get(x,y) is val after set(x, y, val) */
|
||||
public <T> void setGetProperty(IGrid<T> grid, ILocation l, T val) {
|
||||
grid.set(l, val);
|
||||
assertEquals(val, grid.get(l));
|
||||
}
|
||||
|
||||
/** Test that get gives back the same value after set. */
|
||||
@Test
|
||||
public void setGetTest() {
|
||||
@ -105,4 +99,10 @@ public class GridRetting {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void uniqueLocations() {
|
||||
for (int i = 0; i < N / 10; i++) {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -7,14 +7,26 @@ import inf101.v18.gfx.Screen;
|
||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||
import inf101.v18.gfx.textmode.Printer;
|
||||
import inf101.v18.gfx.textmode.TextMode;
|
||||
import inf101.v18.rogue101.game.IGame;
|
||||
import inf101.v18.rogue101.game.Game;
|
||||
import javafx.animation.KeyFrame;
|
||||
import javafx.animation.Timeline;
|
||||
import javafx.application.Application;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.stage.Stage;
|
||||
import javafx.util.Duration;
|
||||
|
||||
public class Main extends Application {
|
||||
// you might want to tune these options
|
||||
public static final boolean MAIN_USE_BACKGROUND_GRID = true;
|
||||
public static final boolean MAP_AUTO_SCALE_ITEM_DRAW = true;
|
||||
public static final boolean MAP_DRAW_ONLY_DIRTY_CELLS = false;
|
||||
public static final TextMode MAIN_TEXT_MODE = TextMode.MODE_80X25;
|
||||
|
||||
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_MSG1 = 22;
|
||||
@ -23,23 +35,61 @@ public class Main extends Application {
|
||||
public static final int LINE_DEBUG = 25;
|
||||
public static final int COLUMN_MAP_END = 40;
|
||||
public static final int COLUMN_RIGHTSIDE_START = 41;
|
||||
private Screen screen;
|
||||
private ITurtle painter;
|
||||
private Printer printer;
|
||||
private IGame game;
|
||||
private boolean grid = true;
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
private Screen screen;
|
||||
private ITurtle painter;
|
||||
private Printer printer;
|
||||
|
||||
private Game game;
|
||||
|
||||
private boolean grid = MAIN_USE_BACKGROUND_GRID;
|
||||
private boolean autoNextTurn = false;
|
||||
private Timeline bigStepTimeline;
|
||||
private Timeline smallStepTimeline;
|
||||
|
||||
private void setup() {
|
||||
//
|
||||
game = new Game(screen, painter, printer);
|
||||
game.draw();
|
||||
|
||||
//
|
||||
bigStepTimeline = new Timeline();
|
||||
bigStepTimeline.setCycleCount(Timeline.INDEFINITE);
|
||||
KeyFrame kf = new KeyFrame(Duration.millis(1000), (ActionEvent event) -> {
|
||||
if (autoNextTurn) {
|
||||
doTurn();
|
||||
}
|
||||
});
|
||||
bigStepTimeline.getKeyFrames().add(kf);
|
||||
// bigStepTimeline.playFromStart();
|
||||
|
||||
//
|
||||
smallStepTimeline = new Timeline();
|
||||
smallStepTimeline.setCycleCount(1);
|
||||
kf = new KeyFrame(Duration.millis(1), (ActionEvent event) -> {
|
||||
doTurn();
|
||||
});
|
||||
smallStepTimeline.getKeyFrames().add(kf);
|
||||
|
||||
// finally, start game
|
||||
doTurn();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws Exception {
|
||||
screen = Screen.startPaintScene(primaryStage, Screen.CONFIG_PIXELS_STEP_SCALED); // Screen.CONFIG_SCREEN_FULLSCREEN_NO_HINT);
|
||||
|
||||
printer = screen.createPrinter();
|
||||
painter = screen.createPainter();
|
||||
printer.setTextMode(TextMode.MODE_80X25, true);
|
||||
printer.setTextMode(MAIN_TEXT_MODE, true);
|
||||
|
||||
// Font with emojis – need separate download
|
||||
// printer.setFont(Printer.FONT_SYMBOLA);
|
||||
|
||||
if (grid)
|
||||
printer.drawCharCells();
|
||||
printer.setAutoScroll(false);
|
||||
@ -52,7 +102,7 @@ public class Main extends Application {
|
||||
printer.cycleMode(true);
|
||||
if (grid)
|
||||
printer.drawCharCells();
|
||||
// game.draw();
|
||||
game.draw();
|
||||
return true;
|
||||
} else if (code == KeyCode.A) {
|
||||
screen.cycleAspect();
|
||||
@ -75,8 +125,7 @@ public class Main extends Application {
|
||||
}
|
||||
} else if (code == KeyCode.ENTER) {
|
||||
try {
|
||||
//game.doTurn();
|
||||
// game.draw();
|
||||
doTurn();
|
||||
} catch (Exception e) {
|
||||
printer.printAt(1, 25, "Exception: " + e.getMessage(), Color.RED);
|
||||
e.printStackTrace();
|
||||
@ -84,8 +133,8 @@ public class Main extends Application {
|
||||
return true;
|
||||
} else {
|
||||
try {
|
||||
// game.keyPressed(code);
|
||||
// game.draw();
|
||||
game.keyPressed(code);
|
||||
doTurn();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
@ -112,13 +161,24 @@ public class Main extends Application {
|
||||
*/
|
||||
setup();
|
||||
|
||||
// game = new Game(screen, painter, printer);
|
||||
// game.draw();
|
||||
primaryStage.show();
|
||||
|
||||
}
|
||||
|
||||
private void setup() {
|
||||
public void doTurn() {
|
||||
long t = System.currentTimeMillis();
|
||||
boolean waitForPlayer = game.doTurn();
|
||||
if (DEBUG_TIME)
|
||||
System.out.println("doTurn() took " + (System.currentTimeMillis() - t) + "ms");
|
||||
long t2 = System.currentTimeMillis();
|
||||
game.draw();
|
||||
if (DEBUG_TIME) {
|
||||
System.out.println("draw() took " + (System.currentTimeMillis() - t2) + "ms");
|
||||
System.out.println("doTurn()+draw() took " + (System.currentTimeMillis() - t) + "ms");
|
||||
System.out.println("waiting for player? " + waitForPlayer);
|
||||
}
|
||||
if (!waitForPlayer)
|
||||
smallStepTimeline.playFromStart(); // this will kickstart a new turn in a few milliseconds
|
||||
}
|
||||
|
||||
public static String BUILTIN_MAP = "40 20\n" //
|
||||
|
@ -1,32 +1,132 @@
|
||||
package inf101.v18.rogue101.events;
|
||||
|
||||
import inf101.v18.rogue101.game.IGame;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
|
||||
/**
|
||||
* Example implementation of events – could be used to have more complex
|
||||
* behaviour than just attack/defend/get damaged/pickup/drop.
|
||||
*
|
||||
* @author anya
|
||||
*
|
||||
* @param <T>
|
||||
* Relevant extra data for this particular event
|
||||
*/
|
||||
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";
|
||||
private String name;
|
||||
private IItem source;
|
||||
private IItem target;
|
||||
|
||||
/**
|
||||
* 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 IItem source;
|
||||
private final IItem target;
|
||||
private T value;
|
||||
|
||||
private final IGame game;
|
||||
|
||||
/**
|
||||
* Create a new game event
|
||||
* @param name The name is used when checking which event this is / determine its “meaning”
|
||||
* @param source The item that caused the event, or <code>null</code> if unknown/not relevant
|
||||
* @param target The item that receives the event, or <code>null</code> if unknown/not relevant
|
||||
* @param value Arbitrary extra data
|
||||
*
|
||||
* @param name
|
||||
* The name is used when checking which event this is / determine its
|
||||
* “meaning”
|
||||
* @param game
|
||||
* The game, or <code>null</code> if unknown/not relevant
|
||||
* @param source
|
||||
* The item that caused the event, or <code>null</code> if
|
||||
* unknown/not relevant
|
||||
* @param target
|
||||
* The item that receives the event, or <code>null</code> if
|
||||
* unknown/not relevant
|
||||
* @param value
|
||||
* Arbitrary extra data
|
||||
*/
|
||||
public GameEvent(String name, IItem source, IItem target, T value) {
|
||||
public GameEvent(String name, IGame game, IItem source, IItem target, T value) {
|
||||
this.name = name;
|
||||
this.game = game;
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new game event
|
||||
*
|
||||
* @param name
|
||||
* The name is used when checking which event this is / determine its
|
||||
* “meaning”
|
||||
* @param source
|
||||
* The item that caused the event, or <code>null</code> if
|
||||
* unknown/not relevant
|
||||
*/
|
||||
public GameEvent(String name, IItem source) {
|
||||
this(name, null, source, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new game event
|
||||
*
|
||||
* @param name
|
||||
* The name is used when checking which event this is / determine its
|
||||
* “meaning”
|
||||
* @param source
|
||||
* The item that caused the event, or <code>null</code> if
|
||||
* unknown/not relevant
|
||||
* @param value
|
||||
* Arbitrary extra data
|
||||
*/
|
||||
public GameEvent(String name, IItem source, T value) {
|
||||
this(name, null, source, null, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getData() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IGame getGame() {
|
||||
return game;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IItem getSource() {
|
||||
return source;
|
||||
@ -37,16 +137,6 @@ public class GameEvent<T> implements IEvent<T> {
|
||||
return target;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T getData() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setData(T value) {
|
||||
this.value = value;
|
||||
|
@ -1,7 +1,30 @@
|
||||
package inf101.v18.rogue101.events;
|
||||
|
||||
import inf101.v18.rogue101.game.IGame;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
|
||||
/**
|
||||
* An “event” is something that happens in the game, typically due to an actor
|
||||
* taking some action (nut could also be used to transmit user input)
|
||||
* <p>
|
||||
* Storing the particular action and it's associated data in an object means
|
||||
* that we can extend our system with many different kinds of actions – and have
|
||||
* many different reactions to those actions – without having to all of the
|
||||
* "game rule specific" stuff to all our interfaces and classes.
|
||||
* <p>
|
||||
* Our event objects let you store an extra (arbitrary) piece of data, giving
|
||||
* more information about what happened. An event handler can also update this
|
||||
* information, which is a possible way to report back to whomever caused the
|
||||
* event in the first place. The event objects also contain an event name, and
|
||||
* source/targets of the event (where relevant).
|
||||
* <p>
|
||||
* This system is fairly simplistic, and you're not expected to make use of it.
|
||||
*
|
||||
* @author anya
|
||||
*
|
||||
* @param <T>
|
||||
* Type of the extra data
|
||||
*/
|
||||
public interface IEvent<T> {
|
||||
/**
|
||||
* @return Extra data stored in this event
|
||||
@ -13,6 +36,16 @@ public interface IEvent<T> {
|
||||
*/
|
||||
String getEventName();
|
||||
|
||||
/**
|
||||
* Not all events need to be connected to the game, but you can use this if you
|
||||
* need it (e.g., for showing a message, or adding something to the map).
|
||||
* <p>
|
||||
* The result might be null.
|
||||
*
|
||||
* @return The game associated with this event, or null.
|
||||
*/
|
||||
IGame getGame();
|
||||
|
||||
/**
|
||||
* The source is the item that “caused” the event
|
||||
* <p>
|
||||
@ -32,7 +65,8 @@ public interface IEvent<T> {
|
||||
IItem getTarget();
|
||||
|
||||
/**
|
||||
* @param value Extra data to store in this event
|
||||
* @param value
|
||||
* Extra data to store in this event
|
||||
*/
|
||||
void setData(T value);
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
package inf101.v18.rogue101.events;
|
||||
|
||||
public interface IPickedUpEvent extends IEvent {
|
||||
|
||||
}
|
@ -9,21 +9,53 @@ public class Carrot implements IItem {
|
||||
private int hp = getMaxHealth();
|
||||
|
||||
public void doTurn(IGame game) {
|
||||
hp = Math.min(hp+1, getMaxHealth());
|
||||
hp = Math.min(hp + 1, getMaxHealth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
painter.save();
|
||||
painter.turn(75);
|
||||
double size = ((double) hp + getMaxHealth()) / (2.0 * getMaxHealth());
|
||||
double carrotLength = size * h * .6;
|
||||
double carrotWidth = size * h * .4;
|
||||
painter.jump(-carrotLength / 6);
|
||||
painter.shape().ellipse().width(carrotLength).height(carrotWidth).fillPaint(Color.ORANGE).fill();
|
||||
painter.jump(carrotLength / 2);
|
||||
painter.setInk(Color.FORESTGREEN);
|
||||
for (int i = -1; i < 2; i++) {
|
||||
painter.save();
|
||||
painter.turn(20 * i);
|
||||
painter.draw(carrotLength / 3);
|
||||
painter.restore();
|
||||
}
|
||||
painter.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDefence() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealthStatus() {
|
||||
return getCurrentHealth() / getMaxHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxHealth() {
|
||||
return 10;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
public String getName() {
|
||||
return "carrot";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -36,45 +68,15 @@ public class Carrot implements IItem {
|
||||
return "C";
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getHealthStatus() {
|
||||
return getCurrentHealth() / getMaxHealth();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int handleDamage(IGame game, IItem source, int amount) {
|
||||
hp -= amount;
|
||||
|
||||
if(hp < 0) {
|
||||
|
||||
if (hp < 0) {
|
||||
// we're all eaten!
|
||||
hp = -1;
|
||||
}
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
painter.save();
|
||||
painter.turn(75);
|
||||
double size = ((double)hp+getMaxHealth())/(2.0*getMaxHealth());
|
||||
double carrotLength = size*h*.6;
|
||||
double carrotWidth = size*h*.2;
|
||||
painter.jump(-carrotLength/6);
|
||||
painter.shape().ellipse().width(carrotLength).height(carrotWidth).fillPaint(Color.ORANGE).fill();
|
||||
painter.jump(carrotLength/2);
|
||||
painter.setInk(Color.FORESTGREEN);
|
||||
for(int i = -1; i < 2; i++) {
|
||||
painter.save();
|
||||
painter.turn(20*i);
|
||||
painter.draw(carrotLength/3);
|
||||
painter.restore();
|
||||
}
|
||||
painter.restore();
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
public String getName() {
|
||||
return "carrot";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,14 +1,22 @@
|
||||
package inf101.v18.rogue101.examples;
|
||||
|
||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||
import inf101.v18.rogue101.events.GameEvent;
|
||||
import inf101.v18.rogue101.events.IEvent;
|
||||
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;
|
||||
@ -20,8 +28,8 @@ public class ExampleItem implements IItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
public String getName() {
|
||||
return "strange model of an item";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,22 +41,11 @@ public class ExampleItem implements IItem {
|
||||
public String getSymbol() {
|
||||
return "X";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int handleDamage(IGame game, IItem source, int amount) {
|
||||
hp -= amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "strange model of an item";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,8 +6,6 @@ import java.util.List;
|
||||
|
||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||
import inf101.v18.grid.GridDirection;
|
||||
import inf101.v18.rogue101.events.GameEvent;
|
||||
import inf101.v18.rogue101.events.IEvent;
|
||||
import inf101.v18.rogue101.game.IGame;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
import inf101.v18.rogue101.objects.INonPlayer;
|
||||
@ -32,13 +30,13 @@ public class Rabbit implements INonPlayer {
|
||||
if (eaten > 0) {
|
||||
System.out.println("ate carrot worth " + eaten + "!");
|
||||
food += eaten;
|
||||
game.displayMessage("You hear a faint crunching (" + getName() + " eats " + item.getArticle() + " " + item.getName() + ")");
|
||||
game.displayMessage("You hear a faint crunching (" + getName() + " eats " + item.getArticle() + " "
|
||||
+ item.getName() + ")");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// TODO: prøv forskjellige varianter her
|
||||
List<GridDirection> possibleMoves = new ArrayList<>();
|
||||
for (GridDirection dir : GridDirection.FOUR_DIRECTIONS) {
|
||||
if (game.canGo(dir))
|
||||
@ -50,11 +48,21 @@ public class Rabbit implements INonPlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAttack() {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDamage() {
|
||||
return 1000;
|
||||
@ -71,8 +79,8 @@ public class Rabbit implements INonPlayer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
public String getName() {
|
||||
return "rabbit";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -91,14 +99,4 @@ public class Rabbit implements INonPlayer {
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "rabbit";
|
||||
}
|
||||
|
||||
}
|
||||
|
489
src/inf101/v18/rogue101/game/Game.java
Normal file
489
src/inf101/v18/rogue101/game/Game.java
Normal file
@ -0,0 +1,489 @@
|
||||
package inf101.v18.rogue101.game;
|
||||
|
||||
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;
|
||||
|
||||
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.ILocation;
|
||||
import inf101.v18.rogue101.Main;
|
||||
import inf101.v18.rogue101.examples.Carrot;
|
||||
import inf101.v18.rogue101.examples.Rabbit;
|
||||
import inf101.v18.rogue101.map.GameMap;
|
||||
import inf101.v18.rogue101.map.IGameMap;
|
||||
import inf101.v18.rogue101.map.IMapView;
|
||||
import inf101.v18.rogue101.map.MapReader;
|
||||
import inf101.v18.rogue101.objects.Dust;
|
||||
import inf101.v18.rogue101.objects.IActor;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
import inf101.v18.rogue101.objects.INonPlayer;
|
||||
import inf101.v18.rogue101.objects.IPlayer;
|
||||
import inf101.v18.rogue101.objects.Wall;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
import javafx.scene.input.KeyCode;
|
||||
import javafx.scene.paint.Color;
|
||||
|
||||
public class Game implements IGame {
|
||||
/**
|
||||
* All the IActors that have things left to do this turn
|
||||
*/
|
||||
private List<IActor> actors = Collections.synchronizedList(new ArrayList<>());
|
||||
/**
|
||||
* For fancy solution to factory problem
|
||||
*/
|
||||
private Map<String, Supplier<IItem>> itemFactories = new HashMap<>();
|
||||
/**
|
||||
* Useful random generator
|
||||
*/
|
||||
private Random random = new Random();
|
||||
/**
|
||||
* The game map. {@link IGameMap} gives us a few more details than
|
||||
* {@link IMapView} (write access to item lists); the game needs this but
|
||||
* individual items don't.
|
||||
*/
|
||||
private IGameMap map;
|
||||
private IActor currentActor;
|
||||
private ILocation currentLocation;
|
||||
private int movePoints = 0;
|
||||
private final ITurtle painter;
|
||||
private final Printer printer;
|
||||
private int numPlayers = 0;
|
||||
|
||||
public Game(Screen screen, ITurtle painter, Printer printer) {
|
||||
this.painter = painter;
|
||||
this.printer = printer;
|
||||
|
||||
// TODO: (*very* optional) for advanced factory technique, use
|
||||
// something like "itemFactories.put("R", () -> new Rabbit());"
|
||||
// must be done *before* you read the map
|
||||
|
||||
// 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
|
||||
IGrid<String> inputGrid = MapReader.readFile("maps/level1.txt");
|
||||
if (inputGrid == null) {
|
||||
System.err.println("Map not found – falling back to builtin map");
|
||||
inputGrid = MapReader.readString(Main.BUILTIN_MAP);
|
||||
}
|
||||
this.map = new GameMap(inputGrid.getArea());
|
||||
for (ILocation loc : inputGrid.locations()) {
|
||||
IItem item = createItem(inputGrid.get(loc));
|
||||
if (item != null) {
|
||||
map.add(loc, item);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Game(String mapString) {
|
||||
printer = new Printer(1280, 720);
|
||||
painter = new TurtlePainter(1280, 720, null);
|
||||
IGrid<String> inputGrid = MapReader.readString(mapString);
|
||||
this.map = new GameMap(inputGrid.getArea());
|
||||
for (ILocation loc : inputGrid.locations()) {
|
||||
IItem item = createItem(inputGrid.get(loc));
|
||||
if (item != null) {
|
||||
map.add(loc, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addItem(IItem item) {
|
||||
map.add(currentLocation, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addItem(String sym) {
|
||||
IItem item = createItem(sym);
|
||||
if (item != null)
|
||||
map.add(currentLocation, item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation attack(GridDirection dir, IItem target) {
|
||||
ILocation loc = map.go(currentLocation, dir);
|
||||
if (map.has(loc, target))
|
||||
throw new IllegalMoveException("Target isn't there!");
|
||||
|
||||
// TODO: implement attack
|
||||
|
||||
|
||||
map.clean(loc);
|
||||
|
||||
if (target.isDestroyed()) {
|
||||
return move(dir);
|
||||
} else {
|
||||
movePoints--;
|
||||
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();
|
||||
}
|
||||
|
||||
// process actors one by one; for the IPlayer, we return and wait for keypresses
|
||||
// Possible TODO: 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)
|
||||
while (!actors.isEmpty()) {
|
||||
// get the next player or non-player in the queue
|
||||
currentActor = actors.remove(0);
|
||||
if (currentActor.isDestroyed()) // skip if it's dead
|
||||
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
|
||||
|
||||
if (currentActor instanceof INonPlayer) {
|
||||
// computer-controlled players do their stuff right away
|
||||
((INonPlayer) currentActor).doTurn(this);
|
||||
// remove any dead items from current location
|
||||
map.clean(currentLocation);
|
||||
} else if (currentActor instanceof IPlayer) {
|
||||
if (currentActor.isDestroyed()) {
|
||||
// a dead human player gets removed from the game
|
||||
// TODO: you might want to be more clever here
|
||||
displayMessage("YOU DIE!!!");
|
||||
map.remove(currentLocation, currentActor);
|
||||
currentActor = null;
|
||||
currentLocation = null;
|
||||
} else {
|
||||
// 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 (numPlayers > 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Go through the map and collect all the actors.
|
||||
*/
|
||||
private void beginTurn() {
|
||||
numPlayers = 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<IItem> list = map.getAllModifiable(loc); // all items at loc
|
||||
Iterator<IItem> li = list.iterator(); // manual iterator lets us remove() items
|
||||
while (li.hasNext()) { // this is what "for(IItem item : list)" looks like on the inside
|
||||
IItem 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 IPlayer) {
|
||||
actors.add(0, (IActor) item); // we let the human player go first
|
||||
synchronized (this) {
|
||||
numPlayers++;
|
||||
}
|
||||
} else if (item instanceof IActor) {
|
||||
actors.add((IActor) item); // add other actors to the end of the list
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canGo(GridDirection dir) {
|
||||
return map.canGo(currentLocation, dir);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IItem createItem(String sym) {
|
||||
switch (sym) {
|
||||
case "#":
|
||||
return new Wall();
|
||||
case ".":
|
||||
// TODO: add Dust
|
||||
return null;
|
||||
case "R":
|
||||
return new Rabbit();
|
||||
case "C":
|
||||
return new Carrot();
|
||||
case "@":
|
||||
// TODO: add Player
|
||||
case " ":
|
||||
return null;
|
||||
default:
|
||||
// alternative/advanced method
|
||||
Supplier<IItem> factory = itemFactories.get(sym);
|
||||
if (factory != null) {
|
||||
return factory.get();
|
||||
} else {
|
||||
System.err.println("createItem: Don't know how to create a '" + sym + "'");
|
||||
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) {
|
||||
// it should be safe to print to lines Main.LINE_MSG1, Main.LINE_MSG2,
|
||||
// Main.LINE_MSG3
|
||||
// TODO: you can save the last three lines, and display/scroll them
|
||||
printer.clearLine(Main.LINE_MSG1);
|
||||
printer.printAt(1, Main.LINE_MSG1, s);
|
||||
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() {
|
||||
map.draw(painter, printer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean drop(IItem item) {
|
||||
if (item != null) {
|
||||
map.add(currentLocation, 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<IItem> getLocalItems() {
|
||||
return map.getItems(currentLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation getLocation() {
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation getLocation(GridDirection dir) {
|
||||
if (currentLocation.canGo(dir))
|
||||
return currentLocation.go(dir);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the game map. {@link IGameMap} gives us a few more details than
|
||||
* {@link IMapView} (write access to item lists); the game needs this but
|
||||
* individual items don't.
|
||||
*/
|
||||
@Override
|
||||
public IMapView getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<GridDirection> getPossibleMoves() {
|
||||
// TODO
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ILocation> getVisible() {
|
||||
// TODO: maybe replace 3 by some sort of visibility range obtained from
|
||||
// currentActor?
|
||||
return map.getNeighbourhood(currentLocation, 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return map.getWidth();
|
||||
}
|
||||
|
||||
public void keyPressed(KeyCode code) {
|
||||
// only an IPlayer/human can handle keypresses, and only if it's the human's
|
||||
// turn
|
||||
if (currentActor instanceof IPlayer) {
|
||||
((IPlayer) currentActor).keyPressed(this, code); // do your thing
|
||||
if (movePoints <= 0)
|
||||
doTurn(); // proceed with turn if we're out of moves
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation move(GridDirection dir) {
|
||||
if (movePoints < 1)
|
||||
throw new IllegalMoveException("You're out of moves!");
|
||||
ILocation newLoc = map.go(currentLocation, dir);
|
||||
map.remove(currentLocation, currentActor);
|
||||
map.add(newLoc, currentActor);
|
||||
currentLocation = newLoc;
|
||||
movePoints--;
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IItem pickUp(IItem item) {
|
||||
if (item != null && map.has(currentLocation, item)) {
|
||||
// TODO: bruk getAttack()/getDefence() til å avgjøre om man får til å plukke opp
|
||||
// tingen
|
||||
// evt.: en IActor kan bare plukkes opp hvis den har få/ingen helsepoeng igjen
|
||||
map.remove(currentLocation, item);
|
||||
return item;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ILocation rangedAttack(GridDirection dir, IItem target) {
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
@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 IActor getActor() {
|
||||
return currentActor;
|
||||
}
|
||||
|
||||
public ILocation setCurrent(IActor actor) {
|
||||
currentLocation = map.getLocation(actor);
|
||||
if (currentLocation != null) {
|
||||
currentActor = actor;
|
||||
movePoints = 1;
|
||||
}
|
||||
return currentLocation;
|
||||
}
|
||||
|
||||
public IActor setCurrent(ILocation loc) {
|
||||
List<IActor> list = map.getActors(loc);
|
||||
if (!list.isEmpty()) {
|
||||
currentActor = list.get(0);
|
||||
currentLocation = loc;
|
||||
movePoints = 1;
|
||||
}
|
||||
return currentActor;
|
||||
}
|
||||
|
||||
public IActor setCurrent(int x, int y) {
|
||||
return setCurrent(map.getLocation(x, y));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Random getRandom() {
|
||||
return random;
|
||||
}
|
||||
}
|
@ -1,23 +1,235 @@
|
||||
package inf101.v18.rogue101.game;
|
||||
|
||||
import java.util.Collection;
|
||||
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;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
IMapView getMap();
|
||||
/**
|
||||
* 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);
|
||||
|
||||
ILocation move(GridDirection dir);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
ILocation rangedAttack(GridDirection dir, IItem target);
|
||||
/**
|
||||
* @param dir
|
||||
* @return True if it's possible to move in the given direction
|
||||
*/
|
||||
boolean canGo(GridDirection dir);
|
||||
|
||||
Collection<IItem> getLocalItems();
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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 texxt 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.
|
||||
@ -27,22 +239,38 @@ public interface IGame {
|
||||
* @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
|
||||
* @param dir
|
||||
* A direction
|
||||
* @return A location, or <code>null</code> if the location would be outside the
|
||||
* map
|
||||
*/
|
||||
ILocation getLocation(GridDirection dir);
|
||||
|
||||
IItem pickUp(IItem item);
|
||||
/**
|
||||
* @return The map
|
||||
*/
|
||||
IMapView getMap();
|
||||
|
||||
boolean drop(IItem item);
|
||||
|
||||
boolean canGo(GridDirection dir);
|
||||
/**
|
||||
* @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.
|
||||
@ -54,26 +282,39 @@ public interface IGame {
|
||||
* @return A list of grid cells visible from the {@link #getLocation()}
|
||||
*/
|
||||
List<ILocation> getVisible();
|
||||
|
||||
|
||||
/**
|
||||
* @return Width of the map
|
||||
*/
|
||||
int getWidth();
|
||||
|
||||
int getHeight();
|
||||
|
||||
IItem createItem(String symbol);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
void addItem(IItem item);
|
||||
/**
|
||||
* 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);
|
||||
|
||||
void addItem(String sym);
|
||||
|
||||
void displayMessage(String s);
|
||||
|
||||
void formatMessage(String s, Object... args);
|
||||
|
||||
void displayStatus(String s);
|
||||
|
||||
void formatStatus(String s, Object...args);
|
||||
|
||||
void displayDebug(String s);
|
||||
|
||||
void formatDebug(String s, Object... args);
|
||||
/**
|
||||
* @return A random generator
|
||||
*/
|
||||
Random getRandom();
|
||||
}
|
||||
|
254
src/inf101/v18/rogue101/map/GameMap.java
Normal file
254
src/inf101/v18/rogue101/map/GameMap.java
Normal file
@ -0,0 +1,254 @@
|
||||
package inf101.v18.rogue101.map;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
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.ILocation;
|
||||
import inf101.v18.grid.IMultiGrid;
|
||||
import inf101.v18.grid.MultiGrid;
|
||||
import inf101.v18.rogue101.Main;
|
||||
import inf101.v18.rogue101.game.IllegalMoveException;
|
||||
import inf101.v18.rogue101.objects.IActor;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
import inf101.v18.rogue101.objects.Wall;
|
||||
import javafx.scene.canvas.GraphicsContext;
|
||||
|
||||
public class GameMap implements IGameMap {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
list.add(item);
|
||||
// TODO: should be sorted!
|
||||
}
|
||||
|
||||
@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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IActor> getActors(ILocation loc) {
|
||||
List<IActor> items = new ArrayList<>();
|
||||
for (IItem item : grid.get(loc)) {
|
||||
if (item instanceof IActor)
|
||||
items.add((IActor) item);
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
@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!
|
||||
|
||||
// TODO: implement this!
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
48
src/inf101/v18/rogue101/map/IGameMap.java
Normal file
48
src/inf101/v18/rogue101/map/IGameMap.java
Normal file
@ -0,0 +1,48 @@
|
||||
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);
|
||||
|
||||
}
|
@ -2,8 +2,6 @@ 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.GridDirection;
|
||||
import inf101.v18.grid.IArea;
|
||||
import inf101.v18.grid.ILocation;
|
||||
@ -12,37 +10,181 @@ import inf101.v18.rogue101.objects.IActor;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
|
||||
public interface IMapView {
|
||||
boolean canGo(ILocation from, GridDirection dir);
|
||||
|
||||
boolean canGo(ILocation to);
|
||||
|
||||
ILocation go(ILocation from, GridDirection dir) throws IllegalMoveException;
|
||||
|
||||
boolean hasActors(ILocation loc);
|
||||
|
||||
List<IActor> getActors(ILocation loc);
|
||||
|
||||
boolean hasItems(ILocation loc);
|
||||
|
||||
List<IItem> getItems(ILocation loc);
|
||||
|
||||
List<IItem> getAll(ILocation loc);
|
||||
|
||||
boolean hasWall(ILocation loc);
|
||||
|
||||
void remove(ILocation loc, IItem item);
|
||||
|
||||
/**
|
||||
* Add an item to the map.
|
||||
*
|
||||
* @param loc
|
||||
* A location
|
||||
* @param item
|
||||
* the item
|
||||
*/
|
||||
void add(ILocation loc, IItem item);
|
||||
|
||||
boolean has(ILocation loc, IItem target);
|
||||
|
||||
ILocation getLocation(int x, int y);
|
||||
ILocation getLocation(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();
|
||||
|
||||
int getWidth();
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
@ -1,7 +1,5 @@
|
||||
package inf101.v18.rogue101.map;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Scanner;
|
||||
@ -36,50 +34,6 @@ import inf101.v18.grid.MyGrid;
|
||||
* @author anya (Rogue101 update, 2018)
|
||||
*/
|
||||
public class MapReader {
|
||||
/**
|
||||
* Load map from file.
|
||||
* <p>
|
||||
* Files are search for relative to the folder containing the MapReader class.
|
||||
*
|
||||
* @return the dungeon map as a grid of characters read from the file, or null
|
||||
* if it failed
|
||||
*/
|
||||
public static IGrid<String> readFile(String path) {
|
||||
IGrid<String> symbolMap = null;
|
||||
InputStream stream = MapReader.class.getResourceAsStream(path);
|
||||
if(stream == null)
|
||||
return null;
|
||||
try (Scanner in = new Scanner(stream, "UTF-8")) {
|
||||
int width = in.nextInt();
|
||||
int height = in.nextInt();
|
||||
// System.out.println(width + " " + height);
|
||||
symbolMap = new MyGrid<String>(width, height, " ");
|
||||
in.nextLine();
|
||||
fillMap(symbolMap, in);
|
||||
}
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
return symbolMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the dungeon map as a grid of characters read from the input string, or null
|
||||
* if it failed
|
||||
*/
|
||||
public static IGrid<String> readString(String input) {
|
||||
IGrid<String> symbolMap = null;
|
||||
try (Scanner in = new Scanner(input)) {
|
||||
int width = in.nextInt();
|
||||
int height = in.nextInt();
|
||||
symbolMap = new MyGrid<String>(width, height, " ");
|
||||
in.nextLine();
|
||||
fillMap(symbolMap, in);
|
||||
}
|
||||
return symbolMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method fills the previously initialized {@link #symbolMap} with the
|
||||
* characters read from the file.
|
||||
@ -98,4 +52,48 @@ public class MapReader {
|
||||
xy[1]++;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load map from file.
|
||||
* <p>
|
||||
* Files are search for relative to the folder containing the MapReader class.
|
||||
*
|
||||
* @return the dungeon map as a grid of characters read from the file, or null
|
||||
* if it failed
|
||||
*/
|
||||
public static IGrid<String> readFile(String path) {
|
||||
IGrid<String> symbolMap = null;
|
||||
InputStream stream = MapReader.class.getResourceAsStream(path);
|
||||
if (stream == null)
|
||||
return null;
|
||||
try (Scanner in = new Scanner(stream, "UTF-8")) {
|
||||
int width = in.nextInt();
|
||||
int height = in.nextInt();
|
||||
// System.out.println(width + " " + height);
|
||||
symbolMap = new MyGrid<String>(width, height, " ");
|
||||
in.nextLine();
|
||||
fillMap(symbolMap, in);
|
||||
}
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
}
|
||||
return symbolMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the dungeon map as a grid of characters read from the input string,
|
||||
* or null if it failed
|
||||
*/
|
||||
public static IGrid<String> readString(String input) {
|
||||
IGrid<String> symbolMap = null;
|
||||
try (Scanner in = new Scanner(input)) {
|
||||
int width = in.nextInt();
|
||||
int height = in.nextInt();
|
||||
symbolMap = new MyGrid<String>(width, height, " ");
|
||||
in.nextLine();
|
||||
fillMap(symbolMap, in);
|
||||
}
|
||||
return symbolMap;
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,20 @@ package inf101.v18.rogue101.objects;
|
||||
|
||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||
import inf101.v18.rogue101.events.IEvent;
|
||||
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;
|
||||
@ -18,8 +27,8 @@ public class Dust implements IItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return 0;
|
||||
public String getName() {
|
||||
return "thick layer of dust";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -32,21 +41,9 @@ public class Dust implements IItem {
|
||||
return BlocksAndBoxes.BLOCK_HALF;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int handleDamage(IGame game, IItem source, int amount) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "thick layer of dust";
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,7 +1,24 @@
|
||||
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();
|
||||
}
|
||||
|
@ -18,131 +18,6 @@ import inf101.v18.rogue101.game.IGame;
|
||||
* @author anya
|
||||
*/
|
||||
public interface IItem extends Comparable<IItem> {
|
||||
/**
|
||||
* The defence score determines how hard an object/actor is to hit or grab.
|
||||
*
|
||||
* @return Defence score of this object
|
||||
*/
|
||||
int getDefence();
|
||||
|
||||
/**
|
||||
* 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 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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
/**
|
||||
* @return "a" or "an", depending on the name
|
||||
*/
|
||||
default String getArticle() {
|
||||
return "a";
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
default boolean isDestroyed() {
|
||||
return getCurrentHealth() < 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
default int compareTo(IItem other) {
|
||||
return Integer.compare(getSize(), other.getSize());
|
||||
@ -175,4 +50,132 @@ public interface IItem extends Comparable<IItem> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,21 @@ 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);
|
||||
}
|
||||
|
@ -4,5 +4,22 @@ 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
|
||||
*/
|
||||
void keyPressed(IGame game, KeyCode key);
|
||||
}
|
||||
|
@ -2,13 +2,21 @@ package inf101.v18.rogue101.objects;
|
||||
|
||||
import inf101.v18.gfx.gfxmode.ITurtle;
|
||||
import inf101.v18.gfx.textmode.BlocksAndBoxes;
|
||||
import inf101.v18.rogue101.events.GameEvent;
|
||||
import inf101.v18.rogue101.events.IEvent;
|
||||
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;
|
||||
@ -20,8 +28,8 @@ public class Wall implements IItem {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentHealth() {
|
||||
return hp;
|
||||
public String getName() {
|
||||
return "wall";
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -33,20 +41,10 @@ public class Wall implements IItem {
|
||||
public String getSymbol() {
|
||||
return BlocksAndBoxes.BLOCK_FULL;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int handleDamage(IGame game, IItem source, int amount) {
|
||||
hp -= amount;
|
||||
return amount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean draw(ITurtle painter, double w, double h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "wall";
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,19 @@
|
||||
package inf101.v18.rogue101.tests;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import inf101.v18.grid.ILocation;
|
||||
import inf101.v18.rogue101.map.GameMap;
|
||||
|
||||
class GameMapTest {
|
||||
|
||||
@Test
|
||||
void testSortedAdd() {
|
||||
// TODO
|
||||
GameMap gameMap = new GameMap(20, 20);
|
||||
ILocation location = gameMap.getLocation(10, 10);
|
||||
// TODO:
|
||||
fail("Not yet implemented");
|
||||
}
|
||||
|
||||
|
42
src/inf101/v18/rogue101/tests/PlayerTest.java
Normal file
42
src/inf101/v18/rogue101/tests/PlayerTest.java
Normal file
@ -0,0 +1,42 @@
|
||||
package inf101.v18.rogue101.tests;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import inf101.v18.grid.GridDirection;
|
||||
import inf101.v18.grid.ILocation;
|
||||
import inf101.v18.rogue101.game.Game;
|
||||
import inf101.v18.rogue101.game.IGame;
|
||||
import inf101.v18.rogue101.map.GameMap;
|
||||
import inf101.v18.rogue101.objects.IItem;
|
||||
import inf101.v18.rogue101.objects.IPlayer;
|
||||
import javafx.scene.input.KeyCode;
|
||||
|
||||
class PlayerTest {
|
||||
public static String TEST_MAP = "40 5\n" //
|
||||
+ "########################################\n" //
|
||||
+ "#...... ..C.R ......R.R......... ..R...#\n" //
|
||||
+ "#.R@R...... ..........RC..R...... ... .#\n" //
|
||||
+ "#... ..R........R......R. R........R.RR#\n" //
|
||||
+ "########################################\n" //
|
||||
;
|
||||
|
||||
@Test
|
||||
void testPlayer1() {
|
||||
// new game with our test map
|
||||
Game game = new Game(TEST_MAP);
|
||||
// pick (3,2) as the "current" position; this is where the player is on the
|
||||
// test map, so it'll set up the player and return it
|
||||
IPlayer player = (IPlayer) game.setCurrent(3, 2);
|
||||
|
||||
|
||||
// find players location
|
||||
ILocation loc = game.getLocation();
|
||||
// press "UP" key
|
||||
player.keyPressed(game, KeyCode.UP);
|
||||
// see that we moved north
|
||||
assertEquals(loc.go(GridDirection.NORTH), game.getLocation());
|
||||
}
|
||||
|
||||
}
|
@ -8,17 +8,6 @@ import java.util.Random;
|
||||
public class ElementGenerator<T> extends AbstractGenerator<T> {
|
||||
private List<T> elts;
|
||||
|
||||
/**
|
||||
* New ElementGenerator, will pick a random element from a list.
|
||||
*
|
||||
* @requires list must not be empty
|
||||
*/
|
||||
public ElementGenerator(List<T> elts) {
|
||||
if (elts.size() == 0)
|
||||
throw new IllegalArgumentException();
|
||||
this.elts = elts;
|
||||
}
|
||||
|
||||
/**
|
||||
* New ElementGenerator, will pick a random element from a collection.
|
||||
*
|
||||
@ -30,6 +19,17 @@ public class ElementGenerator<T> extends AbstractGenerator<T> {
|
||||
this.elts = new ArrayList<>(elts);
|
||||
}
|
||||
|
||||
/**
|
||||
* New ElementGenerator, will pick a random element from a list.
|
||||
*
|
||||
* @requires list must not be empty
|
||||
*/
|
||||
public ElementGenerator(List<T> elts) {
|
||||
if (elts.size() == 0)
|
||||
throw new IllegalArgumentException();
|
||||
this.elts = elts;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T generate(Random r) {
|
||||
return elts.get(r.nextInt(elts.size()));
|
||||
|
@ -4,7 +4,6 @@ import java.util.Random;
|
||||
|
||||
import inf101.v18.grid.IArea;
|
||||
import inf101.v18.grid.ILocation;
|
||||
import inf101.v18.grid.IPosition;
|
||||
|
||||
public class LocationGenerator extends AbstractGenerator<ILocation> {
|
||||
private final IArea area;
|
||||
|
Reference in New Issue
Block a user